Standardargumente mit * args und ** kwargs

84

Was ist in Python 2.x (ich verwende 2.7) der richtige Weg, um Standardargumente mit *argsund zu verwenden **kwargs?
Ich habe eine Frage zu SO im Zusammenhang mit diesem Thema gefunden, aber das ist für Python 3 :
Aufrufen einer Python-Funktion mit * args, ** kwargs und optionalen / Standardargumenten

Dort heißt es, diese Methode funktioniert:

def func(arg1, arg2, *args, opt_arg='def_val', **kwargs):
    #...

In 2.7 ergibt sich a SyntaxError. Gibt es eine empfohlene Möglichkeit, eine solche Funktion zu definieren?
Ich habe es so gemacht, aber ich würde vermuten, dass es eine schönere Lösung gibt.

def func(arg1, arg2, *args, **kwargs):
    opt_arg ='def_val'
    if kwargs.__contains__('opt_arg'):
        opt_arg = kwargs['opt_arg']
    #...
Codeforester
quelle
Immer noch die prägnanteste Erklärung, die mir begegnet ist
verbsintransit
4
Niemals explizit anrufen __contains__. Verwenden Sie immer in: 'opt_arg' in kwargs. (Noch besser: kwargs.get('opt_arg', 'def_val')wie in mgilsons Antwort).
Nneonneo

Antworten:

80

Stellen Sie einfach die Standardargumente vor *args:

def foo(a, b=3, *args, **kwargs):

Nun bwird explizit festgelegt , wenn Sie es als Schlüsselwort - Argument oder das zweite Positions Argument übergeben.

Beispiele:

foo(x) # a=x, b=3, args=(), kwargs={}
foo(x, y) # a=x, b=y, args=(), kwargs={}
foo(x, b=y) # a=x, b=y, args=(), kwargs={}
foo(x, y, z, k) # a=x, b=y, args=(z, k), kwargs={}
foo(x, c=y, d=k) # a=x, b=3, args=(), kwargs={'c': y, 'd': k}
foo(x, c=y, b=z, d=k) # a=x, b=z, args=(), kwargs={'c': y, 'd': k}

Beachten Sie, dass dies insbesondere foo(x, y, b=z)nicht funktioniert, da bes in diesem Fall nach Position zugewiesen wird.


Dieser Code funktioniert auch in Python 3. Wenn Sie in Python 3 das Standardargument nach setzen, *args wird es zu einem "Nur-Schlüsselwort" -Argument, das nur nach Name und nicht nach Position angegeben werden kann. Wenn Sie in Python 2 nur ein Schlüsselwortargument verwenden möchten, können Sie die Lösung von @ mgilson verwenden.

nneonneo
quelle
1
Ja, das liegt an der Mehrdeutigkeit von *args. Ihr Weg ist korrekt, wenn Sie ein Nur-Schlüsselwort-Argument möchten.
Nneonneo
Habe es gerade in den Kommentaren oben beantwortet. (Was ist, wenn ich die Funktion mit einem anderen b aufrufen und auch * args hinzufügen möchte?)
Bevor ich gefragt habe, habe ich diese Lösung ausprobiert, aber ich habe auch festgestellt, dass sie nur funktioniert, wenn ich nach der Definition von opt_arg nur kwargs verwende.
@nneonneo: Ich habe Ihre Beispiele erhalten, aber auf diese Weise haben wir immer noch nicht die Freiheit, Standardargumente und * Argumente gleichzeitig anzugeben, wie es Python 3.x zulässt, wie im Link erläutert. macht es?
5
Sie können ein Standardargument nicht alleine lassen, während Sie es ausfüllen *args, nein. Aus diesem Grund wurde die Funktionalität zu Python 3 hinzugefügt. In der Regel werden Standardargumente in Python 2 als etwas Offensichtliches wie 0 angegeben, Nonedamit sie explizit übergeben werden können.
nneonneo
55

Die Syntax in der anderen Frage lautet nur python3.x und gibt nur Argumente für Schlüsselwörter an. Unter python2.x funktioniert es nicht.

Für python2.x würde ich popes aus kwargs heraus machen:

def func(arg1, arg2, *args, **kwargs):
    opt_arg = kwargs.pop('opt_arg', 'def_val')
mgilson
quelle
Es gibt auch * args vor dem Standardargument an.
4

