Python for Programmers: Variadic Function Calls
Welcome to the Variadic Function Calls lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
We've seen variadic functions, which take an arbitrary number of arguments. We can also call functions with arbitrary numbers of arguments. When we call
some_function(*some_list), each value insome_listbecomes a separate argument to the function. It's like callingf(some_list[0], some_list[1], ...).>
def add(x, y):return x + ynumbers = [1, 2]add(*numbers)Result:
3
That example works because the function takes two arguments, and our
numberslist has two elements. The two list elements become two separate arguments to the function. If we try to pass too many arguments or too few, Python raises an exception.>
def add(x, y):return x + ynumbers = [1, 2, 3]add(*numbers)Result:
TypeError: add() takes 2 positional arguments but 3 were given
>
def negate(x):return -xnumbers = [1, 2]negate(*numbers)Result:
TypeError: negate() takes 1 positional argument but 2 were given
>
def negate(x):return -xnumbers = [1]negate(*numbers)Result:
-1
Variadic function calls are often used with variadic functions. The function takes an arbitrary number of arguments, and we pass it an arbitrary number of arguments.
>
def product(*args):result = 1for arg in args:result *= argreturn result- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
args = [2, 4]product(*args)Result:
8
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
args = [2, 3, 4]product(*args)Result:
24
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
args = []product(*args)Result:
1
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
product(*[5, 6])Result:
30
We can also make variadic function calls with dictionaries. Each item in the dictionary becomes a kwarg. The dictionary's keys are the arguments' names, and the dictionary's values are the arguments' values.
Note that the function below is a regular, non-variadic function. But we're calling it variadically! We use
**rather than*since we're passing kwargs.>
def product(a, b):return a * bargs = {"a": 2,"b": 4,}# This is identical to `product(a=2, b=4)`.product(**args)Result:
8
As usual, we have to pass the correct number of arguments. This is true no matter how we call it, even when calling it variadically with
**!>
def product(a, b):return a * bargs = {"a": 2,"b": 4,"c": 6,}product(**args)Result:
TypeError: product() got an unexpected keyword argument 'c'
In an earlier lesson, we saw that functions are first-class values. We'll use that fact in the next example.
The function below accepts another function as a regular positional argument, along with some
*argsand**kwargs. Then it calls the function, passing the*argsand**kwargsalong.>
def double_result(f, *args, **kwargs):return 2 * f(*args, **kwargs)- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
def product(a, b):return a * bdouble_result(product, 2, 3)Result:
12
double_resultworks with any number of arguments, as long as the argument count matches what the passed function expects.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
def negate(x):return -xdouble_result(negate, 8)Result:
-16
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
def always_3():return 3double_result(always_3)Result:
6
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
def add_four_numbers(a, b, c, d):return a + b + c + ddouble_result(add_four_numbers, 1, 2, 3, 4)Result:
20
The
double_resultfunction passes the arguments along, no matter how many there are. Still, if we pass the wrong number of arguments, we get an exception. The exception happens whendouble_resultcalls the underlying function likenegateoralways_3oradd_four_numbers.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
def add_four_numbers(a, b, c, d):return a + b + c + ddouble_result(add_four_numbers, 1, 2, 3, 4, 5)Result:
TypeError: add_four_numbers() takes 4 positional arguments but 5 were given
So far we've only used lists for the variadic arguments, but we can use any iterable (that is, any data structure that we can loop over). This is a great example of how powerful and universal Python's iterable system is!
For example, we've seen that
list(some_dict)gives us the dictionary's keys. We can also call a function with*some_dict, which turns each key into a separate positional argument. The function call doesn't care whether*some_iterableis a list, a tuple, a dict, or a special iterable object defined by a third-party library. They all work in the same way.>
def add(x, y):return x + ynumbers = {1: "one",2: "two",}add(*numbers)Result:
3
Although that does work, we don't recommend actually passing a dict in this way. If we want to pass the dict's keys as
*args, it would be better to explicitly call.keys(). That's 7 more characters, but it makes our intent very clear, whereasadd(*some_dict)looks like a mistake at first.>
def add(x, y):return x + ynumbers = {1: "one",2: "two",}add(*numbers.keys())Result:
3
We've now seen the main features of Python's function arguments. There are more nuances in Python than in many other languages, but they're there for good reasons.
Python's flexibility allows us to:
- Pass positional arguments, like all other languages:
f(1, 2). - Name our arguments, even when those arguments were declared as positional:
make_cat("Keanu", age=2). - Pass arguments out of order when naming them:
make_cat(age=4, name="Ms.Fluff"). - Pass (and accept) variable numbers of arguments:
sum(*args). - Pass (and accept) variable numbers of kwargs:
return_kwargs(**kwargs).
- Pass positional arguments, like all other languages:
A given function definition or function call can use any mixture of these features. It's a wonderful perk of Python, and you might miss it when working in other languages!
Here's a code problem:
Write a function,
pipeline, to help us write code in a pipelined style. By "pipelined", we mean calling a number of functions sequentially, likeh(g(f(x))).pipelineshould take an initial value and a variable number of functions. It calls each function in order, passing in the previous function's return value. For example,pipeline(12, add_four, double)should returndouble(add_four(12)), which is 32.A hint: you can use a loop to call one function at a time, updating a local variable each time.
def pipeline(initial_value, *functions):result = initial_valuefor function in functions:result = function(result)return result# Define some testing functions that we'll pipeline together.def double(x):return 2 * xdef add_four(x):return x + 4def stringify(x):return str(x)def append_0(s):return s + "0"def integerify(s):return int(s)assert pipeline(5) == 5assert pipeline(4, double) == 8assert pipeline(12, add_four, stringify) == "16"assert pipeline("10", append_0, integerify) == 100assert pipeline(12, add_four, double, stringify, append_0, integerify) == 320- Goal:
None
- Yours:
None