Właściwy sposób używania ** kwargs w Pythonie


452

Jaki jest właściwy sposób używania **kwargsw Pythonie, jeśli chodzi o wartości domyślne?

kwargszwraca słownik, ale jaki jest najlepszy sposób na ustawienie wartości domyślnych, czy istnieje? Czy powinienem uzyskać do niego dostęp jako słownik? Używać funkcji get?

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

Proste pytanie, ale na które nie mogę znaleźć dobrych zasobów. Ludzie robią to w różny sposób w kodzie, który widziałem i trudno jest wiedzieć, czego użyć.

Odpowiedzi:


468

Możesz przekazać wartość domyślną get()dla kluczy, których nie ma w słowniku:

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

Jeśli jednak planujesz użyć konkretnego argumentu o określonej wartości domyślnej, dlaczego nie użyć w pierwszej kolejności nazwanych argumentów?

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

16
Lubię używać argumentów pozycyjnych tylko dla wymaganych argumentów, a kwargs dla argumentów, które mogą, ale nie muszą być określone, ale warto mieć wartość domyślną. Kwargs jest fajny, ponieważ możesz przesyłać argumenty w dowolnej kolejności. Argumenty pozycyjne nie dają ci tej wolności.
Kekoa

95
Możesz przekazać nazwane argumenty w dowolnej kolejności. Musisz stosować się do pozycji tylko wtedy, gdy nie używasz nazw - co w przypadku kwargs musisz. Raczej używanie nazwanych argumentów w przeciwieństwie do kwargs daje ci dodatkową swobodę nieużywania nazw - wtedy jednak musisz zachować porządek.
balpha

13
@Kekoa: Zawsze możesz przesłać nazwane argumenty w dowolnej kolejności. Nie musisz używać ** kwargs, aby uzyskać tę elastyczność.
S.Lott,

13
pylint oznacza to jako złą formę do używania kwargs w __init__(). Czy ktoś może wyjaśnić, dlaczego jest to niepozostawiająca kłótni przestępstwo?
hughdbrown


271

Podczas gdy większość odpowiedzi mówi, że np.

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

jest taki sam jak"

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

to nie jest prawda. W drugim przypadku fmożna wywołać jako f(23, 42), podczas gdy pierwszy przypadek przyjmuje tylko nazwane argumenty - bez wywołań pozycyjnych. Często chcesz pozwolić na maksymalną elastyczność abonenta wywołującego, dlatego preferowana jest druga forma, jak twierdzi większość odpowiedzi: ale nie zawsze tak jest. Gdy akceptujesz wiele opcjonalnych parametrów, z których zwykle tylko kilka jest przekazywanych, może być doskonałym pomysłem (unikanie wypadków i nieczytelnego kodu w witrynach połączeń!) Wymuszenie użycia nazwanych argumentów - threading.Threadto przykład. Pierwszą formą jest sposób implementacji tego w Python 2.

Idiom jest tak ważne, że w Pythonie 3 ma teraz specjalną składnię pomocnicze: każdy argument, po jednokrotnym *w defpodpisie jest tylko słowa kluczowe, czyli nie mogą być przekazywane jako argumentu pozycyjnym, ale tylko w nazwie jednego. Tak więc w Python 3 możesz zakodować powyższe jako:

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

Rzeczywiście, w Pythonie 3 możesz nawet mieć argumenty zawierające tylko słowa kluczowe, które nie są opcjonalne (te bez wartości domyślnej).

Jednak Python 2 ma przed sobą długie lata produktywnego życia, więc lepiej nie zapominać o technikach i idiomach, które pozwalają wdrożyć w Pythonie 2 ważne pomysły projektowe, które są bezpośrednio obsługiwane w języku Python 3!


10
@Alex Martelli: Nie znalazłem ani jednej odpowiedzi, która twierdzi, że kwargs jest identyczny z nazwanymi argumentami, nie mówiąc już o przełożeniu. Ale dobry dyskurs o zmianach Py3k, więc +1
balpha

1
@Alex Martelli: wielkie dzięki za odpowiedź, dowiedziałem się, że Python 3 dopuszcza obowiązkowe argumenty słów kluczowych, których brak często skutkował niezadowalającą architekturą mojego kodu i funkcji.
FloW,

78

Sugeruję coś takiego

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'}

A następnie użyj wartości w dowolny sposób

dictionaryA.update(dictionaryB)Dodaje zawartość dictionaryBdo dictionaryAnadpisywania żadnych duplikatów kluczy.


2
Dzięki @AbhinavGupta! Dokładnie tego szukałem. Właśnie dodałem for key in options: self.__setattr__(key, options[key])i jestem gotowy. :)
propjk007

53

Zrobiłbyś

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

lub

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

Jeśli używasz pop, możesz sprawdzić, czy wysyłane są fałszywe wartości, i podjąć odpowiednie działania (jeśli istnieją).


2
Czy możesz wyjaśnić, co masz na myśli, sugerując, .popże pomogłoby Ci „sprawdzić, czy przesłano jakieś fałszywe wartości”?
Alan H.

