Domyślne argumenty z * args i ** kwargs

84

W Pythonie 2.x (ja używam 2.7), jaki jest właściwy sposób używania domyślnych argumentów z *argsi **kwargs?
Znalazłem pytanie na SO związane z tym tematem, ale to jest dla Pythona 3 :
Wywołanie funkcji Pythona z * args, ** kwargs i opcjonalnymi / domyślnymi argumentami

Tam mówią, że ta metoda działa:

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

W wersji 2.7 powoduje to SyntaxError. Czy jest jakiś zalecany sposób zdefiniowania takiej funkcji?
Mam to w ten sposób, ale myślę, że jest lepsze rozwiązanie.

def func(arg1, arg2, *args, **kwargs):
    opt_arg ='def_val'
    if kwargs.__contains__('opt_arg'):
        opt_arg = kwargs['opt_arg']
    #...
codeforester
źródło
Wciąż najbardziej zwięzłe wyjaśnienie, z jakim się spotkałem: saltycrane.com/blog/2008/01/…
verbsintransit
4
Nigdy wyraźnie nie dzwoń __contains__. Zawsze należy stosować in: 'opt_arg' in kwargs. (Jeszcze lepiej: kwargs.get('opt_arg', 'def_val')jak w odpowiedzi mgilsona).
nneonneo

Odpowiedzi:

80

Po prostu umieść domyślne argumenty przed *args:

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

Teraz bzostanie jawnie ustawiony, jeśli przekażesz go jako argument słowa kluczowego lub drugi argument pozycyjny.

Przykłady:

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}

Zwróć uwagę, że w szczególności foo(x, y, b=z)nie działa, ponieważ bjest przypisany do pozycji w tym przypadku.


Ten kod działa również w Pythonie 3. Umieszczenie domyślnego argumentu po *args w Pythonie 3 sprawia, że ​​jest to argument „tylko ze słowem kluczowym”, który może być określony tylko przez nazwę, a nie przez pozycję. Jeśli potrzebujesz argumentu zawierającego tylko słowa kluczowe w Pythonie 2, możesz użyć rozwiązania @ mgilson.

nneonneo
źródło
1
Tak, to z powodu niejednoznaczności *args. Twój sposób jest poprawny, jeśli potrzebujesz argumentu zawierającego tylko słowo kluczowe.
nneonneo
Właśnie otrzymałem odpowiedź w komentarzach powyżej. (Co jeśli chcę wywołać funkcję z innym b, a także chcę dodać * args?)
W każdym razie, zanim zapytałem, wypróbowałem to rozwiązanie, ale okazało się, że działa tylko wtedy, gdy po zdefiniowaniu opt_arg używam tylko kwargs.
@nneonneo: Mam twoje przykłady, ale w ten sposób nadal nie daje nam swobody określania domyślnych argumentów i * argumentów w tym samym czasie, tak jak pozwala na to Python 3.x, jak wyjaśniono w linku; czy to?
5
Nie możesz zostawić argumentu domyślnego samego, jednocześnie wypełniając *args, nie. Z tego powodu funkcjonalność została dodana do Pythona 3. Zazwyczaj domyślne argumenty w Pythonie 2 są określane jako coś oczywistego, np. 0 lub Nonetak, aby można je było jawnie przekazać.
nneonneo
55

Składnia w drugim pytaniu to tylko python3.x i określa argumenty zawierające tylko słowa kluczowe. Nie działa na python2.x.

W przypadku python2.x zrobiłbym popto z kwargs:

def func(arg1, arg2, *args, **kwargs):
    opt_arg = kwargs.pop('opt_arg', 'def_val')
mgilson
źródło
Określa również * args przed argumentem domyślnym.
4

Możesz również użyć dekoratora takiego jak ten:

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

Następnie po prostu wykonaj:

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

Na przykład:

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

f() # Returns 2
f(3) # Returns 4
Daniel Américo
źródło
1

Trzymając się dość blisko podejścia do rozwiązania, próbując uczynić je bardziej ogólnym i kompaktowym, sugerowałbym rozważenie czegoś takiego:

>>> 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
źródło
0

Inny sposób obsługi w Pythonie 2.x:

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

W *argsprzeciwieństwie do odpowiedzi @ nneonneo obsługuje przekazywanie dowolnych wywołań bazowych.

conner.xyz
źródło
1
Byłoby to wyraźniejsze, gdybyś użył prawidłowego kodu, tj. 'opt_arg'Zamiast < kwarg-name >i 'def_val'zamiast< kwarg-name-default-value >
wjandrea
0

Ta odpowiedź jest rozszerzeniem tego, co zasugerował Daniel Américo .

Ten dekorator przypisuje domyślne wartości kwarg, jeśli nie są one ściśle zdefiniowane.

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

Wynik

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

Wariant

Jeśli chcesz użyć wartości domyślnych, jak zapisano w definicji funkcji, możesz uzyskać dostęp do wartości domyślnych argumentów za pomocą f.func_defaults, która zawiera listę wartości domyślnych. Aby dopasować te domyślne wartości do nazw zmiennych, należałoby zipje dodać na końcu f.__code__.varnames.

AlexLoss
źródło