2014-11-05 26 views
21

Czy można przekazać instancję OrderedDict do funkcji używającej składni **kwargs i zachować kolejność?Korzystanie z OrderedDict w ** kwargs

Co chciałbym zrobić, to:

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 

Jednak rzeczywisty wynik jest:

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

czyli typowy losowy DICT zamawiania.

mam innych zastosowań gdzie ustalania kolejności wyraźnie jest dobra, więc chcę zachować **kwargs a nie tylko zdać OrderedDict jako zwykły argumentu

+0

Dzięki za szczegółowe odpowiedzi. W końcu udało mi się zezwolić na opcjonalny pierwszy argument (uporządkowany dyktat), który byłby użyty przed dodaniem ** kwargów, jeśli taki był. Świetne informacje, chociaż – theodox

+0

** Zobacz też: ** https://duckduckgo.com/?q=pep468 – dreftymac

Odpowiedz

22

As Pythona 3.6, the keyword argument order is preserved. Przed 3.6 nie jest to możliwe, ponieważ OrderedDict zamienia się w dict.


Pierwszą rzeczą, aby zdać sobie sprawę, że wartość przechodzą w **example nie stają się automatycznie wartością w **kwargs. Rozważmy następujący przypadek, gdzie kwargs będzie miał tylko część example:

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

Albo to przypadek, gdzie będzie mieć więcej wartości niż w przykładzie:

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

lub nawet bez nakładania na wszystkich:

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

Więc co pytasz o tak naprawdę nie ma sensu.

Mimo to może być miło, gdyby był jakiś sposób, aby określić, że ma uporządkowaną **kwargs, nieważne gdzie słowa kluczowe pochodziła z-wyraźnych args słowo kluczowe w kolejności ich występowania, a następnie wszystkie pozycje **example w kolejności, w jakiej pojawiają się w example (które mogą być dowolne, jeśli example były dict, ale mogą być również znaczące, jeśli były to OrderedDict).

Definiowanie wszystkich skomplikowanych szczegółów i utrzymywanie wydajności do zaakceptowania okazuje się trudniejsze niż się wydaje. Zobacz PEP 468 i powiązane wątki, aby porozmawiać na ten temat. Wygląda na to, że tym razem zawiesiło się, ale jeśli ktoś je podniesie i poprowadzi mistrzostwo (i zapisze referencyjną implementację dla ludzi, z którymi można grać), co zależy od OrderedDict dostępnej z C API, ale mam nadzieję, że będzie tam w wersji 3.5+), Podejrzewam, że w końcu dotrze do tego języka.


Nawiasem mówiąc, należy pamiętać, że jeśli ten były możliwe, to prawie na pewno być używany w konstruktorze dla samej OrderedDict. Ale jeśli spróbujesz, że wszystko robisz jest zamrożenie trochę dowolnej kolejności jako stałej kolejności:

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

Tymczasem, jakie opcje masz?

Cóż, oczywistym rozwiązaniem jest po prostu przejść example jako normalny argumentu zamiast rozpakowaniu go:

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

lub, oczywiście, można użyć *args i przekazać przedmioty jak krotki, ale to ogólnie brzydsze .

Możliwe są inne sposoby obejścia wątków powiązanych z PEP, ale tak naprawdę żadna z nich nie będzie lepsza. (Z wyjątkiem ... IIRC, Li Haoyi wymyślił rozwiązanie oparte na jego MacroPy w celu przekazania argumentów słów kluczowych zachowujących porządek, ale nie pamiętam szczegółów.Opcje MacroPy są generalnie świetne, jeśli chcesz używać MacroPy i pisać kod to nie całkiem czyta się jak Python, ale to nie zawsze jest odpowiednie ...)

+0

Czy możesz zaktualizować tę odpowiedź dla Pythona 3.6? – Moberg

2

Gdy Python napotka konstruktor **kwargs w podpisie, oczekuje, że kwargs będzie "mapowaniem", co oznacza dwie rzeczy: (1) aby móc zadzwonić pod numer kwargs.keys(), aby uzyskać możliwość sprawdzenia kluczy zawartych w odwzorowaniu, oraz (2), aby można było wywołać kwargs.__getitem__(key) dla każdego klucza w iteracji zwróconej przez keys() i że wynikowa wartość jest żądana do powiązania z tym kluczem. klawisz.

Wewnętrznie Python będzie potem „przekształcić” cokolwiek odwzorowanie jest w słowniku, jakby tak:

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

Wygląda to trochę głupie, jeśli uważasz, że kwargs jest już dict - i to będzie, ponieważ nie ma żadnego powodu, aby skonstruować zupełnie równoważne dict od tego, który jest przekazywany w.

Ale kiedy kwargs niekoniecznie jest dict, to ważne, aby przynieść jej zawartość w dół do odpowiedniej struktury domyślny danych tak że kod który przeprowadza rozpakowywanie argumentów zawsze wie, z czym pracuje.

Więc może bałagan z okazji pewien typ danych pobiera opakowania, ale ze względu na konwersję do dict dla dobra spójnego Arg-rozpakowywania z protokołem, po prostu zdarza się, że wprowadzenie gwarancji na zlecenie rozpakowywanie argumentów nie jest możliwe (ponieważ dict nie śledzi kolejności dodawania elementów). Jeśli język Python przyniósł **kwargs w dół do OrderedDict zamiast dict (co oznacza, że ​​kolejność klawiszy jako słowa kluczowego args będzie taka, w jakiej są one wykonywane), a następnie przekazanie albo OrderedDict lub innej struktury danych, gdzie keys() szanuje jakiś rodzaj zamawiania, może może oczekiwać pewnego porządku na argumentach. To tylko dziwactwo implementacji, która została wybrana jako standard, a nie jakikolwiek inny typ mapowania.

Oto głupi przykład z klasy, która może „być rozpakowane”, ale który zawsze traktuje wartości wszystkich rozpakowane jako 42 (choć nie są one naprawdę):

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

Następnie zdefiniować funkcję, aby wydrukować rozpakowanych treść:

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

i tworzą wartość i wypróbować:

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 

to c ustomizowane dostarczanie par klucz-wartość (tutaj, zawsze grzecznie wracający 42) jest zakresem zdolności do majstrowania przy tym, jak działa **kwargs w Pythonie.

Istnieje nieco większa elastyczność w majstrowaniu przy tym, jak *args zostaje rozpakowany. Aby dowiedzieć się więcej, zobacz to pytanie: < 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 

Nie można tego zrobić wcześniej, jak zauważono w innych odpowiedziach.