Подтвердить что ты не робот

Использование 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 в качестве обычного аргумента

4b9b3361

Ответ 1

Как и в случае с Python 3.6, сохраняется порядок аргументов в ключе. До 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, независимо от того, где ключевые слова были получены из-явных ключевых слов args в порядке их появления, а затем все элементы **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, но это не всегда подходит...)

Ответ 2

Теперь это значение по умолчанию в 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

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

Ответ 3

Когда 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 не обязательно a dict, то важно привести его содержимое в подходящую структуру данных по умолчанию, чтобы код, который выполняет распаковку аргументов, всегда знает, с чем он работает.

Итак, вы можете столкнуться с тем, что определенный тип данных распаковывается, но из-за преобразования в dict для последовательного протокола arg-unpacking-протокола просто случается, что размещение гарантий по порядку аргумента распаковка невозможна (поскольку 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

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

Есть немного больше гибкости для того, чтобы поработать с тем, как *args распаковывается. Более подробно об этом см. Этот вопрос: Развертывание аргументов использует итерацию или получение элементов? > .