Modo corretto di usare ** kwargs in Python


454

Qual è il modo corretto di usare **kwargsin Python quando si tratta di valori predefiniti?

kwargsrestituisce un dizionario, ma qual è il modo migliore per impostare i valori predefiniti o ce n'è uno? Devo solo accedervi come dizionario? Utilizzare la funzione get?

class ExampleClass:
    def __init__(self, **kwargs):
        self.val = kwargs['val']
        self.val2 = kwargs.get('val2')

Una domanda semplice, ma su cui non riesco a trovare buone risorse. Le persone lo fanno in modi diversi nel codice che ho visto ed è difficile sapere cosa usare.

Risposte:


468

È possibile passare un valore predefinito a get()per le chiavi che non sono nel dizionario:

self.val2 = kwargs.get('val2',"default value")

Tuttavia, se prevedi di utilizzare un argomento specifico con un determinato valore predefinito, perché non utilizzare gli argomenti denominati in primo luogo?

def __init__(self, val2="default value", **kwargs):

16
Mi piace usare gli argomenti posizionali solo per gli argomenti richiesti e kwargs per gli argomenti che possono o meno essere specificati, ma è utile avere un valore predefinito. kwargs è carino perché puoi inviare i tuoi argomenti in qualsiasi ordine tu scelga. Gli argomenti posizionali non ti danno quella libertà.
Kekoa,

95
Puoi passare argomenti nominati nell'ordine che preferisci. Devi solo aderire alle posizioni se non usi i nomi, cosa che nel caso di kwargs devi. Piuttosto, utilizzando denominati argomenti al contrario di kwargs ti dà l' ulteriore libertà di non utilizzare i nomi - allora, tuttavia, è necessario mantenere l'ordine.
balpha,

13
@Kekoa: puoi sempre inviare argomenti con nome in qualsiasi ordine tu scelga. Non è necessario utilizzare ** kwargs per ottenere questa flessibilità.
S.Lott

13
pylint lo contrassegna come una cattiva forma in cui usare kwargs __init__(). Qualcuno può spiegare perché questa è una trasgressione degna di lanugine?
hughdbrown,


271

Mentre la maggior parte delle risposte afferma che, ad esempio,

def f(**kwargs):
    foo = kwargs.pop('foo')
    bar = kwargs.pop('bar')
    ...etc...

equivale a"

def f(foo=None, bar=None, **kwargs):
    ...etc...

questo non è vero. In quest'ultimo caso, fpuò essere chiamato come f(23, 42), mentre il primo caso accetta solo argomenti con nome - nessuna chiamata posizionale. Spesso si desidera consentire al chiamante la massima flessibilità e quindi è preferibile il secondo modulo, come affermano la maggior parte delle risposte: ma non è sempre così. Quando si accettano molti parametri opzionali di cui in genere vengono passati solo alcuni, può essere un'ottima idea (evitare incidenti e codice illeggibile nei siti di chiamata!) Per forzare l'uso di argomenti denominati - ne threading.Threadè un esempio. Il primo modulo è il modo in cui lo implementi in Python 2.

Il linguaggio è così importante che in Python 3 ora ha una sintassi di supporto speciale: ogni argomento dopo un singolo *nella deffirma è solo parola chiave, cioè non può essere passato come argomento posizionale, ma solo come un nome. Quindi in Python 3 puoi scrivere quanto sopra come:

def f(*, foo=None, bar=None, **kwargs):
    ...etc...

In effetti, in Python 3 puoi persino avere argomenti solo per parole chiave che non sono opzionali (quelli senza un valore predefinito).

Tuttavia, Python 2 ha ancora molti anni di vita produttiva, quindi è meglio non dimenticare le tecniche e gli idiomi che ti consentono di implementare in Python 2 importanti idee di design che sono direttamente supportate nel linguaggio in Python 3!


10
@Alex Martelli: non ho trovato una sola risposta che afferma che i kwargs sono identici agli argomenti nominati, figuriamoci superiori. Ma un buon discorso ai Py3k cambia, quindi +1
balpha

1
@Alex Martelli: grazie mille per la tua risposta, mi ha appreso che Python 3 consente argomenti di parole chiave obbligatori, che la mancanza di ciò ha spesso portato a un'architettura insoddisfacente nel mio codice e nelle mie funzioni.
FloW

78

Suggerisco qualcosa del genere

