Richtige Verwendung von ** kwargs in Python

454

Was ist die richtige Verwendung **kwargsin Python, wenn es um Standardwerte geht?

kwargsgibt ein Wörterbuch zurück, aber wie lassen sich Standardwerte am besten festlegen, oder gibt es einen? Soll ich nur als Wörterbuch darauf zugreifen? Get-Funktion verwenden?

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

Eine einfache Frage, für die ich keine guten Ressourcen finden kann. Die Leute machen es auf unterschiedliche Weise in Code, den ich gesehen habe, und es ist schwer zu wissen, was sie verwenden sollen.

Kekoa
quelle

Antworten:

468

Sie können einen Standardwert get()für Schlüssel übergeben, die nicht im Wörterbuch enthalten sind:

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

Wenn Sie jedoch ein bestimmtes Argument mit einem bestimmten Standardwert verwenden möchten, warum nicht zuerst benannte Argumente verwenden?

def __init__(self, val2="default value", **kwargs):
balpha
quelle
16
Ich verwende Positionsargumente nur für erforderliche Argumente und kwargs für Argumente, die möglicherweise angegeben werden oder nicht, aber es ist hilfreich, einen Standardwert zu haben. kwargs ist nett, weil Sie Ihre Argumente in beliebiger Reihenfolge einreichen können. Positionsargumente geben Ihnen diese Freiheit nicht.
Kekoa
95
Sie können benannte Argumente in beliebiger Reihenfolge übergeben. Sie müssen sich nur an die Positionen halten, wenn Sie die Namen nicht verwenden - was Sie bei Kwargs tun müssen. Die Verwendung von benannten Argumenten im Gegensatz zu kwargs gibt Ihnen die zusätzliche Freiheit, die Namen nicht zu verwenden - dann müssen Sie jedoch die Reihenfolge beibehalten .
Balpha
13
@Kekoa: Sie können benannte Argumente jederzeit in beliebiger Reihenfolge einreichen. Sie müssen keine ** kwargs verwenden, um diese Flexibilität zu erhalten.
S.Lott
13
pylint kennzeichnet es als schlechte Form, kwargs in zu verwenden __init__(). Kann jemand erklären, warum dies eine fusselfreie Übertretung ist?
Hughdbrown
271

Während die meisten Antworten sagen, dass z.

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

ist das gleiche wie"

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

das ist nicht wahr. Im letzteren Fall fkann als aufgerufen werden f(23, 42), während im ersteren Fall nur benannte Argumente akzeptiert werden - keine Positionsaufrufe. Oft möchten Sie dem Anrufer maximale Flexibilität gewähren, und daher ist die zweite Form, wie die meisten Antworten behaupten, vorzuziehen. Dies ist jedoch nicht immer der Fall. Wenn Sie viele optionale Parameter akzeptieren, von denen normalerweise nur wenige übergeben werden, ist es möglicherweise eine hervorragende Idee (Vermeidung von Unfällen und unlesbarem Code an Ihren Anrufstellen!), Die Verwendung benannter Argumente zu erzwingen - threading.Threadist ein Beispiel. Das erste Formular ist, wie Sie das in Python 2 implementieren.

Das Idiom ist so wichtig, dass es in Python 3 jetzt eine spezielle unterstützende Syntax hat: Jedes Argument nach einem einzelnen *in der defSignatur ist nur ein Schlüsselwort, dh es kann nicht als Positionsargument übergeben werden, sondern nur als benanntes. In Python 3 können Sie Folgendes wie folgt codieren:

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

In Python 3 können Sie sogar Argumente nur mit Schlüsselwörtern verwenden, die nicht optional sind (solche ohne Standardwert).

Python 2 hat jedoch noch lange Jahre produktiven Lebens vor sich. Vergessen Sie daher nicht die Techniken und Redewendungen, mit denen Sie wichtige Designideen in Python 2 implementieren können, die direkt in der Sprache in Python 3 unterstützt werden!

Alex Martelli
quelle
10
@ Alex Martelli: Ich habe keine einzige Antwort gefunden, die behauptet, kwargs sei identisch mit benannten Argumenten, geschweige denn überlegen. Aber der gute Diskurs zum Py3k ändert sich, also +1
balpha
1
@Alex Martelli: Vielen Dank für Ihre Antwort. Ich habe erfahren, dass Python 3 obligatorische Schlüsselwortargumente zulässt, deren Fehlen häufig zu einer unbefriedigenden Architektur in meinem Code und meinen Funktionen führte.
FloW
78

Ich schlage so etwas vor

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

Und dann verwenden Sie die Werte wie Sie wollen

dictionaryA.update(dictionaryB)Fügt den Inhalt dictionaryBzum dictionaryAÜberschreiben doppelter Schlüssel hinzu.

Abhinav Gupta
quelle
2
Danke @AbhinavGupta! Genau das, wonach ich gesucht habe. Gerade hinzugefügt for key in options: self.__setattr__(key, options[key])und ich bin gut zu gehen. :)
propjk007
53

Das würdest du tun

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

oder

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

Wenn Sie verwenden pop, können Sie überprüfen, ob falsche Werte gesendet wurden, und die entsprechenden Maßnahmen ergreifen (falls vorhanden).

