variable - varargs python




Utilizzando un OrderedDict in** kwargs (2)

A partire da Python 3.6, l'ordine degli argomenti delle parole chiave viene mantenuto . Prima di 3.6, non è possibile dal momento che OrderedDict viene trasformato in un dict .

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

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

O questo caso, dove avrà più valori di quelli in esempio:

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

O addirittura nessuna sovrapposizione:

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

Quindi, quello che chiedi non ha davvero senso.

Tuttavia, potrebbe essere bello se ci fosse un modo per specificare che vuoi un **kwargs ordinato, non importa da dove provengano le parole chiave - argomenti di parole chiave espliciti nell'ordine in cui appaiono, seguito da tutti gli elementi di **example in l'ordine in cui appaiono example (che potrebbe essere arbitrario se l' 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 una discussione sull'idea. Sembra essersi fermato questa volta, ma se qualcuno lo raccoglie e lo OrderedDict (e scrive un'implementazione di riferimento per le persone con cui giocare - che dipende da un OrderedDict accessibile dall'API C, ma che si spera ci sia in 3.5+ ), Sospetto che alla fine entrerà nella lingua.

A proposito, nota che se ciò fosse possibile, sarebbe quasi certamente usato nel costruttore per OrderedDict stesso. Ma se ci provi, tutto quello che stai facendo è congelare un ordine arbitrario come l'ordine permanente:

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

Nel frattempo, quali opzioni hai?

Bene, l'opzione ovvia è solo passare l' example come argomento normale invece di scompattarlo:

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

O, naturalmente, puoi usare *args e passare gli articoli come tuple, ma in genere è più brutto.

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 sulla sua MacroPy per passare argomenti di parole chiave che mantengono l'ordine, ma io non ricordo i dettagli. Le soluzioni di MacroPy in generale sono fantastiche se sei disposto ad usare MacroPy e scrivere codice che non è abbastanza simile a Python, ma non è sempre appropriato ...)

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

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

vale a dire, ordinamento tipico di dettatura casuale.

Ho altri usi dove impostare l'ordine in modo esplicito è buono, quindi voglio mantenere **kwargs e non solo passare OrderedDict come argomento regolare


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

Internamente, Python "trasformerà" qualunque sia la mappatura in un dizionario, una specie di questo:

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

Questo sembra un po 'sciocco se si pensa che kwargs è già un dict - e lo sarebbe, dal momento che non c'è motivo di costruire un dict totalmente equivalente da quello che è passato.

Ma quando kwargs non è necessariamente un dict , allora è importante portare il suo contenuto in una struttura di dati di default adatta in modo che il codice che esegue l'argomento disimballare sappia sempre con cosa sta funzionando.

Quindi, è possibile fare confusione con il modo in cui un determinato tipo di dati viene decompresso, ma a causa della conversione a dict per un coerente protocollo di arg-unpacking, succede semplicemente che non è possibile posizionare garanzie sull'ordine dell'operazione di disimballaggio ( poiché dict non tiene traccia dell'ordine in cui vengono aggiunti gli elementi). Se il linguaggio di Python ha portato **kwargs in un OrderedDict anziché in un dict (il che significa che l'ordine delle chiavi come parola chiave sarebbe l'ordine in cui sono attraversati), quindi passando un OrderedDict o un'altra struttura di dati dove keys() rispetta un qualche tipo di ordinamento, potresti aspettarti un certo ordine sugli argomenti. È solo un capriccio dell'implementazione che dict viene scelto come standard e non qualche altro tipo di mappatura.

Ecco un esempio stupido di una classe che può essere "spacchettata" ma che considera sempre tutti i valori non compressi come 42 (anche se non sono realmente):

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 i contenuti decompressi:

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

e crea un valore e provalo:

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 consegna personalizzata di coppie chiave-valore (qui, sempre stupidamente restituendo 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 il modo in cui *args viene spacchettato. Per ulteriori informazioni, vedere questa domanda: < L'argomento decompressione utilizza l'iterazione o l'acquisizione di elementi? >.





kwargs