2009-09-12 4 views
51

Ich komme aus einem Hintergrund in statischen Sprachen. Kann jemand (im Idealfall durch Beispiel) die reale Welt erklären Vorteile der Verwendung von ** kwargs über benannte Argumente?Warum verwenden ** kwargs in Python? Was sind echte Vorteile gegenüber benannten Argumenten?

Für mich scheint es nur den Funktionsaufruf mehrdeutig zu machen. Vielen Dank.

+1

Denken Sie an varargs in C - manchmal kann man nicht wissen, was Ihre Argumente sein. –

+10

Bitte verwenden Sie nicht den Ausdruck "reale Welt" - es ist argumentativ. Es sagt: "Alle Beispiele, die ich bisher gesehen habe, sind erfunden und nutzlos; meine Anwendung ist die reale Welt, deine Beispiele sind eine Fantasiewelt." Bitte ändere deine Frage, um "reale Welt" zu eliminieren. –

+67

Nur jemand, der streiten mag, würde denken, dass meine Frage argumentativ war. – meppum

Antwort

32

Reale Beispiele:

Zierer - sie sind in der Regel generisch, so dass Sie nicht die Argumente im Voraus angeben:

def decorator(old): 
    def new(*args, **kwargs): 
     # ... 
     return old(*args, **kwargs) 
    return new 

Orte, an denen Sie möchten mit einer unbekannten Anzahl von Schlüsselwortargumenten zaubern. Django ORM tut, zB:

Model.objects.filter(foo__lt = 4, bar__iexact = 'bar') 
4

**kwargs sind gut, wenn Sie nicht im Voraus den Namen der Parameter kennen. Zum Beispiel verwendet der Konstruktor dict diese, um die Schlüssel des neuen Wörterbuchs zu initialisieren.

dict(**kwargs) -> new dictionary initialized with the name=value pairs 
    in the keyword argument list. For example: dict(one=1, two=2) 
In [3]: dict(one=1, two=2) 
Out[3]: {'one': 1, 'two': 2} 
+1

Es wird oft verwendet, wenn Sie viele Argumente an eine andere Funktion übergeben möchten, bei der Sie die Optionen nicht unbedingt kennen.Zum Beispiel kann specialplot (a, ** kwargs) Plotoptionen in kwargs an eine generische Plotfunktion übergeben, die diese Optionen als benannte Parameter akzeptiert. – Tristan

+1

Obwohl ich diesen schlechten Stil erwäge, da er die Introspektion behindert. – bayer

0

Ein Beispiel ist die Umsetzung python-argument-binders, so benutzte:

>>> from functools import partial 
>>> def f(a, b): 
...  return a+b 
>>> p = partial(f, 1, 2) 
>>> p() 
3 
>>> p2 = partial(f, 1) 
>>> p2(7) 
8 

Dies ist aus der functools.partial Python-Dokumentation: teilweise ist 'relativ gleichwertig' zu diese impl:

def partial(func, *args, **keywords): 
    def newfunc(*fargs, **fkeywords): 
     newkeywords = keywords.copy() 
     newkeywords.update(fkeywords) 
     return func(*(args + fargs), **newkeywords) 
    newfunc.func = func 
    newfunc.args = args 
    newfunc.keywords = keywords 
    return newfunc 
+0

hier, da 'keywords' ein Wörterbuch ist, erstellt .copy() nicht einfach einen anderen Zeiger? was bedeuten würde, dass "fkeywords" zum ursprünglichen Wörterbuch hinzugefügt wird. Ich würde denken, dass Sie mit copy.deepcopy() eine tatsächliche Kopie des Wörterbuchs erstellen möchten, nein? – aeroNotAuto

+0

* 'currying' * ist der Straßenname für * partielle Argumentbindung * – smci

51

Vielleicht möchten Sie aus einer Reihe von Gründen fast beliebige benannte Argumente akzeptieren - und das können Sie mit dem Formular **kw machen.

Der häufigste Grund ist, die Argumente direkt an eine andere Funktion weiterzugeben, die Sie umhüllen (Dekoratoren sind ein Fall davon, aber FAR von der einzigen!) - in diesem Fall löst **kw die Kopplung zwischen Wrapper und Wrapper, da der Wrapper die Argumente des Wrappers nicht kennen oder berücksichtigen muss. Hier ist ein weiterer, ganz anderer Grund:

d = dict(a=1, b=2, c=3, d=4) 

