Usando un OrderedDict en * * kwargs


¿Es posible pasar una instancia OrderedDict a una función que usa la sintaxis **kwargs y conserva el orden?

Lo que me gustaría hacer es :

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

Sin embargo, el resultado real es:

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

Es decir, típico orden aleatorio de dict.

Tengo otros usos donde establecer el orden explícitamente es bueno, por lo que quiero mantener **kwargs y no solo pasar el OrderedDict como un argumento regular

Author: theodox, 2014-11-05

3 answers

A partir de Python 3.6, el orden de los argumentos de la palabra clave se conserva. Antes de 3.6, no es posible ya que el OrderedDict se convierte en un dict.


Lo primero que debe darse cuenta es que el valor que pasa en **example no se convierte automáticamente en el valor en **kwargs. Considere este caso, donde kwargs solo tendrá parte de example:

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

O este caso, donde tendrá más valores que los del ejemplo:

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

O incluso no hay superposición en

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

Entonces, lo que estás pidiendo realmente no tiene sentido.

Aún así, podría ser bueno si hubiera alguna manera de especificar que desea un **kwargs ordenado, no importa de donde provienen las palabras clave-args de palabras clave explícitas en el orden en que aparecen, seguido de todos los elementos de **example en el orden en que aparecen en example (lo que podría ser arbitrario si example fuera un dict, pero también podría ser significativo si fuera un OrderedDict).

Definiendo todo el fiddly los detalles, y mantener el rendimiento aceptable, resulta ser más difícil de lo que parece. Ver PEP 468 , y los hilos enlazados, para alguna discusión sobre la idea. Parece que se ha estancado esta vez, pero si alguien lo recoge y lo defiende (y escribe una implementación de referencia para que la gente juegue con ella, que depende de un OrderedDict accesible desde la API de C, pero que con suerte estará allí en 3.5+), sospecho que eventualmente entraría en el lenguaje.


Por el de manera, tenga en cuenta que si este fuera posible, casi seguramente se usaría en el constructor para OrderedDict en sí. Pero si lo intentas, todo lo que estás haciendo es congelar algún orden arbitrario como el orden permanente:

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

Mientras tanto, ¿qué opciones tienes?

Bueno, la opción obvia es simplemente pasar example como un argumento normal en lugar de desempaquetarlo:

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

O, por supuesto, puede usar *args y pasar los elementos como tuplas, pero eso es generalmente feo.

Podría haber algunas otras soluciones en los hilos vinculados desde el PEP, pero en realidad, ninguno de ellos va a ser mejor que esto. (Excepto... IIRC, Li Haoyi se le ocurrió una solución basada en su MacroPy para pasar argumentos de palabras clave de retención de orden, pero no recuerdo los detalles. Las soluciones MacroPy en general son increíbles si estás dispuesto a usar MacroPy y escribir código que no se lee como Python, pero que no siempre es apropiado {)

 24
Author: abarnert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-29 18:48:38

Este es ahora el valor predeterminado en 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

No es posible hacerlo antes como se indica en las otras respuestas.

 14
Author: damio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-01 11:01:43

Cuando Python encuentra la construcción **kwargs en una firma, espera que kwargs sea una "asignación", lo que significa dos cosas: (1) poder llamar a kwargs.keys() para obtener un iterable de las claves contenidas en la asignación, y (2) que kwargs.__getitem__(key) se pueda llamar para cada clave en el iterable devuelto por keys() y que el valor resultante sea el deseado para ser asociado para esa clave.

Internamente, Python luego "transformará" lo que sea que sea la asignación en un diccionario, algo así como esto:

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

Esto parece un poco tonto si piensas que kwargs ya es un dict} y lo sería, ya que no hay razón para construir un dict totalmente equivalente a partir del que se pasa.

Pero cuando kwargs no es necesariamente un dict, entonces importa bajar su contenido a una estructura de datos predeterminada adecuada para que el código que lleva a cabo el desempaquetado del argumento siempre sepa con qué está trabajando.

Por lo tanto, usted puede meterse con la forma en que un cierto tipo de datos se desempaqueta, pero debido a la conversión a dict por el bien de un protocolo arg-unpacking-consistente, simplemente sucede que colocar garantías en el orden de desempaquetado de argumentos no es posible (ya que dict no realiza un seguimiento del orden en que se agregan los elementos). Si el lenguaje de Python trajo **kwargs hacia abajo en un OrderedDict en lugar de un dict (lo que significa que el orden de las claves como args de palabras clave sería el orden en el que se atraviesan), entonces al pasar ya sea una OrderedDict o alguna otra estructura de datos donde keys() respeta algún tipo de orden, podría esperar un cierto orden en los argumentos. Es solo una peculiaridad de la implementación que dict se elija como estándar, y no algún otro tipo de asignación.

Aquí hay un ejemplo tonto de una clase que se puede" desempaquetar " pero que siempre trata todos los valores desempaquetados como 42 (a pesar de que en realidad no lo son):

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()

A continuación, definir una función para imprimir el contenido:

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

Y hacer un valor y probarlo:

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

Esta entrega personalizada de pares clave-valor (aquí, dumbly siempre devuelve 42) es la medida de su capacidad para jugar con cómo funciona **kwargs en Python.

Hay un poco más de flexibilidad para retocar cómo *args se desempaqueta. Para obtener más información al respecto, consulte esta pregunta: ¿El desempaquetado de argumentos utiliza iteración o obtención de elementos? >.

 2
Author: ely,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:57