Utilisation D'un OrderedDict en * * kwargs

est-il possible de passer une instance OrderedDict à une fonction qui utilise la syntaxe **kwargs et de conserver l'ordre?

ce que j'aimerais faire c'est :

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

cependant le résultat réel est:

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

, c'est-à-dire l'ordre aléatoire typique des dictons.

j'ai d'autres utilisations où le réglage explicite de l'ordre est bon, donc je veux garder **kwargs et pas simplement passer L'OrderedDict comme un argument régulier

28
demandé sur theodox 2014-11-05 04:10:48

3 réponses

comme Python 3.6, l'ordre d'argument de mot clé est préservé . Avant 3.6, il n'est pas possible puisque le OrderedDict se transforme en un dict .


la première chose à réaliser est que la valeur que vous passez dans **example ne devient pas automatiquement la valeur dans **kwargs . Considérons ce cas ,où kwargs aura seulement une partie de example :

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

ou ce cas, où il aura plus de valeurs que celles de l'exemple:

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

ou même pas de chevauchement du tout:

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

donc, ce que vous demandez n'a pas vraiment de sens.

encore, il pourrait être agréable s'il y avait un moyen de spécifier que vous voulez un commandé **kwargs , peu importe les mots-clés proviennent de-mots-clés explicites args in l'ordre qu'ils apparaissent, suivis par tous les éléments de **example dans l'ordre où ils apparaissent dans example (qui pourrait être arbitraire si example étaient une dict , mais pourrait aussi être utile si elle était un OrderedDict ).

définir tous les détails fiddly, et garder la performance acceptable, s'avère être plus difficile qu'il n'y paraît. Voir PEP 468 , et les fils reliés, pour une discussion sur l'idée. Il semble avoir en attente cette fois-ci, mais si quelqu'un le prend en charge et le soutient (et écrit une implémentation de référence pour que les gens puissent jouer avec-qui dépend d'un OrderedDict accessible à partir de L'API C, mais qui sera peut-être là en 3.5+), je soupçonne qu'il finirait par entrer dans le langage.


soit dit en passant, notez que si ce était possible, il serait presque certainement utilisé dans le constructeur pour OrderedDict lui-même. Mais si vous essayez cela, tout ce que vous faites est de geler un ordre arbitraire comme l'ordre permanent:

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

en attendant, quelles options avez-vous?

Eh bien, l'option évidente est juste de passer example comme un argument normal au lieu de le déballer:

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

ou, bien sûr, vous pouvez utiliser *args et passer les articles comme tuples, mais c'est généralement plus laid.

il y a peut-être d'autres solutions dans les fils liés au PEP, mais en fait, aucune d'entre elles ne sera meilleure que celle-ci. (Sauf... IIRC, Li Haoyi a trouvé une solution basée sur son MacroPy pour passer les arguments de mot-clé de maintien de l'ordre, mais je ne me souviens pas des détails. Les solutions MacroPy en général sont géniales si vous êtes prêt à utiliser MacroPy et écrire du code qui ne se lit pas tout à fait comme Python, mais qui n'est pas toujours approprié...)

24
répondu abarnert 2017-05-29 18:48:38

C'est maintenant la valeur par défaut dans 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

il n'est pas possible de le faire auparavant, comme l'indiquent les autres réponses.

14
répondu damio 2016-12-01 11:01:43

lorsque Python rencontre la construction **kwargs dans une signature, il s'attend à ce que kwargs soit une" cartographie", ce qui signifie deux choses: (1) pouvoir appeler kwargs.keys() pour obtenir un itérable des clés contenues dans la cartographie, et (2) que kwargs.__getitem__(key) peut être appelé pour chaque clé dans l'itérable retourné par keys() et que la valeur résultante est la valeur désirée pour être associée à cette clé.

en interne, Python transformera alors" quelle que soit la correspondance dans un dictionnaire, un peu comme ceci:

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

cela semble un peu ridicule si vous pensez que kwargs est déjà un dict -- et il le serait, puisqu'il n'y a aucune raison de construire un dict totalement équivalent de celui qui est passé.

mais quand kwargs n'est pas nécessairement un dict , alors il importe de ramener son contenu dans une structure de données par défaut appropriée de sorte que le code qui réalise l'argument déballage sait toujours ce qu'il travaille avec.

donc, vous can gâchez avec la façon dont un certain type de données est non emballée, mais en raison de la conversion en dict pour le bien d'un arg-unpacking-protocole cohérent, il se trouve que des garanties placer sur l'ordre de déballage argument n'est pas possible (depuis dict ne suit pas l'ordre dans lequel les éléments sont ajoutés). Si l' le langage de Python a apporté **kwargs dans un OrderedDict au lieu d'un dict (ce qui signifie que l'ordre des clés comme mot clé args serait l'ordre dans lequel ils sont traversés), puis en passant soit un OrderedDict ou une autre structure de données où keys() respecte une sorte d'ordre, vous pourrait s'attendre à un certain ordre sur les arguments. C'est juste une bizarrerie de la mise en œuvre que dict est choisi comme la norme, et pas certains autre type de cartographie.

voici un exemple stupide d'une classe qui peut "être déballée" mais qui traite toujours toutes les valeurs déballées comme 42 (même si elles ne le sont pas vraiment):

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

définit alors une fonction pour imprimer le contenu non emballé:

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

et faire une valeur et l'essayer:

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

cette Livraison personnalisée de paires de clés de valeur (ici, dumbly toujours retour 42) est le étendue de votre capacité à modifier le fonctionnement de **kwargs en Python.

il y a un peu plus de flexibilité pour bricoler avec la façon dont *args est non emballé. Pour plus de détails, voir la question suivante: < est-ce que l'argument unpacking utilise itération ou obtention d'un article? >.

2
répondu ely 2017-05-23 12:02:57