105

In Python 2.x (I use 2.7), which is the proper way to use default arguments with *args and **kwargs?
I've found a question on SO related to this topic, but that is for Python 3:
Calling a Python function with *args,**kwargs and optional / default arguments

There, they say this method works:

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

In 2.7, it results in a SyntaxError. Is there any recommended way to define such a function?
I got it working this way, but I'd guess there is a nicer solution.

def func(arg1, arg2, *args, **kwargs):
    opt_arg ='def_val'
    if kwargs.__contains__('opt_arg'):
        opt_arg = kwargs['opt_arg']
    #...
codeforester
  • 39,467
  • 16
  • 112
  • 140
  • 6
    *Never* explicitly call `__contains__`. Always use `in`: `'opt_arg' in kwargs`. (Even better: `kwargs.get('opt_arg', 'def_val')` as in mgilson's answer). – nneonneo Mar 08 '13 at 19:59
  • Still the most succinct explanation I've run across: http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/ – verbsintransit Mar 08 '13 at 19:52

7 Answers7

114

Just put the default arguments before the *args:

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

Now, b will be explicitly set if you pass it as a keyword argument or the second positional argument.

Examples:

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}

Note that, in particular, foo(x, y, b=z) doesn't work because b is assigned by position in that case.


This code works in Python 3 too. Putting the default arg after *args in Python 3 makes it a "keyword-only" argument that can only be specified by name, not by position. If you want a keyword-only argument in Python 2, you can use @mgilson's solution.

martineau
  • 119,623
  • 25
  • 170
  • 301
nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • 1
    Yes, that's due to the ambiguity of `*args`. Your way is correct if you want a keyword-only argument. – nneonneo Mar 08 '13 at 19:48
  • Just got it answered in the comments above. (What if I want to call the function with a different b, and also I want to add *args?) –  Mar 08 '13 at 19:50
  • Anyway, before asking, I tried this solution, but I also found it that only worked if after defining the opt_arg I use only kwargs. –  Mar 08 '13 at 19:54
  • @nneonneo: I got your examples, but this way still doesn't give us the freedom to specify default arguments and *args at the same time, like Python 3.x allows it, as explained in the link; does it? –  Mar 08 '13 at 20:05
  • 7
    You can't leave a default argument alone while also filling in `*args`, no. This is why the functionality was added to Python 3. Typically, default arguments in Python 2 are specified as something obvious like 0 or `None` so that they can be explicitly passed in. – nneonneo Mar 08 '13 at 20:07
  • Just FYI, this solution gives a warning in `pylint` saying: `keyword-arg-before-vararg`. To avoid this warning, you can instead do `def foo(a, *args, b=3, **kwargs):`. However this will mean you **always** need to pass the value of `b` as a keyword argument – amin_nejad Mar 06 '20 at 14:10
63

The syntax in the other question is python3.x only and specifies keyword only arguments. It doesn't work on python2.x.

For python2.x, I would pop it out of kwargs:

def func(arg1, arg2, *args, **kwargs):
    opt_arg = kwargs.pop('opt_arg', 'def_val')
mgilson
  • 300,191
  • 65
  • 633
  • 696
21

Similar approach to @yaccob, but clear and concise:

In Python 3.5 or greater:

def foo(a, b=3, *args, **kwargs):
  defaultKwargs = { 'c': 10, 'd': 12 }
  kwargs = { **defaultKwargs, **kwargs }
  print(a, b, args, kwargs)
  
  # Do something    

foo(1) # 1 3 () {'c': 10, 'd': 12}
foo(1, d=5) # 1 3 () {'c': 10, 'd': 5}
foo(1, 2, 4, d=5) # 1 2 (4,) {'c': 10, 'd': 5}

Note: you can use In Python 2

kwargs = merge_two_dicts(defaultKwargs, kwargs)

In Python 3.5

kwargs = { **defaultKwargs, **kwargs }

In Python 3.9

kwargs = defaultKwargs | kwargs  # NOTE: 3.9+ ONLY
Roman
  • 19,236
  • 15
  • 93
  • 97
5

You could also use a decorator like this:

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

Then just do:

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

For instance:

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

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

Sticking quite close to your solution approach while trying to make it more generic and more compact I would suggest to consider something like this:

>>> 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
  • 1,230
  • 13
  • 16
0

Another way to handle with 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)

This handles passing arbitrary *args to the underlying call unlike @nneonneo's answer.

naught101
  • 18,687
  • 19
  • 90
  • 138
conner.xyz
  • 6,273
  • 8
  • 39
  • 65
  • 1
    This would be clearer if you used valid code, i.e. `'opt_arg'` instead of `< kwarg-name >` and `'def_val'` instead of `< kwarg-name-default-value >` – wjandrea Dec 31 '18 at 14:31
0

This answer is an extension of what Daniel Américo suggested.

This decorator assigns default kwarg values if they are not strictly defined.

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

Result

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

Variant

If you want to use the default values as written in the function definition, you could access the argument default values using f.func_defaults, which lists the default values. You would have to zip them with the end of the f.__code__.varnames to match these default values with the variable names.

AlexLoss
  • 491
  • 4
  • 13