*args and **kwargs
What you'll learn:
- write functions that take any number of arguments with
*argsand**kwargs; - pass a function as an argument and write a short function with
lambda; - explain how a closure keeps the outer function's variables and changes them with
nonlocal; - write a decorator that adds behavior to any function.

Picture a funnel over a box. However many coins fall into it—one, three or none—they all end up in one pack. A parameter with a star works like that funnel: it gathers all the extra arguments into one tuple.
Why would you need it? The print function takes any number of arguments: one value, three, or none.
An ordinary function fixes the number of its positional parameters in the def line. An extra argument stops the program with a TypeError. A star before a parameter lifts this limit:
def total(*prices):
return sum(prices)
print(total(250, 100, 50)) # 400
print(total(7)) # 7
print(total()) # 0Let's walk through the example line by line:
def total(*prices)declares one parameter with a star—our funnel.total(250, 100, 50)puts three numbers into the tuple(250, 100, 50), andsumreturns400.total(7)gathers a tuple of one number,(7,), and the sum is7.total()gets an empty tuple(), and the sum of an empty tuple is0.
A tuple and a dict
A parameter can have one star or two. Their number decides which arguments go into the parameter and what Python collects them into.
One star: a tuple
A parameter with one star collects all extra positional arguments into a tuple. You work with it like any tuple: loop over it with for, pass it to sum or len. If there are no extra arguments, the tuple is empty, not None.
Two stars: a dict
A parameter with two stars collects extra keyword arguments into a dict. The argument's name becomes the key, and its value the value. With no extra keyword arguments, the dict is empty too.
You can put both funnels into one function:
def show(first, *args, **kwargs):
print(first, args, kwargs)
show(1, 2, 3, color="red") # 1 (2, 3) {'color': 'red'}
show(1) # 1 () {}The parameters in the def line come in this order:
- ordinary parameters;
*args;**kwargs—always last.
Ordinary parameters get their arguments first, and *args receives only what is left. In the first call, 1 goes to first, and args is (2, 3). In the second call, nothing is left over, so args and kwargs are empty.
The names args and kwargs are a convention, not a rule. Only * and ** carry the meaning, so def total(*prices) works the same way.
This is where the funnel picture stops working: the coins in the pack do not get mixed up. A tuple keeps the arguments in exactly the order they were passed.
A star in a call
In a call, the star works the other way around: it does not gather, it spreads. This is the same unpacking as in the lesson on tuples:
*spreads a list or a tuple into separate positional arguments:print(*[3, 1, 2])is the same asprint(3, 1, 2)and prints3 1 2;**spreads a dict into keyword arguments:print(1, 2, **{"sep": "-"})is the same asprint(1, 2, sep="-")and prints1-2.
Together they let a function accept any arguments and pass them to another function unchanged. The show_price function below prints the prefix price: and passes everything it received to price:
def price(amount, currency="USD"):
return f"{amount} {currency}"
def show_price(*args, **kwargs):
print("price:", price(*args, **kwargs))
show_price(250) # price: 250 USD
show_price(250, currency="EUR") # price: 250 EURLet's take the second call step by step:
show_price(250, currency="EUR")puts250intoargsandcurrency="EUR"intokwargs.price(*args, **kwargs)spreads them back and becomes the callprice(250, currency="EUR").pricereturns250 EUR, andshow_priceprints it after the prefix.
Interviewers like to ask this: "What are *args and **kwargs?" A good answer has three parts:
*argscollects extra positional arguments into a tuple, and**kwargscollects extra keyword ones into a dict;- if nothing extra was passed, they are an empty tuple and dict, not
None; - they let a function take any number of arguments or pass them on.
Common mistake
A function with a star is given a ready-made list:
def total(*prices):
return sum(prices)
print(total([250, 100])) # ✗TypeError: unsupported operand type(s) for +: 'int' and 'list'The list is one argument, so prices is ([250, 100],): a tuple of one element, the list. You dropped a whole wallet into the funnel instead of coins, and the pack holds one item. sum tries to add this list to the number 0 and stops. Unpack the list in the call: total(*[250, 100]) returns 350.
Key points
*argscollects extra positional arguments into a tuple,**kwargscollects extra keyword ones into a dict.- With no extra arguments, they are empty—
()and{}, notNone. - In a call,
*and**spread a list and a dict into separate arguments. - The pair
*args, **kwargspasses any arguments on to another function unchanged.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.