Vinay Sajip
quelle
2
Können Sie klarstellen, was Sie meinen, indem Sie vorschlagen .pop, dass Sie überprüfen können, ob falsche Werte gesendet wurden?
Alan H.
13
@Alan H.: Wenn nach dem Knallen noch etwas in kwargs übrig ist, dann haben Sie falsche Werte.
Vinay Sajip
@VinaySajip: Ok, das ist ein großartiger Punkt für .pop "vs" .get, aber ich verstehe immer noch nicht, warum Pop benannten Argumenten vorzuziehen ist, außer dass der Aufrufer gezwungen wird, keine Positionsparameter zu verwenden.
MestreLion
1
@MestreLion: Dies hängt davon ab, wie viele Keyword-Argumente Ihre API zulässt. Ich behaupte nicht, dass mein Vorschlag besser ist als benannte Argumente, aber mit Python können Sie unbenannte Argumente kwargsaus einem bestimmten Grund erfassen .
Vinay Sajip
Also nur nachsehen. Gibt pop einen Wörterbuchwert zurück, wenn der Schlüssel vorhanden ist, und wenn nicht, gibt es den default_valueübergebenen zurück? Und entfernt diesen Schlüssel danach?
Daniel Möller
42

Die Verwendung von ** kwargs und Standardwerten ist einfach. Manchmal sollten Sie jedoch überhaupt keine ** kwargs verwenden.

In diesem Fall nutzen wir ** kwargs nicht wirklich optimal.

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

Das obige ist ein "warum sich die Mühe machen?" Erklärung. Es ist das gleiche wie

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

Wenn Sie ** kwargs verwenden, bedeutet dies, dass ein Schlüsselwort nicht nur optional, sondern auch bedingt ist. Es gibt komplexere Regeln als einfache Standardwerte.

Wenn Sie ** kwargs verwenden, meinen Sie normalerweise etwas wie das Folgende, bei dem einfache Standardeinstellungen nicht zutreffen.

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" )
S.Lott
quelle
Ich mag diesen kleinen Denksport! Ich dachte immer wieder: "Aber du könntest einfach get oder pop verwenden - oh, sie sind co-abhängig ..."
trojjer
28

Da dies verwendet **kwargswird, wenn die Anzahl der Argumente unbekannt ist, warum nicht?

class Exampleclass(object):
  def __init__(self, **kwargs):
    for k in kwargs.keys():
       if k in [acceptable_keys_list]:
          self.__setattr__(k, kwargs[k])
srhegde
quelle
Ja, das ist elegant und kraftvoll ... Ich bin mir jedoch nicht sicher über die eckigen Klammern um accept_keys_list: Ich würde dies zu einem Tupel oder einer Liste machen und diese Klammern dann in die "if"
-Anweisung
Ich habe dies leicht geändert für Fälle, in denen alle Schlüssel erwartet werden: stackoverflow.com/questions/1098549/…
rebelliard
14

Hier ist ein anderer Ansatz:

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)
Orokusaki
quelle
Wird häufig in Django CBVs verwendet (z get_form_kwargs(). B. ). ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/…
Trojjer
Die get_form()Methode zeigt, wie Sie Schlüsselwortargumente ausführlich erhalten, indem Sie auf eine andere Methode zurückgreifen ( get_form_kwargswie oben erwähnt). Es instanziiert das Formular wie folgt : form_class(**self.get_form_kwargs()).
Trojjer
Es ist dann einfach, get_form_kwargs()in einer Unterklassenansicht zu überschreiben und kwargs basierend auf einer bestimmten Logik hinzuzufügen / zu entfernen. Aber das ist für ein Django-Tutorial.
Trojjer
12

Ich denke, der richtige Weg, um **kwargsin Python zu verwenden, wenn es um Standardwerte geht, ist die Verwendung der Wörterbuchmethode setdefault, wie unten angegeben:

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

Auf diese Weise werden Benutzer verwendet, die im Schlüsselwort 'val' oder 'val2' übergeben args. Andernfalls werden die festgelegten Standardwerte verwendet.

user1930143
quelle
11

Sie könnten so etwas tun

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

Im Anschluss an @srhegde Vorschlag der Verwendung von 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]

Diese Variante ist nützlich, wenn von der Klasse erwartet wird, dass sie alle Elemente in unserer acceptableListe enthält.

Rebellier
quelle
1
Dies ist kein Anwendungsfall für ein Listenverständnis. Sie sollten in Ihrer init-Methode eine for-Schleife verwenden.
ettanany
5

Wenn Sie dies mit * args kombinieren möchten, müssen Sie * args und ** kwargs am Ende der Definition behalten.

Damit:

def method(foo, bar=None, *args, **kwargs):
    do_something_with(foo, bar)
    some_other_function(*args, **kwargs)
Jens Timmerman
quelle
1

@AbhinavGupta und @Steef schlugen die Verwendung vor update(), was ich für die Verarbeitung großer Argumentlisten sehr hilfreich fand:

args.update(kwargs)

Was ist, wenn wir überprüfen möchten, ob der Benutzer keine falschen / nicht unterstützten Argumente übergeben hat? @VinaySajip wies darauf hin, dass pop()die Liste der Argumente iterativ verarbeitet werden kann. Dann sind alle verbleibenden Argumente falsch. Nett.

Hier ist eine andere Möglichkeit, dies zu tun, wobei die einfache Syntax der Verwendung beibehalten wird 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_argsist a und setenthält die Namen von Argumenten, die in den Standardeinstellungen nicht vorkommen.

user20160
quelle
0

Eine andere einfache Lösung für die Verarbeitung unbekannter oder mehrerer Argumente kann sein:

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()
tmsss
quelle
0

** kwargs gibt die Freiheit, eine beliebige Anzahl von Schlüsselwortargumenten hinzuzufügen. Man kann eine Liste von Schlüsseln haben, für die man Standardwerte festlegen kann. Das Festlegen von Standardwerten für eine unbestimmte Anzahl von Schlüsseln erscheint jedoch nicht erforderlich. Schließlich kann es wichtig sein, die Schlüssel als Instanzattribute zu haben. Also würde ich das wie folgt machen:

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)
user3503692
quelle