def testFunc( **kwargs ):
    options = {
            'option1' : 'default_value1',
            'option2' : 'default_value2',
            'option3' : 'default_value3', }

    options.update(kwargs)
    print options

testFunc( option1='new_value1', option3='new_value3' )
# {'option2': 'default_value2', 'option3': 'new_value3', 'option1': 'new_value1'}

testFunc( option2='new_value2' )
# {'option1': 'default_value1', 'option3': 'default_value3', 'option2': 'new_value2'}

E poi usa i valori come preferisci

dictionaryA.update(dictionaryB)aggiunge il contenuto dictionaryBalla dictionaryAsovrascrittura di eventuali chiavi duplicate.


2
Grazie @AbhinavGupta! Esattamente quello che stavo cercando. Ho appena aggiunto for key in options: self.__setattr__(key, options[key])e sono a posto. :)
propjk007

53

Faresti

self.attribute = kwargs.pop('name', default_value)

o

self.attribute = kwargs.get('name', default_value)

Se lo usi pop, puoi verificare se ci sono valori spuri inviati e intraprendere l'azione appropriata (se presente).


2
Puoi chiarire cosa intendi suggerendo che .poppotrebbe aiutarti a "verificare se ci sono valori spuri inviati"?
Alan H.

13
@Alan H .: se c'è qualcosa rimasto in kwargs dopo aver fatto tutto lo scoppiettio, allora hai valori spuri.
Vinay Sajip,

@VinaySajip: Ok, questo è un ottimo punto su .pop "vs" .get, ma ancora non vedo perché il pop sia preferibile agli argomenti con nome, oltre a forzare il chiamante a non usare i parametri posizionali.
MestreLion,

1
@MestreLion: dipende dal numero di argomenti di parole chiave consentiti dall'API. Non sostengo che il mio suggerimento sia migliore degli argomenti con nome, ma Python consente di acquisire argomenti senza nome kwargsper un motivo.
Vinay Sajip,

Quindi, sto solo controllando. Pop restituisce un valore di dizionario se la chiave esiste e in caso contrario restituisce il valore default_valuepassato? E rimuove quella chiave in seguito?
Daniel Möller,

42

L'uso di ** kwargs e dei valori predefiniti è semplice. A volte, tuttavia, non dovresti usare ** kwargs in primo luogo.

In questo caso, non stiamo davvero sfruttando al meglio ** kwargs.

class ExampleClass( object ):
    def __init__(self, **kwargs):
        self.val = kwargs.get('val',"default1")
        self.val2 = kwargs.get('val2',"default2")

Quanto sopra è un "perché preoccuparsi?" dichiarazione. È lo stesso di

class ExampleClass( object ):
    def __init__(self, val="default1", val2="default2"):
        self.val = val
        self.val2 = val2

Quando usi ** kwargs, intendi che una parola chiave non è solo facoltativa, ma condizionale. Esistono regole più complesse dei semplici valori predefiniti.

Quando usi ** kwargs, di solito intendi qualcosa di più simile al seguente, in cui non si applicano le impostazioni predefinite semplici.

class ExampleClass( object ):
    def __init__(self, **kwargs):
        self.val = "default1"
        self.val2 = "default2"
        if "val" in kwargs:
            self.val = kwargs["val"]
            self.val2 = 2*self.val
        elif "val2" in kwargs:
            self.val2 = kwargs["val2"]
            self.val = self.val2 / 2
        else:
            raise TypeError( "must provide val= or val2= parameter values" )

Mi piace quel piccolo rompicapo! Continuavo a pensare "Ma potresti usare get o pop con - oh, sono co-dipendenti ..."
trojjer

28

Poiché **kwargsviene utilizzato quando il numero di argomenti è sconosciuto, perché non farlo?

class Exampleclass(object):
  def __init__(self, **kwargs):
    for k in kwargs.keys():
       if k in [acceptable_keys_list]:
          self.__setattr__(k, kwargs[k])

sì, questo è elegante e potente ... non troppo sicuro delle parentesi quadre attorno a accettabile_keys_list però: renderei questa una tupla o una lista e poi lascerei cadere quelle parentesi nell'istruzione "if"
mike rodent

L'ho leggermente modificato per i casi in cui sono previsti tutti i tasti: stackoverflow.com/questions/1098549/…
ribellione

14

Ecco un altro approccio:

def my_func(arg1, arg2, arg3):
    ... so something ...

