2014-11-05 16 views
21

Ist es möglich, eine OrderedDict-Instanz an eine Funktion zu übergeben, die die **kwargs-Syntax verwendet und die Reihenfolge beibehält?Verwenden eines OrderedDict in ** kwargs

Was Ich mag würde zu tun ist:

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 

jedoch das tatsächliche Ergebnis ist:

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

dh typische zufällige dict Ordnung.

Ich habe andere Anwendungen, bei denen die Reihenfolge explizit Einstellung gut ist, so mag ich **kwargs halten und nicht nur die OrderedDict als reguläres Argument

+0

Danke für die detaillierten Antworten. Was ich getan habe, war, ein optionales erstes Argument (ein geordnetes Diktat) zuzulassen, das gegenüber den ** kwargs verwendet würde, falls es bereitgestellt wird. Große Info, obwohl – theodox

+0

** Siehe auch: ** https://duckduckgo.com/?q=pep468 – dreftymac

Antwort

22

Ab Python 3.6, the keyword argument order is preserved passieren. Vor 3.6 ist es nicht möglich, da die OrderedDict in eine dict umgewandelt wird.


Das erste, was zu erkennen ist, dass der Wert, den Sie in **example gehen nicht automatisch der Wert in **kwargs werden. Betrachten Sie diesen Fall, wo kwargs nur haben Teil example:

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

Oder diesen Fall, wo es mehr Werte als die in Beispiel hat:

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

Oder auch ohne Überlappung überhaupt:

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

Also, was Sie fragen nicht wirklich Sinn machen.

Dennoch könnte es schön, wenn es eine Möglichkeit gab, um anzugeben, dass Sie eine geordnete **kwargs wollen, egal wo die Schlüsselwörter aus explizitem Schlüsselwort args in der Reihenfolge, wie sie kamen erscheinen, gefolgt von allen Elementen von **example in der Reihenfolge, in der sie in example erscheinen (was beliebig sein könnte, wenn example ein dict wäre, aber könnte auch sinnvoll sein, wenn es ein OrderedDict wäre).

Es ist schwieriger, alle Details zu definieren und die Leistung akzeptabel zu halten. Eine Diskussion über die Idee finden Sie unter PEP 468 und den verknüpften Threads. Es scheint dieses Mal zum Stillstand gekommen zu sein, aber wenn jemand es aufgreift und es verfasst (und eine Referenzimplementierung für Leute zum Spielen schreibt), die auf einer OrderedDict basiert, die von der C API erreichbar ist, aber das wird hoffentlich in 3.5+ da sein), Ich vermute, dass es irgendwann in die Sprache kommen würde.


By the way, beachten Sie, dass, wenn diese waren möglich, es mit ziemlicher Sicherheit im Konstruktor verwendet werden würde, für OrderedDict selbst. Aber wenn Sie versuchen, dass alles, was Sie tun ist das Einfrieren etwas beliebige Reihenfolge als Dauerauftrag:

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

Inzwischen welche Möglichkeiten haben Sie?

Nun, die offensichtliche Option ist nur example als normale Argument übergeben, anstatt es zu dem Auspacken:

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

Oder natürlich können Sie *args verwenden und die Einzelteile als Tupel übergeben, aber das ist im Allgemeinen hässlichen .

Es kann einige andere Problemumgehungen in den Threads geben, die von der PEP verknüpft sind, aber wirklich, keiner von ihnen wird besser als das sein. (Außer ... IIRC, Li Haoyi hat eine Lösung auf der Grundlage seiner MacroPy gefunden, um bestellungsbeständige Schlüsselwortargumente zu übergeben, aber ich erinnere mich nicht an die Details. MacroPy-Lösungen im Allgemeinen sind großartig, wenn Sie MacroPy verwenden und Code schreiben möchten das liest sich nicht wie Python, aber das ist nicht immer passend ...)

+0

Könnten Sie diese Antwort für Python 3.6 aktualisieren? – Moberg

2

Wenn Python das **kwargs Konstrukt in einer Signatur findet, erwartet es kwargs ein "Mapping", was zwei Dinge bedeutet: (1) in der Lage sein, kwargs.keys() aufzurufen, um ein iterbares der durch das Mapping enthaltenen Schlüssel zu erhalten, und (2) dass für jeden Schlüssel in dem iterablen, der von keys() zurückgegeben wird, aufgerufen werden kann und dass der resultierende Wert der gewünschte Wert ist Schlüssel.

Intern wird Python dann „transformieren“, was auch immer die Abbildung in ein Wörterbuch, ein bisschen wie folgt aus:

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

Dies ist ein wenig albern aussieht, wenn Sie denken, dass kwargs ist bereits ein dict - und es wäre, da gibt es keinen Grund ein völlig gleichwertigen dict von dem einen zu konstruieren, die in übergeben wird.

Aber wenn kwargs nicht unbedingt ein dict ist, dann zählt es seinen Inhalt nach unten in eine geeignete Standard-Datenstruktur zu bringen, so dass der Code was das Argument entpacken immer weiß, woran es arbeitet.

Also, Sie können Chaos mit dem Weg zu einem bestimmten Datentyp Unpackaged wird, sondern auch wegen der Umstellung auf dict im Interesse eines einheitlichen Arg- auspacken-Protokolls, es geschieht nur, dass platzieren Garantien in der Größenordnung von Das Entpacken des Arguments ist nicht möglich (da dict nicht die Reihenfolge verfolgt, in der Elemente hinzugefügt werden). Wenn die Sprache von Python **kwargs in eine OrderedDict statt einer dict (was bedeutet, dass die Schlüssel 'Reihenfolge als Schlüsselwort Args wäre die Reihenfolge, in der sie durchlaufen werden) gebracht, dann entweder durch eine OrderedDict oder eine andere Datenstruktur, wo respektiert eine Art von Bestellung, Sie könnte erwarten eine bestimmte Reihenfolge der Argumente. Es ist nur eine Eigenart der Implementierung, dass dict als Standard und nicht eine andere Art der Zuordnung gewählt wird.

Hier ist ein stummes Beispiel einer Klasse, die „ausgepackt werden“ können, aber die immer all entpackten Werte als 42 behandelt (auch wenn sie nicht wirklich sind):

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

Dann eine Funktion definieren, die drucken entpackten Inhalt:

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

und einen Wert machen und probieren sie es aus:

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 

Diese c ustomized Lieferung von Schlüssel-Wert-Paare (hier, dumm immer 42 zurück) ist das Ausmaß Ihrer Fähigkeit zu basteln mit, wie **kwargs in Python funktioniert.

Es gibt etwas mehr Flexibilität, um zu basteln, wie *args unverpackt wird. Für weitere Informationen siehe diese Frage: < 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 

Es ist nicht möglich, es vorher zu tun, wie von den anderen Antworten bemerkt.