Sie können auch einen Dekorateur wie diesen verwenden:

import functools
def default_kwargs(**defaultKwargs):
    def actual_decorator(fn):
        @functools.wraps(fn)
        def g(*args, **kwargs):
            defaultKwargs.update(kwargs)
            return fn(*args, **defaultKwargs)
        return g
    return actual_decorator

Dann machen Sie einfach:

@default_kwargs(defaultVar1 = defaultValue 1, ...)
def foo(*args, **kwargs):
    # Anything in here

Zum Beispiel:

@default_kwargs(a=1)
def f(*args, **kwargs):
    print(kwargs['a']+ 1)

f() # Returns 2
f(3) # Returns 4
Daniel Américo
quelle
1

Wenn Sie sich an Ihren Lösungsansatz halten und versuchen, ihn allgemeiner und kompakter zu gestalten, würde ich Folgendes in Betracht ziehen:

>>> def func(arg1, arg2, *args, **kwargs):
...     kwargs_with_defaults = dict({'opt_arg': 'def_val', 'opt_arg2': 'default2'}, **kwargs)
...     #...
...     return arg1, arg2, args, kwargs_with_defaults

>>> func('a1', 'a2', 'a3', 'a5', x='foo', y='bar')
('a1', 'a2', ('a3', 'a5'), {'opt_arg2': 'default2', 'opt_arg': 'def_val', 'y': 'bar', 'x': 'foo'})

>>> func('a1', 'a2', 'a3', 'a5', opt_arg='explicit_value', x='foo', y='bar')
('a1', 'a2', ('a3', 'a5'), {'opt_arg2': 'default2', 'opt_arg': 'explicit_value', 'y': 'bar', 'x': 'foo'})
yaccob
quelle
0

Eine andere Möglichkeit, mit Python 2.x umzugehen:

def foo(*args, **kwargs):
    if 'kwarg-name' not in kwargs.keys():
        kwargs['kwarg-name'] = 'kwarg-name-default-value'
    return bar(*args, **kwargs)

Dies behandelt die Weitergabe beliebig *argsan den zugrunde liegenden Anruf im Gegensatz zu @ nneonneos Antwort.

conner.xyz
quelle
1
Dies wäre klarer, wenn Sie gültigen Code verwenden würden, dh 'opt_arg'anstelle von < kwarg-name >und 'def_val'anstelle von< kwarg-name-default-value >
wjandrea
0

Diese Antwort ist eine Erweiterung dessen, was Daniel Américo vorgeschlagen hat .

Dieser Dekorateur weist Standard-Kwarg-Werte zu, wenn diese nicht genau definiert sind.

from functools import wraps

def force_kwargs(**defaultKwargs):
    def decorator(f):
        @wraps(f)
        def g(*args, **kwargs):
            new_args = {}
            new_kwargs = defaultKwargs
            varnames = f.__code__.co_varnames
            new_kwargs.update(kwargs)
            for k, v in defaultKwargs.items():
                if k in varnames:
                    i = varnames.index(k)
                    new_args[(i, k)] = new_kwargs.pop(k)
            # Insert new_args into the correct position of the args.
            full_args = list(args)
            for i, k in sorted(new_args.keys()):
                if i <= len(full_args):
                    full_args.insert(i, new_args.pop((i, k)))
                else:
                    break
            # re-insert the value as a key-value pair
            for (i, k), val in new_args.items():
                new_kwargs[k] = val
            return f(*tuple(full_args), **new_kwargs)
        return g
    return decorator

Ergebnis

@force_kwargs(c=7)
def f(a, b='B', c='C', d='D', *args, **kw):
    return a, b, c, d, args, kw
#                               a    b  c    d  args      kwargs
f('r')                      # 'r', 'B', 7, 'D',   (),         {}
f(1,2,3)                    #   1,   2, 7,   3,   (),         {}
f(1, 2, 3, b=3, c=9, f='F') #   1,   3, 9,   2, (3,), {'f': 'F'}

Variante

Wenn Sie die in der Funktionsdefinition angegebenen Standardwerte verwenden möchten, können Sie mit auf das Argument Standardwerte zugreifen f.func_defaults, in dem die Standardwerte aufgelistet sind. Sie müssten zipsie mit dem Ende von versehen f.__code__.varnames, um diese Standardwerte mit den Variablennamen abzugleichen.

AlexLoss
quelle