kwargs = {'arg1': 'Value One', 'arg2': 'Value Two', 'arg3': 'Value Three'}
# Now you can call the function with kwargs like this:

my_func(**kwargs)

Utilizzato molto nei CBV Django (ad es get_form_kwargs().). ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/…
trojjer

Il get_form()metodo mostra come ottenere ampiamente gli argomenti delle parole chiave rimandando a un altro metodo ( get_form_kwargscome menzionato sopra). Si crea un'istanza del modulo come segue: form_class(**self.get_form_kwargs()).
trojjer,

È quindi facile sovrascrivere get_form_kwargs()in una vista di una sottoclasse e aggiungere / rimuovere kwargs in base a una logica specifica. Ma questo è per un tutorial di Django.
trojjer,

12

Penso che il modo corretto di utilizzare **kwargsin Python quando si tratta di valori predefiniti sia quello di utilizzare il metodo del dizionario setdefault, come indicato di seguito:

class ExampleClass:
    def __init__(self, **kwargs):
        kwargs.setdefault('val', value1)
        kwargs.setdefault('val2', value2)

In questo modo, se un utente passa "val" o "val2" nella parola chiave args, verranno utilizzati; in caso contrario, verranno utilizzati i valori predefiniti impostati.


11

Potresti fare qualcosa del genere

class ExampleClass:
    def __init__(self, **kwargs):
        arguments = {'val':1, 'val2':2}
        arguments.update(kwargs)
        self.val = arguments['val']
        self.val2 = arguments['val2']

11

Facendo seguito a @srhegde suggerimento di utilizzare setattr :

class ExampleClass(object):
    __acceptable_keys_list = ['foo', 'bar']

    def __init__(self, **kwargs):
        [self.__setattr__(key, kwargs.get(key)) for key in self.__acceptable_keys_list]

Questa variante è utile quando si prevede che la classe abbia tutti gli elementi nel nostro acceptableelenco.


1
Questo non è un caso d'uso per la comprensione di un elenco, dovresti usare un ciclo for nel tuo metodo init.
ettanany,

5

Se vuoi combinare questo con * args devi mantenere * args e ** kwargs alla fine della definizione.

Così:

def method(foo, bar=None, *args, **kwargs):
    do_something_with(foo, bar)
    some_other_function(*args, **kwargs)

1

@AbhinavGupta e @Steef hanno suggerito di utilizzare update(), che ho trovato molto utile per l'elaborazione di grandi elenchi di argomenti:

args.update(kwargs)

Cosa succede se vogliamo verificare che l'utente non abbia superato argomenti spuri / non supportati? @VinaySajip ha sottolineato che pop()può essere utilizzato per elaborare iterativamente l'elenco degli argomenti. Quindi, tutti gli argomenti rimanenti sono spuri. Bello.

Ecco un altro modo possibile per farlo, che mantiene la semplice sintassi dell'uso update():

# kwargs = dictionary of user-supplied arguments
# args = dictionary containing default arguments

# Check that user hasn't given spurious arguments
unknown_args = user_args.keys() - default_args.keys()
if unknown_args:
    raise TypeError('Unknown arguments: {}'.format(unknown_args))

# Update args to contain user-supplied arguments
args.update(kwargs)

unknown_argsè un setcontenente i nomi degli argomenti che non si verificano nelle impostazioni predefinite.


0

Un'altra soluzione semplice per l'elaborazione di argomenti sconosciuti o multipli può essere:

class ExampleClass(object):

    def __init__(self, x, y, **kwargs):
      self.x = x
      self.y = y
      self.attributes = kwargs

    def SomeFunction(self):
      if 'something' in self.attributes:
        dosomething()

0

** kwargs offre la libertà di aggiungere qualsiasi numero di argomenti per parole chiave. Uno può avere un elenco di chiavi per le quali può impostare valori predefiniti. Ma l'impostazione di valori predefiniti per un numero indefinito di chiavi sembra superflua. Infine, potrebbe essere importante avere le chiavi come attributi di istanza. Quindi, farei questo come segue:

class Person(object):
listed_keys = ['name', 'age']

def __init__(self, **kwargs):
    _dict = {}
    # Set default values for listed keys
    for item in self.listed_keys: 
        _dict[item] = 'default'
    # Update the dictionary with all kwargs
    _dict.update(kwargs)

    # Have the keys of kwargs as instance attributes
    self.__dict__.update(_dict)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.