Аргументи за замовчуванням із * args та ** kwargs


84

У Python 2.x (я використовую 2.7), який правильний спосіб використовувати аргументи за замовчуванням за допомогою *argsта **kwargs?
Я знайшов запитання щодо SO, пов’язаного з цією темою, але це стосується Python 3 :
Виклик функції Python за допомогою * args, ** kwargs та необов’язкових / типових аргументів

Там вони кажуть, що цей метод працює:

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

У 2.7 це призводить до a SyntaxError. Чи існує рекомендований спосіб визначення такої функції?
Я змусив це працювати таким чином, але, мабуть, є більш приємне рішення.

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


4
Ніколи не дзвоніть прямо __contains__. Завжди використовуйте in: 'opt_arg' in kwargs. (Ще краще: kwargs.get('opt_arg', 'def_val')як у відповіді Мґільсона).
nneonneo

Відповіді:


80

Просто поставте аргументи за замовчуванням перед *args:

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

Тепер, bбуде явно встановлено, якщо ви передасте його як аргумент ключового слова або другий позиційний аргумент.

Приклади:

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}

Зверніть увагу, що, зокрема, foo(x, y, b=z)це не працює, оскільки bв такому випадку воно присвоюється посаді.


Цей код працює і в Python 3. Поміщення аргументу за замовчуванням після*args в Python 3 робить його аргументом "лише для ключових слів", який можна вказати лише за іменем, а не за позицією. Якщо ви хочете аргумент лише для ключового слова в Python 2, ви можете використовувати рішення @ mgilson.


1
Так, це через неоднозначність *args. Ваш шлях правильний, якщо ви хочете аргумент лише для ключового слова.
nneonneo

Щойно отримав відповідь у коментарях вище. (Що робити, якщо я хочу викликати функцію з іншим b, а також я хочу додати * args?)

У будь-якому випадку, перед тим, як запитати, я спробував це рішення, але я також виявив, що воно працювало лише в тому випадку, якщо після визначення opt_arg я використовував лише kwargs.

@nneonneo: Я отримав ваші приклади, але цей спосіб все одно не дає нам свободи одночасно вказувати аргументи за замовчуванням та * args, як це дозволяє Python 3.x, як пояснюється у посиланні; робить це?

5
Ви не можете залишити аргумент за замовчуванням поодинці, а також заповнюючи *args, ні. Ось чому функціональність була додана до Python 3. Як правило, аргументи за замовчуванням у Python 2 визначаються як щось очевидне, наприклад 0, або Noneтак, що їх можна явно передавати.
nneonneo

55

Синтаксис в іншому питанні - лише python3.x і вказує аргументи лише для ключових слів. Це не працює на python2.x.

Для python2.x я б popце зробив з kwargs:

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

Він також визначає * args перед аргументом за замовчуванням.

4

Ви також можете використовувати такий декоратор:

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

Тоді просто виконайте:

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

Наприклад:

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

f() # Returns 2
f(3) # Returns 4

1

Дотримуючись наближеного до вашого підходу рішення, намагаючись зробити його загальнішим та компактнішим, я б запропонував розглянути щось подібне:

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

0

Інший спосіб роботи з Python 2.x:

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

Це обробляє довільне передавання *argsбазового дзвінка на відміну від відповіді @ nneonneo.


1
Це було б зрозуміліше, якби ви використовували дійсний код, тобто 'opt_arg'замість < kwarg-name >та 'def_val'замість< kwarg-name-default-value >
wjandrea

0

Ця відповідь є продовженням того, що запропонував Даніель Амеріко .

Цей декоратор призначає значення kwarg за замовчуванням, якщо вони не є строго визначеними.

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

Результат

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

Варіант

Якщо ви хочете використовувати значення за замовчуванням, як це записано у визначенні функції, ви можете отримати доступ до значень за замовчуванням аргументу, використовуючи f.func_defaultsперелік значень за замовчуванням. Вам довелося б zipїм з кінцем, f.__code__.varnamesщоб ці значення за замовчуванням відповідали іменам змінних.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.