Was tun **
(Doppelstern) und *
(Stern) für Parameter ?
Sie ermöglichen die Definition von Funktionen zur Annahme und die Übergabe einer beliebigen Anzahl von Argumenten, positional ( *
) und keyword ( **
).
Funktionen definieren
*args
ermöglicht eine beliebige Anzahl optionaler Positionsargumente (Parameter), die einem Tupel mit dem Namen zugewiesen werden args
.
**kwargs
ermöglicht eine beliebige Anzahl von optionalen Schlüsselwortargumenten (Parametern), die in einem Diktat mit dem Namen enthalten sind kwargs
.
Sie können (und sollten) einen geeigneten Namen auswählen, aber wenn die Argumente eine unspezifische Semantik haben sollen args
und kwargs
Standardnamen sind.
Erweiterung, Übergabe einer beliebigen Anzahl von Argumenten
Sie können auch verwendet werden *args
und **kwargs
jeweils in den Parametern aus den Listen (oder jede iterable) und dicts (oder jedes mapping), zu übergeben.
Die Funktion, die die Parameter empfängt, muss nicht wissen, dass sie erweitert werden.
Zum Beispiel erwartet der xrange von Python 2 nicht explizit *args
, aber da 3 Ganzzahlen als Argumente verwendet werden:
>>> x = xrange(3) # create our *args - an iterable of 3 integers
>>> xrange(*x) # expand here
xrange(0, 2, 2)
Als weiteres Beispiel können wir die Dikt-Erweiterung verwenden in str.format
:
>>> foo = 'FOO'
>>> bar = 'BAR'
>>> 'this is foo, {foo} and bar, {bar}'.format(**locals())
'this is foo, FOO and bar, BAR'
Neu in Python 3: Funktionen mit Argumenten definieren, die nur Schlüsselwörter enthalten
Sie können nur Schlüsselwortargumente verwenden, nachdem *args
- beispielsweise hier kwarg2
als Schlüsselwortargument angegeben werden muss - nicht positionell:
def foo(arg, kwarg=None, *args, kwarg2=None, **kwargs):
return arg, kwarg, args, kwarg2, kwargs
Verwendungszweck:
>>> foo(1,2,3,4,5,kwarg2='kwarg2', bar='bar', baz='baz')
(1, 2, (3, 4, 5), 'kwarg2', {'bar': 'bar', 'baz': 'baz'})
Auch *
selbst kann verwendet werden , um anzuzeigen , dass nur ein Schlüsselwort Argument folgen, ohne dass für unbegrenzte Positionsargumente.
def foo(arg, kwarg=None, *, kwarg2=None, **kwargs):
return arg, kwarg, kwarg2, kwargs
Auch hier kwarg2
muss es sich um ein explizit benanntes Schlüsselwortargument handeln:
>>> foo(1,2,kwarg2='kwarg2', foo='foo', bar='bar')
(1, 2, 'kwarg2', {'foo': 'foo', 'bar': 'bar'})
Und wir können keine unbegrenzten Positionsargumente mehr akzeptieren, weil wir nicht haben *args*
:
>>> foo(1,2,3,4,5, kwarg2='kwarg2', foo='foo', bar='bar')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes from 1 to 2 positional arguments
but 5 positional arguments (and 1 keyword-only argument) were given
Auch hier müssen wir einfacher gesagt kwarg
namentlich und nicht positionell angegeben werden:
def bar(*, kwarg=None):
return kwarg
In diesem Beispiel sehen wir, dass beim Versuch, die kwarg
Position zu bestehen, eine Fehlermeldung angezeigt wird:
>>> bar('kwarg')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: bar() takes 0 positional arguments but 1 was given
Wir müssen den kwarg
Parameter explizit als Schlüsselwortargument übergeben.
>>> bar(kwarg='kwarg')
'kwarg'
Python 2-kompatible Demos
*args
(normalerweise "star-args" genannt) und **kwargs
(Sterne können durch das Aussprechen von "kwargs" impliziert werden, aber mit "double star kwargs" explizit angegeben werden) sind gängige Redewendungen von Python für die Verwendung der *
und **
-Notation. Diese spezifischen Variablennamen sind nicht erforderlich (z. B. könnten Sie *foos
und verwenden **bars
), aber eine Abweichung von der Konvention kann Ihre Python-Codierkollegen wahrscheinlich verärgern.
Wir verwenden diese normalerweise, wenn wir nicht wissen, was unsere Funktion erhalten wird oder wie viele Argumente wir möglicherweise übergeben, und manchmal sogar, wenn jede Variable separat benannt wird, dies sehr chaotisch und redundant wird (dies ist jedoch ein Fall, in dem dies normalerweise explizit ist besser als implizit).
Beispiel 1
Die folgende Funktion beschreibt, wie sie verwendet werden können, und demonstriert das Verhalten. Beachten Sie, dass das genannte b
Argument vom zweiten Positionsargument vor verwendet wird:
def foo(a, b=10, *args, **kwargs):
'''
this function takes required argument a, not required keyword argument b
and any number of unknown positional arguments and keyword arguments after
'''
print('a is a required argument, and its value is {0}'.format(a))
print('b not required, its default value is 10, actual value: {0}'.format(b))
# we can inspect the unknown arguments we were passed:
# - args:
print('args is of type {0} and length {1}'.format(type(args), len(args)))
for arg in args:
print('unknown arg: {0}'.format(arg))
# - kwargs:
print('kwargs is of type {0} and length {1}'.format(type(kwargs),
len(kwargs)))
for kw, arg in kwargs.items():
print('unknown kwarg - kw: {0}, arg: {1}'.format(kw, arg))
# But we don't have to know anything about them
# to pass them to other functions.
print('Args or kwargs can be passed without knowing what they are.')
# max can take two or more positional args: max(a, b, c...)
print('e.g. max(a, b, *args) \n{0}'.format(
max(a, b, *args)))
kweg = 'dict({0})'.format( # named args same as unknown kwargs
', '.join('{k}={v}'.format(k=k, v=v)
for k, v in sorted(kwargs.items())))
print('e.g. dict(**kwargs) (same as {kweg}) returns: \n{0}'.format(
dict(**kwargs), kweg=kweg))
Wir können in der Online-Hilfe nach der Signatur der Funktion suchen, mit help(foo)
der es uns sagt
foo(a, b=10, *args, **kwargs)
Nennen wir diese Funktion mit foo(1, 2, 3, 4, e=5, f=6, g=7)
welche druckt:
a is a required argument, and its value is 1
b not required, its default value is 10, actual value: 2
args is of type <type 'tuple'> and length 2
unknown arg: 3
unknown arg: 4
kwargs is of type <type 'dict'> and length 3
unknown kwarg - kw: e, arg: 5
unknown kwarg - kw: g, arg: 7
unknown kwarg - kw: f, arg: 6
Args or kwargs can be passed without knowing what they are.
e.g. max(a, b, *args)
4
e.g. dict(**kwargs) (same as dict(e=5, f=6, g=7)) returns:
{'e': 5, 'g': 7, 'f': 6}
Beispiel 2
Wir können es auch mit einer anderen Funktion aufrufen, die wir nur bereitstellen a
:
def bar(a):
b, c, d, e, f = 2, 3, 4, 5, 6
# dumping every local variable into foo as a keyword argument
# by expanding the locals dict:
foo(**locals())
bar(100)
Drucke:
a is a required argument, and its value is 100
b not required, its default value is 10, actual value: 2
args is of type <type 'tuple'> and length 0
kwargs is of type <type 'dict'> and length 4
unknown kwarg - kw: c, arg: 3
unknown kwarg - kw: e, arg: 5
unknown kwarg - kw: d, arg: 4
unknown kwarg - kw: f, arg: 6
Args or kwargs can be passed without knowing what they are.
e.g. max(a, b, *args)
100
e.g. dict(**kwargs) (same as dict(c=3, d=4, e=5, f=6)) returns:
{'c': 3, 'e': 5, 'd': 4, 'f': 6}
Beispiel 3: Praktische Anwendung bei Dekorateuren
OK, vielleicht sehen wir das Dienstprogramm noch nicht. Stellen Sie sich vor, Sie haben mehrere Funktionen mit redundantem Code vor und / oder nach dem Differenzierungscode. Die folgenden benannten Funktionen sind nur zur Veranschaulichung Pseudocode.
def foo(a, b, c, d=0, e=100):
# imagine this is much more code than a simple function call
preprocess()
differentiating_process_foo(a,b,c,d,e)
# imagine this is much more code than a simple function call
postprocess()
def bar(a, b, c=None, d=0, e=100, f=None):
preprocess()
differentiating_process_bar(a,b,c,d,e,f)
postprocess()
def baz(a, b, c, d, e, f):
... and so on
Wir könnten in der Lage sein, dies anders zu handhaben, aber wir können die Redundanz mit einem Dekorateur auf jeden Fall extrahieren, und so zeigt unser unten stehendes Beispiel, wie *args
und **kwargs
sehr nützlich sein kann:
def decorator(function):
'''function to wrap other functions with a pre- and postprocess'''
@functools.wraps(function) # applies module, name, and docstring to wrapper
def wrapper(*args, **kwargs):
# again, imagine this is complicated, but we only write it once!
preprocess()
function(*args, **kwargs)
postprocess()
return wrapper
Und jetzt kann jede umschlossene Funktion viel prägnanter geschrieben werden, da wir die Redundanz herausgerechnet haben:
@decorator
def foo(a, b, c, d=0, e=100):
differentiating_process_foo(a,b,c,d,e)
@decorator
def bar(a, b, c=None, d=0, e=100, f=None):
differentiating_process_bar(a,b,c,d,e,f)
@decorator
def baz(a, b, c=None, d=0, e=100, f=None, g=None):
differentiating_process_baz(a,b,c,d,e,f, g)
@decorator
def quux(a, b, c=None, d=0, e=100, f=None, g=None, h=None):
differentiating_process_quux(a,b,c,d,e,f,g,h)
Und durch unseren Code Ausklammern, die *args
und **kwargs
uns erlaubt , zu tun, reduzieren wir Codezeilen, die Verbesserung der Lesbarkeit und Wartbarkeit und die alleinige kanonische Standorte für die Logik in unserem Programm. Wenn wir einen Teil dieser Struktur ändern müssen, haben wir einen Ort, an dem wir jede Änderung vornehmen können.