wenn alle Namen im Voraus bekannt sein mußten, so ist offenbar dieser Ansatz konnte einfach nicht existiert, nicht wahr? Und btw, wenn anwendbar, ich viel lieber auf diese Weise eine dict machen, dessen Schlüssel Literalzeichenfolgen zu:

d = {'a': 1, 'b': 2, 'c': 3, 'd': 4} 

einfach, weil die letztere ganz Interpunktion lastig und damit weniger lesbar.

Wenn keiner der ausgezeichneten Gründe für die Annahme **kwargs gilt, dann akzeptiere es nicht: es ist so einfach. IOW, wenn es keinen guten Grund gibt, dem Aufrufer zu erlauben, extra benannte Args mit beliebigen Namen zu übergeben, darf dies nicht passieren - vermeiden Sie einfach ein **kw Formular am Ende der Signatur der Funktion in der def Anweisung.

Was mit**kw in einem Aufruf, die Sie, dass Sie die jeweils zusammen den genauen Satz von benannten Argumente können, mit entsprechenden Werten in einem dict passieren muss, unabhängig von einem Punkt einzigen Anruf, dann verwenden Sie diese dict an dem einzelnen Anrufpunkt. Vergleichen:

if x: kw['x'] = x 
if y: kw['y'] = y 
f(**kw) 

zu:

if x: 
    if y: 
    f(x=x, y=y) 
    else: 
    f(x=x) 
else: 
    if y: 
    f(y=y) 
    else: 
    f() 

Selbst mit nur zwei Möglichkeiten (! Und der einfachsten Art), das Fehlen von **kw aleady macht die zweite Option absolut unhaltbar und untragbar - nur stell dir vor, wie es ausgeht, wenn da ein halbes dutzend möglichkeiten, möglicherweise in etwas reicherer interaktion ... ohne **kw wäre das leben unter solchen umständen absolute hölle!

+0

+1 - überraschend nützlich, um dies zu tun, aber etwas von einem Paradigmenwechsel, wenn Sie (sagen wir) Java gewohnt sind. – ConcernedOfTunbridgeWells

10

Es gibt zwei Fälle:

Erstens: Sie wickeln eine weitere Funktion, die eine Reihe von Keyword-Argumente, aber du wird sie nur entlang passieren:

def my_wrapper(a, b, **kwargs): 
    do_something_first(a, b) 
    the_real_function(**kwargs) 

Zweitens: Sie sind bereit, jede Keyword-Argument zu akzeptieren, beispielsweise Attribute für ein Objekt einzustellen:

class OpenEndedObject: 
    def __init__(self, **kwargs): 
     for k, v in kwargs.items(): 
      setattr(self, k, v) 

foo = OpenEndedObject(a=1, foo='bar') 
assert foo.a == 1 
assert foo.foo == 'bar' 
31

ein weiterer Grund, warum Sie wollen vielleicht **kwargs verwenden (und 0.123.) ist, wenn Sie eine vorhandene Methode in einer Unterklasse erweitern. Sie wollen alle vorhandenen Argumente auf die Methode der übergeordneten Klasse, passieren aber sicherstellen wollen, dass Ihre Klasse, auch wenn die Unterschrift Änderungen in einer zukünftigen Version arbeitet weiter:

class MySubclass(Superclass): 
    def __init__(self, *args, **kwargs): 
     self.myvalue = kwargs.pop('myvalue', None) 
     super(MySubclass, self).__init__(*args, **kwargs) 
+4

+1: Tun Sie das sehr, sehr häufig. –

2

Hier ist ein Beispiel, habe ich in CGI Python. Ich habe eine Klasse erstellt, die **kwargs in die __init__-Funktion übernommen hat. Die erlaubte mir das DOM auf der Server-Seite mit Klassen zu emulieren:

document = Document() 
document.add_stylesheet('style.css') 
document.append(Div(H1('Imagist\'s Page Title'), id = 'header')) 
document.append(Div(id='body')) 

Das einzige Problem ist, dass Sie die folgenden nicht tun können, weil class ein Python-Schlüsselwort ist.

Div(class = 'foo') 

Die Lösung besteht darin, auf das zugrunde liegende Wörterbuch zuzugreifen.

Div(**{'class':'foo'}) 

Ich sage nicht, dass dies eine "richtige" Verwendung der Funktion ist. Was ich sage ist, dass es alle möglichen unvorhersehbaren Möglichkeiten gibt, wie solche Features genutzt werden können.

1

Und hier ist ein weiteres typisches Beispiel:

MESSAGE = "Lo and behold! A message {message!r} came from {object_} with data {data!r}." 

def proclaim(object_, message, data): 
    print(MESSAGE.format(**locals()))