13
@Alan H .: jeśli w kwargach pozostało coś po zakończeniu poppingu, masz fałszywe wartości.
Vinay Sajip

@VinaySajip: Ok, to świetny punkt na .pop „vs” .get, ale wciąż nie rozumiem, dlaczego pop jest lepszy od nazwanych argumentów, poza zmuszaniem dzwoniącego do nieużywania parametrów pozycyjnych.
MestreLion,

1
@MestreLion: To zależy od liczby argumentów słów kluczowych dozwolonych przez interfejs API. Nie twierdzę, że moja sugestia jest lepsza niż nazwane argumenty, ale Python pozwala przechwytywać nienazwane argumenty kwargsz jakiegoś powodu.
Vinay Sajip,

Więc tylko sprawdzam. Czy pop zwraca wartość słownika, jeśli klucz istnieje, a jeśli nie, zwraca default_valueprzekazany? A potem usuwa ten klucz?
Daniel Möller,

42

Używanie ** kwargs i wartości domyślnych jest łatwe. Czasami jednak nie powinieneś używać ** kwargs w pierwszej kolejności.

W tym przypadku tak naprawdę nie najlepiej wykorzystujemy ** kwargs.

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

Powyżej jest „po co zawracać sobie głowę”? deklaracja. To jest to samo co

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

Kiedy używasz ** kwargs, masz na myśli, że słowo kluczowe jest nie tylko opcjonalne, ale warunkowe. Istnieją bardziej złożone reguły niż proste wartości domyślne.

Kiedy używasz ** kwargs, zwykle masz na myśli coś podobnego do tego, w którym nie obowiązują proste wartości domyślne.

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

Podoba mi się ten mały łamigłówka! Ciągle myślałem: „Ale możesz po prostu użyć get lub pop z - och, są one współzależne ...”
trojjer

28

Ponieważ **kwargsjest używany, gdy liczba argumentów jest nieznana, dlaczego tego nie zrobić?

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

Tak, jest to elegancki i potężny ... nie zbyt pewni nawiasów kwadratowych wokół acceptable_keys_list choć: Chciałbym zrobić to krotki lub listy, a następnie upuść te wsporniki w „if”
Mike gryzoni

Lekko zmodyfikowałem to w przypadkach, gdy spodziewane są wszystkie klucze: stackoverflow.com/questions/1098549/…
rebelliard

14

Oto inne podejście:

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)

Często używane w CBV Django (np get_form_kwargs().). ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/…
trojjer

Te get_form()metody wykazuje, w jaki sposób uzyskać argumenty słów kluczowych obszernie o odroczenie innego sposobu ( get_form_kwargsjak wspomniano powyżej). To wystąpienie formularz w następujący sposób: form_class(**self.get_form_kwargs()).
trojjer

Następnie można łatwo zastąpić get_form_kwargs()widok podklasy i dodać / usunąć kwargs na podstawie określonej logiki. Ale to dotyczy samouczka Django.
trojjer

12

Myślę, że właściwym sposobem używania **kwargsw Pythonie, jeśli chodzi o wartości domyślne, jest użycie metody słownikowej setdefault, jak podano poniżej:

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

W ten sposób, jeśli użytkownik argspoda słowo kluczowe „val” lub „val2” w słowie kluczowym , zostanie ono użyte; w przeciwnym razie zostaną użyte ustawione wartości domyślne.


11

Możesz zrobić coś takiego

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

11

W następstwie sugestii @srhegde o użyciu 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]

Ten wariant jest przydatny, gdy oczekuje się, że klasa będzie miała wszystkie elementy na naszej acceptableliście.


1
To nie jest przypadek użycia do zrozumienia listy, powinieneś użyć pętli for w swojej metodzie init.
ettanany

5

Jeśli chcesz połączyć to z * args, musisz zachować * args i ** kwargs na końcu definicji.

Więc:

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

1

@AbhinavGupta i @Steef zaproponowały użycie update(), które okazało się bardzo pomocne przy przetwarzaniu dużych list argumentów:

args.update(kwargs)

Co jeśli chcemy sprawdzić, czy użytkownik nie przekazał żadnych fałszywych / nieobsługiwanych argumentów? @VayaySajip wskazał, że pop()można go użyć do iteracyjnego przetwarzania listy argumentów. Zatem wszelkie pozostałe argumenty są fałszywe. Miły.

Oto inny możliwy sposób, aby to zrobić, zachowując prostą składnię użycia 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_argsjest setzawierający nazwy argumentów, które nie występują w domyślnych.


0

Innym prostym rozwiązaniem do przetwarzania nieznanych lub wielu argumentów może być:

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 daje swobodę dodawania dowolnej liczby argumentów słów kluczowych. Można mieć listę kluczy, dla których można ustawić wartości domyślne. Ale ustawienie domyślnych wartości dla nieokreślonej liczby kluczy wydaje się niepotrzebne. Wreszcie ważne może być posiadanie kluczy jako atrybutów instancji. Zrobiłbym to w następujący sposób:

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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.