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']
#...
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
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.
Method 2
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')
Method 3
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
Method 4
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
Method 5
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'})
Method 6
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.
Method 7
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.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0