Usando un OrderedDict en ** kwargs

¿Es posible pasar una instancia de OrderedDict a una función que usa la syntax de **kwargs y conservar 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, ordenamiento aleatorio típico.

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

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


    Lo primero que debes tener en cuenta es que el valor que pasas en **example no se convierte automáticamente en el valor en **kwargs . Considere este caso, donde los kwargs solo tendrán parte del 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 se superponen en absoluto:

     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 forma de especificar que desea un **kwargs , sin importar de dónde provienen las palabras clave: **kwargs palabras clave explícitas en el orden en que aparecen, seguidos de todos los elementos del **example en el orden en que aparecen en el example (que podría ser arbitrario si el example fuera un dict , pero también podría ser significativo si fuera un OrderedDict ).

    Definir todos los detalles complicados y mantener el rendimiento aceptable, resulta más difícil de lo que parece. Vea PEP 468 , y los hilos enlazados, para una discusión sobre la idea. Parece que se ha estancado esta vez, pero si alguien lo toma y lo defiende (y escribe una implementación de referencia para que la gente juegue, lo que depende de un OrderedDict puede acceder desde la API de C, pero es de esperar que haya más de 3.5) ), Sospecho que eventualmente entraría en el lenguaje.


    Por cierto, tenga en cuenta que si esto fuera posible, sería casi seguro que se usaría en el constructor para OrderedDict . Pero si lo intentas, todo lo que estás haciendo es congelar un 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 el example como un argumento normal en lugar de descomprimirlo:

     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 más feo.

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

    Este es ahora el 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 lo indican las otras respuestas.

    Cuando Python encuentra la construcción **kwargs en una firma, espera que kwargs sea ​​un “mapeo”, lo que significa dos cosas: (1) ser capaz de llamar a kwargs.keys() para obtener un iterable de las claves contenidas por el mapeo, y (2) que se puede llamar a kwargs.__getitem__(key) para cada clave en el iterable devuelto por keys() y que el valor resultante es el que se desea asociar para esa clave.

    Internamente, Python luego “transformará” lo 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 los kwargs ya son un dict , y lo sería, ya que no hay razón para construir un dict totalmente equivalente del que se transmite.

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

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

    Este es un ejemplo estúpido de una clase que se puede “desempaquetar” pero que siempre trata a todos los valores desempaquetados como 42 (aunque 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() 

    Luego define una función para imprimir los contenidos desempaquetados:

     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 devolviendo 42) es el grado de su habilidad para jugar con cómo **kwargs funciona en Python.

    Hay un poco más de flexibilidad para jugar con cómo se desempaquetan *args . Para más información, vea esta pregunta: < ¿El desempaquetado de argumentos utiliza la iteración o la obtención de elementos? >.