2014-11-05 3 views
21

Можно ли передать экземпляр OrderedDict функции, которая использует синтаксис **kwargs и сохранить заказ?Использование OrderedDict в ** kwargs

То, что я хотел бы сделать, это:

def I_crave_order(**kwargs): 
    for k, v in kwargs.items(): 
     print k, v 

example = OrderedDict([('first', 1), ('second', 2), ('third', -1)]) 

I_crave_order(**example) 
>> first 1 
>> second 2 
>> third -1 

Однако фактический результат:

>> second 2 
>> third -1 
>> first 1 

т.е. типичное случайное упорядочение ДИКТ.

У меня есть и другие применения, где задания порядка явно хорошо, поэтому я хочу, чтобы **kwargs и не просто передать OrderedDict как обычный аргумент

+0

Спасибо за подробные ответы. То, что я закончил, это разрешить необязательный первый аргумент (упорядоченный dict), который будет использоваться в предпочтении ** kwargs, если он предоставлен. Отличная информация, хотя – theodox

+0

** Смотрите также: ** https://duckduckgo.com/?q=pep468 – dreftymac

ответ

22

По состоянию на Python 3.6, the keyword argument order is preserved. До 3.6 это невозможно, так как OrderedDict превращается в dict.


Первое, что нужно понимать, что значение вы передаете в **example не автоматически становится значением в **kwargs. Рассмотрим случай, когда kwargs будет иметь только часть example:

def f(a, **kwargs): 
    pass 
example = {'a': 1, 'b': 2} 
f(**example) 

Или этот случай, где он будет иметь больше значения, чем в примере:

example = {'b': 2} 
f(a=1, c=3, **example) 

или даже без перекрытия на всех:

example = {'a': 1} 
f(b=2, **example) 

Итак, что вы просите не имеет смысла.

Тем не менее, было бы хорошо, если бы каким-нибудь способом, чтобы определить, что вы хотите упорядоченный **kwargs, независимо от того, где ключевых слов пришли из-явных ключевых слов аргументов в порядке их появления, а затем все предметы **example в том порядке, в котором они отображаются в example (что может быть произвольным, если example были dict, но также могут иметь смысл, если это был OrderedDict).

Определить все детали, затрудняющие работу, и обеспечить приемлемую производительность, оказывается сложнее, чем кажется. См. PEP 468 и связанные темы для обсуждения этой идеи. На этот раз он зашел в тупик, но если кто-то его подхватит и защитит (и пишет ссылку для людей, с которыми можно играть, что зависит от OrderedDict, доступного из C API, но, мы надеемся, будет там в 3.5+), Я подозреваю, что он в конечном итоге попадет на этот язык.


Кстати, обратите внимание, что если это были возможно, он почти наверняка будет использоваться в конструкторе для самого OrderedDict. Но если вы попытаетесь это сделать, все, что вы делаете замерзает некоторый произвольный порядок в качестве постоянного порядка:

>>> d = OrderedDict(a=1, b=2, c=3) 
OrderedDict([('a', 1), ('c', 3), ('b', 2)]) 

Между тем, какие варианты у вас есть?

Ну, очевидный вариант просто передать example как обычный аргумент вместо распаковки:

def f(example): 
    pass 
example = OrderedDict([('a', 1), ('b', 2)]) 
f(example) 

Или, конечно, вы можете использовать *args и передавать предметы, как кортежи, но это обычно уродливее ,

В потоках, связанных с PEP, могут возникнуть некоторые другие обходные пути, но на самом деле ни один из них не будет лучше этого. (За исключением ... IIRC, Ли Хаойи придумал решение, основанное на его MacroPy, чтобы передать аргументы ключевого слова, сохраняющие порядок, но я не помню подробностей. Решения MacroPy в целом удивительны, если вы готовы использовать MacroPy и писать код который не совсем читается как Python, но это не всегда подходит ...)

+0

Не могли бы вы обновить этот ответ для python 3.6? – Moberg

2

Когда Python встречает конструкцию **kwargs в сигнатуре, она ожидает, что kwargs будет «отображением», что означает две вещи: (1) чтобы иметь возможность вызывать kwargs.keys() для получения итерации ключей, содержащихся в сопоставлении, и (2), что может быть вызван kwargs.__getitem__(key) для каждого ключа в итерабельном, возвращаемом keys(), и что результирующее значение является желательным для этого ключ.

Внутренне, Python будет «трансформировать» независимо от отображения в словарь, вроде этого:

**kwargs -> {key:kwargs[key] for key in kwargs.keys()} 

Это выглядит немного глупо, если вы думаете, что kwargs уже dict - и будет, так как нет никакой причины, чтобы построить полностью эквивалентно dict от той, которая передается в.

Но когда kwargs не обязательно dict, то это важно довести ее содержание вниз в соответствующую структуру данных по умолчанию, так что код который выполняет распаковку аргументов, всегда знает, с чем он работает.

Таким образом, вы можете бардак с тем, как определенный тип данных получает в неупакованном виде, но из-за преобразования в dict ради последовательного Arg-распаковке-протокола, это просто происходит, что размещение гарантий по заказу аргумент распаковки невозможен (поскольку dict не отслеживает порядок добавления элементов). Если язык Python привел **kwargs в OrderedDict вместо dict (это означает, что порядок ключей в качестве ключевых слов args будет порядком, в котором они пройдены), затем путем передачи либо OrderedDict, либо какой-либо другой структуры данных, где keys() уважает какой-то порядок, вы могли бы ожидать определенного порядка по аргументам. Это всего лишь причуда реализации, которая выбрана в качестве стандарта, а не каким-либо другим типом сопоставления dict.

Вот тупой пример класса, который может «быть распакованы», но который всегда рассматривает все распакованные значения, как 42 (даже если они не являются на самом деле):

class MyOrderedDict(object): 
    def __init__(self, odict): 
     self._odict = odict 

    def __repr__(self): 
     return self._odict.__repr__() 

    def __getitem__(self, item): 
     return 42 

    def __setitem__(self, item, value): 
     self._odict[item] = value 

    def keys(self): 
     return self._odict.keys() 

Затем определим функцию для печати распакованные содержание:

def foo(**kwargs): 
    for k, v in kwargs.iteritems(): 
     print k, v 

и сделать значение и попробовать его:

In [257]: import collections; od = collections.OrderedDict() 

In [258]: od['a'] = 1; od['b'] = 2; od['c'] = 3; 

In [259]: md = MyOrderedDict(od) 

In [260]: print md 
OrderedDict([('a', 1), ('b', 2), ('c', 3)]) 

In [261]: md.keys() 
Out[261]: ['a', 'b', 'c'] 

In [262]: foo(**md) 
a 42 
c 42 
b 42 

Это с ustomized поставка пар ключ-значение (здесь, тупо всегда возвращающ 42) будет степень вашей способности возиться с как **kwargs работает в Python.

Существует немного больше гибкости для возиться с тем, как *args получает неупакованную. Подробнее об этом см. Этот вопрос: < Does argument unpacking use iteration or item-getting?>.

13

This is now the default in python 3.6.

Python 3.6.0a4+ (default:d43f819caea7, Sep 8 2016, 13:05:34) 
>>> def func(**kw): print(kw.keys()) 
... 
>>> func(a=1, b=2, c=3, d=4, e=5) 
dict_keys(['a', 'b', 'c', 'd', 'e']) # expected order 

Невозможно сделать это раньше, как указано в других ответах.

Смежные вопросы