When defining a function, we determine the names and positions of its parameters, thereby completing the definition of the function’s interface. For the caller of the function, it is only necessary to know how to pass the correct parameters and what value the function will return. The complex logic inside the function is encapsulated, and the caller does not need to understand it.
Python’s function definition is very simple, yet highly flexible. In addition to the normally defined required parameters, you can also use default parameters, variable positional arguments, and keyword arguments. This allows the interfaces defined by functions to handle complex parameters and also simplifies the caller’s code.
Let’s first write a function to calculate x squared:
def power(x):
return x * x
For the power(x) function, the parameter x is a positional argument.
When we call the power function, we must pass exactly one argument, x:
>>> power(5)
25
>>> power(15)
225
Now, what if we want to calculate x cubed? We could define another function power3, but what if we need to calculate x^4, x^5, and so on? We cannot define an infinite number of functions.
You might have thought of modifying power(x) to power(x, n) to calculate x^n. Let’s do that:
def power(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
With this modified power(x, n) function, we can calculate any power of x:
>>> power(5, 2)
25
>>> power(5, 3)
125
The modified power(x, n) function has two parameters: x and n. Both are positional arguments. When calling the function, the two passed values are assigned to the parameters x and n in the order of their positions.
The new definition of the power(x, n) function is fine, but the old calling code will fail because we added a parameter, causing the old code to miss an argument and fail to call properly:
>>> power(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: power() missing 1 required positional argument: 'n'
Python’s error message is clear: calling the function power() is missing a positional argument n.
This is where default arguments come in handy. Since we often calculate x squared, we can set the default value of the second parameter n to 2:
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
Now, when we call power(5), it is equivalent to calling power(5, 2):
>>> power(5)
25
>>> power(5, 2)
25
For other cases where n > 2, we must explicitly pass n, such as power(5, 3).
As can be seen from the above example, default arguments can simplify function calls. When setting default arguments, there are a few points to note:
What are the benefits of using default arguments? The biggest benefit is that it reduces the difficulty of calling the function.
For example, let’s write a function to register a first-grade student, which needs to take two arguments: name and gender:
def enroll(name, gender):
print('name:', name)
print('gender:', gender)
Calling the enroll() function only requires passing two arguments:
>>> enroll('Sarah', 'F')
name: Sarah
gender: F
What if we want to pass additional information like age and city? This would greatly increase the complexity of calling the function.
We can set age and city as default arguments:
def enroll(name, gender, age=6, city='Beijing'):
print('name:', name)
print('gender:', gender)
print('age:', age)
print('city:', city)
Now, most students only need to provide the two required arguments when registering:
>>> enroll('Sarah', 'F')
name: Sarah
gender: F
age: 6
city: Beijing
Only students who differ from the default parameters need to provide additional information:
enroll('Bob', 'M', 7)
enroll('Adam', 'M', city='Tianjin')
It is clear that default arguments reduce the difficulty of function calls, and when more complex calls are needed, more arguments can be passed. Whether it’s a simple call or a complex one, only one function needs to be defined.
When there are multiple default arguments, during a call, you can provide default arguments in order. For example, calling enroll('Bob', 'M', 7) means that, besides the name and gender arguments, the last argument is applied to the age parameter, and the city parameter still uses the default value since it was not provided.
You can also provide some default arguments out of order. When doing so, you need to specify the parameter name. For example, calling enroll('Adam', 'M', city='Tianjin') means the city parameter uses the passed value, while other default parameters continue to use their default values.
Default arguments are very useful, but if used improperly, you can get stuck in a pitfall. The biggest pitfall with default arguments is demonstrated below:
First, define a function that takes a list, appends ‘END’ to it, and returns it:
def add_end(L=[]):
L.append('END')
return L
When you call it normally, the results seem fine:
>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']
When you call it using the default argument, the result is correct at first:
>>> add_end()
['END']
But when you call add_end() again, the result is incorrect:
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']
Many beginners are confused: the default argument is [], but the function seems to “remember” the list after adding ‘END’ last time.
The reason is explained as follows:
When a Python function is defined, the value of the default argument L is calculated once, which is []. Because the default argument L is also a variable, it points to the object []. Every time the function is called, if the content of L is changed, the content of the default argument will change the next time it is called, and it will no longer be the [] from the function definition.
Important Note
When defining default arguments, always remember one thing: default arguments must point to immutable objects!
To fix the above example, we can use None, an immutable object:
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
Now, no matter how many times you call it, there will be no problem:
>>> add_end()
['END']
>>> add_end()
['END']
Why are immutable objects like str and None designed? Because once an immutable object is created, the data inside the object cannot be modified, which reduces errors caused by data modification. Additionally, since the object is immutable, there is no need to lock it when reading it simultaneously in a multi-tasking environment; reading it concurrently is perfectly fine. When writing programs, if you can design an immutable object, try to do so.
In Python functions, you can also define variable positional arguments. As the name suggests, variable positional arguments allow you to pass a variable number of arguments, which can be 1, 2, any number, or even 0.
Let’s take a math problem as an example: given a set of numbers a, b, c…, please calculate a^2 + b^2 + c^2 + ….
To define this function, we must determine the input parameters. Since the number of parameters is uncertain, we first think of passing a, b, c… as a list or tuple. Thus, the function can be defined as follows:
def calc(numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
But when calling it, we need to first assemble a list or tuple:
>>> calc([1, 2, 3])
14
>>> calc((1, 3, 5, 7))
84
If we use variable positional arguments, the way to call the function can be simplified to this:
运行
>>> calc(1, 2, 3)
14
>>> calc(1, 3, 5, 7)
84
So, we change the function’s parameter to a variable positional argument:
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
Defining a variable positional argument is similar to defining a list or tuple parameter; you just add an asterisk * before the parameter name. Inside the function, the parameter numbers receives a tuple, so the function code remains completely unchanged. However, when calling the function, you can pass any number of arguments, including 0:
>>> calc(1, 2)
5
>>> calc()
0
What if you already have a list or tuple and want to call a function with variable positional arguments? You can do this:
>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])
14
This way of writing is certainly feasible, but the problem is that it’s too cumbersome. Therefore, Python allows you to add an asterisk * before a list or tuple to unpack its elements as variable positional arguments:
>>> nums = [1, 2, 3]
>>> calc(*nums)
14
*nums means unpack all elements of the nums list and pass them as variable positional arguments. This way of writing is quite useful and common.
Variable positional arguments allow you to pass 0 or any number of arguments, which are automatically assembled into a tuple when the function is called. Variable keyword arguments, on the other hand, allow you to pass 0 or any number of arguments with parameter names (keyword arguments), which are automatically assembled into a dictionary (dict) inside the function. Here’s an example:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
The function person accepts required parameters name and age, as well as variable keyword arguments kw. When calling this function, you can pass only the required parameters:
>>> person('Michael', 30)
name: Michael age: 30 other: {}
You can also pass any number of keyword arguments:
>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
What’s the use of variable keyword arguments? They can extend the functionality of a function. For example, in the person function, we ensure that we receive the name and age parameters. However, if the caller is willing to provide more parameters, we can receive them too. Imagine you are implementing a user registration feature where username and age are required fields, and everything else is optional. Using variable keyword arguments to define this function can meet the registration requirements.
Similar to variable positional arguments, you can first assemble a dictionary and then convert it into keyword arguments to pass in:
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, city=extra['city'], job=extra['job'])
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
Of course, the above complex call can be simplified to:
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
**extra means unpack all key-value pairs of the extra dictionary and pass them as keyword arguments to the function’s **kw parameter. kw will receive a dictionary. Note that the dictionary received by kw is a copy of extra; modifications to kw will not affect the extra outside the function.
For variable keyword arguments, the function caller can pass any unrestricted keyword arguments. To find out exactly which ones were passed, you need to check within the function via kw.
Taking the person() function again as an example, we want to check if the city and job parameters were passed:
def person(name, age,** kw):
if 'city' in kw:
# has city parameter
pass
if 'job' in kw:
# has job parameter
pass
print('name:', name, 'age:', age, 'other:', kw)
However, the caller can still pass unrestricted keyword arguments:
>>> person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)
If you want to restrict the names of the keyword arguments, you can use keyword-only arguments. For example, to only accept city and job as keyword arguments, the function can be defined as follows:
def person(name, age, *, city, job):
print(name, age, city, job)
Unlike variable keyword arguments **kw, keyword-only arguments require a special separator, *. Parameters after * are treated as keyword-only arguments.
Here’s how to call it:
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer
If a function definition already includes a variable positional argument (*args), the keyword-only arguments that follow no longer require the special separator *:
def person(name, age, *args, city, job):
print(name, age, args, city, job)
Keyword-only arguments must be passed with their parameter names; this is different from positional arguments. If the parameter names are not provided during the call, an error will occur:
>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() missing 2 required keyword-only arguments: 'city' and 'job'
Because the call lacks the parameter names city and job, the Python interpreter treats the first two arguments as positional arguments and the latter two as being passed to *args, but the missing keyword-only arguments cause an error.
Keyword-only arguments can have default values, simplifying the call:
def person(name, age, *, city='Beijing', job):
print(name, age, city, job)
Since the keyword-only argument city has a default value, it is not necessary to pass the city parameter when calling:
>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer
When using keyword-only arguments, it is important to note that if there is no variable positional argument, you must add a * as a special separator. Without the *, the Python interpreter cannot distinguish between positional arguments and keyword-only arguments:
def person(name, age, city, job):
# Missing *, city and job are treated as positional arguments
pass
In Python, you can define functions using a combination of up to 5 types of parameters: required parameters, default parameters, variable positional arguments (*args), keyword-only arguments, and variable keyword arguments (**kwargs). However, please note that the order of parameter definition must be:
required parameters -> default parameters -> variable positional arguments (*args) -> keyword-only arguments -> variable keyword arguments (**kwargs)
For example, defining a function that includes several of these parameter types:
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
When calling a function, the Python interpreter automatically passes the corresponding arguments based on their positions and names.
>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
Most impressively, you can also call the above functions using a tuple and a dictionary:
>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
Therefore, for any function, it can be called in a form similar to func(*args, **kw), regardless of how its parameters are defined.
Tip
Although up to 5 types of parameters can be combined, the comprehensibility of the function interface becomes very poor. It is generally recommended to use no more than 3-4 types in combination for clarity.
Python functions have very flexible parameter forms, enabling both simple calls and the passing of very complex parameters.
Default arguments must always use immutable objects. If a mutable object is used, logical errors will occur during program execution!
Note the syntax for defining variable positional arguments and variable keyword arguments:
*args are variable positional arguments; args receives a tuple.**kw are variable keyword arguments; kw receives a dict.Also, note the syntax for passing variable positional arguments and variable keyword arguments when calling a function:
func(1, 2, 3), or by first assembling a list or tuple and then passing it via *args: func(*(1, 2, 3)).func(a=1, b=2), or by first assembling a dict and then passing it via **kw: func(**{'a': 1, 'b': 2}).Using *args and **kw is a Python convention, although other parameter names can be used, it is best to follow the convention.
Keyword-only arguments are used to restrict the parameter names that the caller can pass and can also provide default values.
When defining keyword-only arguments without a preceding variable positional argument (*args), do not forget to write the separator *; otherwise, they will be defined as positional arguments.