Default arguments with *args and **kwargs

PythonPython 2.7

Python Problem Overview


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:
https://stackoverflow.com/questions/9872824/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']
    #...

Python Solutions


Solution 1 - Python

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.

Solution 2 - Python

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

Solution 3 - Python

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

Solution 4 - Python

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

Solution 5 - Python

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

Solution 6 - Python

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.

Solution 7 - Python

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser1563285View Question on Stackoverflow
Solution 1 - PythonnneonneoView Answer on Stackoverflow
Solution 2 - PythonmgilsonView Answer on Stackoverflow
Solution 3 - PythonRomanView Answer on Stackoverflow
Solution 4 - PythonDaniel AméricoView Answer on Stackoverflow
Solution 5 - PythonyaccobView Answer on Stackoverflow
Solution 6 - Pythonconner.xyzView Answer on Stackoverflow
Solution 7 - PythonAlexLossView Answer on Stackoverflow