2014-11-05 15 views
21

È possibile passare un'istanza OrderedDict a una funzione che utilizza la sintassi **kwargs e conserva l'ordine?Utilizzo di OrderedDict in ** kwargs

Quello che mi piacerebbe fare è:

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 

Tuttavia il risultato effettivo è:

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

cioè, tipico ordinamento dict casuale.

Ho altri usi in cui impostare l'ordine esplicito è buono, quindi voglio continuare **kwargs e non solo passare l'OrderedDict come un argomento normale

+0

Grazie per le risposte dettagliate. Quello che ho finito per fare è stato permettere un primo argomento opzionale (un ditt ordinato) che sarebbe stato usato preferibilmente con i ** kwargs se forniti. Ottime informazioni, anche se – theodox

+0

** Vedi anche: ** https://duckduckgo.com/?q=pep468 – dreftymac

risposta

22

Come di Python 3.6, the keyword argument order is preserved. Prima di 3.6, non è possibile poiché lo OrderedDict diventa trasformato in dict.


La prima cosa da capire è che il valore si passa **example non diventa automaticamente il valore in **kwargs. Considerate questo caso, in cui kwargs avrà solo una parte del example:

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

O questo caso, in cui avrà più valori di quelli nell'esempio:

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

O ancora alcuna sovrapposizione a tutti:

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

Quindi, quello che stai chiedendo perché non ha molto senso.

Eppure, potrebbe essere bello se ci fosse un modo per specificare che si desidera una ordinata **kwargs, non importa dove le parole chiave provenivano da-espliciti args parole chiave nell'ordine in cui appaiono, seguito da tutti gli elementi di **example nell'ordine in cui appaiono in example (che potrebbe essere arbitrario se example fosse un dict, ma potrebbe anche essere significativo se fosse un OrderedDict).

Definire tutti i dettagli complessi e mantenere le prestazioni accettabili risulta essere più difficile di quanto sembri. Vedi PEP 468 e i thread collegati, per qualche discussione sull'idea. Sembra essersi fermato questa volta, ma se qualcuno lo raccoglie e lo sostiene (e scrive un'implementazione di riferimento per le persone con cui giocare, il che dipende da un OrderedDict accessibile dall'API C, ma che spero ci sia tra 3.5 e più), Sospetto che alla fine entrerà nella lingua.


A proposito, si noti che se questo fosse possibile, sarebbe quasi certamente essere utilizzato nel costruttore per OrderedDict sé. Ma se si prova che, tutto quello che stai facendo è il congelamento qualche ordine arbitrario come ordine permanente:

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

Nel frattempo, quali opzioni avete?

Ebbene, l'opzione più ovvia è solo per passare example come un normale argomento, invece di disimballaggio:

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

O, naturalmente, è possibile utilizzare *args e trasmettere gli articoli come tuple, ma questo è generalmente più brutta .

Potrebbero esserci altre soluzioni alternative nei thread collegati dal PEP, ma in realtà nessuno di questi sarà migliore di questo. (Ad eccezione di ... IIRC, Li Haoyi ha escogitato una soluzione basata sul suo MacroPy per passare gli argomenti delle parole chiave di conservazione degli ordini, ma non ricordo i dettagli. Le soluzioni MacroPy in generale sono fantastiche se sei disposto ad usare MacroPy e scrivere codice che non è abbastanza leggibile come Python, ma non è sempre appropriato ...)

+0

Potresti aggiornare questa risposta per Python 3.6? – Moberg

2

Quando Python rileva il costrutto **kwargs in una firma, si aspetta che kwargs sia una "mappatura", che significa due cose: (1) essere in grado di chiamare kwargs.keys() per ottenere un iterable delle chiavi contenute dalla mappatura, e (2) che kwargs.__getitem__(key) possa essere chiamato per ogni chiave nel iterable restituito da keys() e che il valore risultante sia quello desiderato da associare per quello chiave.

Internamente, Python sarà poi "trasformare" qualunque sia la mappatura è in un dizionario, un po 'come questo:

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

Questo sembra un po' sciocco se si pensa che è già un kwargsdict - e sarebbe, poiché non v'è alcun motivo per costruire un tutto equivalente dict da quello che viene passato.

Ma quando kwargs non è necessariamente una dict, quindi è importante per portare il contenuto giù in una opportuna struttura di dati predefinita in modo che il codice che svolge l'argomento disimballare sa sempre con cosa funziona.

Quindi, è possibile pasticciare con il modo in cui un certo tipo di dati viene imballate, ma a causa della conversione al dict per il bene di una coerente arg-disimballaggio-protocollo, succede solo che l'immissione garanzie dell'ordine di non è possibile rimuovere la discussione (poiché dict non tiene traccia dell'ordine in cui vengono aggiunti gli elementi). Se la lingua di Python ha portato **kwargs in uno OrderedDict anziché dict (ovvero l'ordine delle chiavi come parola chiave sarebbe l'ordine in cui sono attraversati), quindi passando uno rispetti o una qualche altra struttura di dati. una sorta di ordinamento, è possibile aspettarsi un certo ordine sugli argomenti. È solo un capriccio dell'implementazione che lo standard dict viene scelto e non un altro tipo di mappatura.

Ecco un esempio muto di una classe che può "essere spacchettato", ma che tratta sempre tutti i valori non imballati come 42 (anche se in realtà non sono):

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

quindi definire una funzione per stampare il contenuti non imballati:

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

e rendere un valore e provarlo:

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 

Questa c la consegna ustomizzata di coppie chiave-valore (qui, sempre stupidamente, che restituisce sempre 42) è l'estensione della tua capacità di armeggiare con il modo in cui **kwargs funziona in Python.

C'è un po 'più di flessibilità per armeggiare con come *args si spacchetta. Per ulteriori informazioni, vedere questa domanda: < 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 

Non è possibile farlo prima, come indicato dalle altre risposte.