Python programming *args and **kwargs
Introduction
I have worked with Python a fair amount, but never on large projects. I am able to write and read Python for the most part, but I would not call myself an expert.
Recently when working in Python I saw several function with signatures like this:
def my_func(*args **kwargs):
# do stuff....
I had no idea with *args or **kwargs meant… So I looked it up.
The Python docs definition of parameter sheds some light on what the * and ** mean when placed on parameters in a function signature. Specifically the var-positional and var-keyword parameter definitions.
These are widely used mechanisms to allow function to accept a variable number of arguments. For example in the AWS boto3 library service functions.
Keyword arguments and the **kwargs parameter
Keyword arguments are those specified by name. For example:
def func1(foo, bar):
# here bar = 1 and foo = 2
func1(bar=1, foo=2)
These parameters are specified in calling the function by their parameter names. When you use keyword arguments the order the arguments are specified is not relevant.
In a function using the **kwargs parameter all arguments that are passed in with names are represented in kwargs as a dictionary. For example:
def func2(**kwargs):
print(kwargs)
func2(a=1, b=2, c=3) # outputs: {'a': 1, 'b': 2, 'c': 3}
Another neat feature here is to be able to use a dictionary built in your code to pass as the keyword arguments to your function with the **kwargs parameter. For example:
def func3(**kwargs):
print(kwargs)
d={'a': 1,'b':2, 'c': 3}
func3(**d) # outputs: {'a': 1, 'b': 2, 'c': 3}
Position arguments and the *args parameter
Position arguments are arguments that are not keyword arguments. Meaning they do not have the parameter name in the argument. Just the values are passed in with no label or name.
def func4(foo, bar):
# here foo = 1 and bar = 2
func4(1, 2)
With positional parameters the order in which the arguments are passed in matters!
Using *args as a parameter to a function allows you to use args as an iterable list of the positional arguments passed to the function.
def func5(*args):
print(args)
func5(1,2,3,4,5) # outputs: (1, 2, 3, 4, 5)
Much like the keyword arguments above you can build a list and pass it into your function with the *args parameter to populate the position arguments. For example:
def func6(*args):
print(args)
l=[1,2,3,4,5]
func6(*l) # outputs: (1, 2, 3, 4, 5)
Additional Info
Using *args and **kwargs together
I often see function signatures using both *args and **kwargs. There are likely several reasons you would want to do this, but only specifying one can lead to a situation like this:
def func7(**kwargs):
print(kwargs)
func7(1, 2, 3)
# results in:
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: func2() takes 0 positional arguments but 3 were given
and
def func8(*args):
print(args)
func8(a=1, b=2, c=3)
# results in:
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: func3() got an unexpected keyword argument 'a'
Let see how using both parameters works in practice:
def func9(*args, **kwargs):
print(f'args={args}')
print(f'kwargs={kwargs}')
func9(1,2,3)
# outputs:
# args=(1, 2, 3)
# kwargs={}
func9(a=1,b=2,c=3)
# outputs:
# args=()
# kwargs={'a': 1, 'b': 2, 'c': 3}
Using both can work but I would avoid it. It depends on your specific use case of course, but as a general rule I would avoid the complexity of using both.
Pythons definitions of arguments and parameters
This link talks about the distinction, but in short:
- Parameters are defined by names and appear in the function definition.
- Arguments are the values passed to a function when calling it.