函数的参数分为 形式参数 与 实际参数 ,简称为 形参 与 实参 。
形参 即在定义函数的时候,括号内声明的参数,形参本质就是一个 变量名 ,用来接收外部传来的值。
实参 即在调用函数的时候,括号内传入的值,值可以是常量、变量、表达式或三者的组合
def max_num(num1, num2):
""" 该函数用于返回两个数中的最大值 """
return num1 if num1 > num2 else num2
# 1、实参是常量
res = max_num(1, 2)
print(res)
# 2、实参是变量
a = 99
b = 66
res = max_num(a, b)
print(res)
# 3、实参是表达式
res = max_num(10*2, max_num(3, 4)*10)
print(res)
# 结果
2
99
40
实参可以是常量、变量、表达式的任意组合
在调用有参函数时,实参(值)会赋值给形参(变量名)。在Python中,变量名与值只是单纯的绑定关系,而对于函数来说,这种绑定关系只在函数调用时生效,在调用结束后解除。
# 位置就是顺序,位置参数指的是按照顺序定义的参数,需要从两个角度去看:
# 在定义函数的时候,按照从左到右的顺序依次定义形参,称为位置形参,凡是按照这种形式定义的形参都必须被传值。
def register(name, age, gender):
print(f'Name: {name} Age: {age} Gender: {gender}')
# 在调用函数的时候,按照从左到右的顺序依次定义为实参,称为位置实参,凡是按照这种形式定义的实参会按照从左到右的顺序与形参一一对应
register() # 由于没有给位置参数传值,所以报错,由此可以得出,位置参数必须被传值
# 结果
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: register() missing 3 required positional arguments: 'name', 'age', and 'gender'
正确使用
def register(name, age, gender):
print(f'Name: {name} Age: {age} Gender: {gender}')
register('trevor', 22, 'male')
# 结果
Name: trevor Age: 22 Gender: male
在调用函数的时候,实参可以是 key=value 的形式,称为关键字参数,凡是按照这种形式定义的实参,可以不像位置参数那样从左到右依次传值。
def register(name, age, gender):
print(f'Name: {name} Age: {age} Gender: {gender}')
register(gender="male", name="Tim Cook", age=60)
# 结果
Name: Tim Cook Age: 60 Gender: male
需要注意在调用函数的时候,实参也可以是按照位置或按关键字的混合使用,但是必须保证关键字参数在位置参数后面,且不可以对一个形参重复赋值
def register(name, age, gender):
print(f'Name: {name} Age: {age} Gender: {gender}')
register('Tim Cook', gender='male', age=60) # 正确使用
# 结果
Name: Tim Cook Age: 60 Gender: male
在定义函数的时候,就已经为形参赋值,这类形参称之为默认参数,当函数有多个参数的时候,需要将值经常改变的参数定义成位置参数,而将值较少改变的参数定义成默认参数。例如编写一个注册学生信息的函数,如果大多数学生的年龄都是22,那么完全可以将形参age定义成默认参数。
def register(name, gender, age=22): # 默认age的值为22,定义的时候就已经为参数age赋值,意味着调用时可以不对age赋值,这降低了函数调用的复杂度
print(f'Name: {name} Age: {age} Gender: {gender}')
register('tom', 'male') # 大多数情况,无需为age传值,默认为22
register('Tim Cook', 'male', 60)
# 结果
Name: tom Age: 22 Gender: male
Name: Tim Cook Age: 60 Gender: male
注意:
默认参数 必须放在位置参数之后
默认参数的值仅在函数定义阶段被赋值一次
x = 1
def foo(arg=x):
print(arg)
x = 5
foo()
# 结果
1
默认参数的值通常应该被设置为不可变数据类型
def foo(n, arg=[]):
arg.append(n)
print(arg)
foo(1)
foo(2)
foo(3)
foo(4)
# 结果
[1]
[1, 2]
[1, 2, 3]
[1, 2, 3, 4]
每次调用是在上一次的基础上向同一个列表增加值,修改如下
def foo(n, arg=None):
if arg is None:
arg = []
arg.append(n)
print(arg)
foo(1)
foo(2)
foo(3)
foo(4)
# 结果
[1]
[2]
[3]
[4]
参数的长度可变指的是在调用函数时,实参的个数可以不固定,而在调用函数时,实参的定义无非是按位置或按关键字两种形式,这就要求形参提供两种解决方案来分别处理两种形式的可变长度的参数。
如果在最后一个形参名前加*号,那么在调用函数的时候,溢出的位置实参,都会被接收,以元组的形式保存下来赋值给该形参
def foo(x, y, z=1, *args):
print(x)
print(y)
print(z)
print(args)
foo(1,2,3,4,5,6)
# 结果
1
2
3
(4, 5, 6)
如果我们事先生成了一个列表,仍然是可以传值给*args的
def foo(x, y, *args):
print(x)
print(y)
print(args)
lis = [3, 4, 5]
foo(1, 2, *lis) # *lis就相当于位置参数3, 4, 5, foo(1, 2, *lis)就等同于foo(1, 2, 3, 4, 5)
# 结果
1
2
(3, 4, 5)
如果形参为常规的参数(位置或默认),实参仍然可以是*的形式
def add(*args):
res = 0
for i in args:
res += i
print(res)
add(1, 2, 3, 4, 5)
# 结果
15
如果在最后一个形参名前加**号,那么在调用函数的时候,溢出的关键字参数,都会被接收并且以字典的形式保存下来赋值给该形参。
def foo(x, **kwargs):
print(x)
print(kwargs)
foo(y=2,x=1,z=3)
# 结果
1
{'y': 2, 'z': 3}
如果我们事先生成了一个字典,仍然是可以传值给**kwargs的。
def foo(x, y, **kwargs):
print(x)
print(y)
print(kwargs)
dic = {'a': 1, 'b': 2}
foo(1, 2, **dic) # 使用**dic就等同于foo(1, 2, a=1, b=2)
# 结果
1
2
{'a': 1, 'b': 2}
如果形参为常规参数(位置或默认),实参仍然可以是**的形式
def foo(x, y, z=3):
print(x)
print(y)
print(z)
foo(**{'x': 1, 'y': 2}) # 等同于foo(y=2, x=1)
# 结果
1
2
3
如果我们要编写一个用户认证的函数,起初可能只基于用户名密码的验证就可以了,可以使用**kwargs为日后的扩展提供良好的环境,同时保持了函数的简洁性。
在定义了**kwargs参数后,函数调用者就可以传入任意的关键字参数key=value,如果函数体代码的执行需要依赖某个key,必须在函数内进行判断
def register(name, age, **kwargs):
if 'gender' in kwargs:
# 有gender参数
pass
if 'height' in kwargs:
# 有height参数
pass
想要限定函数的调用者必须以key=value的形式传值,Python3 提供了专门的语法:需要在定义形参时,用*作为一个分隔符号,逗号之后的形参称为命名关键字参数。对于这类参数,在函数调用的时候,必须按照key=value的形式为其传值,且必须被传值
def register(name, age, *, gender, height): # gender,height为命名关键字参数
pass
register('lmy', 22, gender='male', height='180') # 正确使用
register('Tim Cook', 60, 'male', '180') # 错误,没有使用关键字的形式为gender和height传值
register('Tim Cook', 60, height='180') # 错误,没有使用关键字的形式为gender传值
但是,需要注意的是:gender不是默认参数,height也不是位置参数,因为这两个参数都在*的后面,所以都是命名关键字参数,形参 gender='male' 属于命名关键字参数的默认值,因而即便是放到形参height之前也不会有问题。
另外,如果形参中已经有args了,命名关键字参数就不再需要一个单独的作为分隔符号了,请看如下示例
def register(name, age, *args, gender='male', height):
print(f'Name: {name}, Age: {age}, Gender: {gender}, Height: {height}')
register('Tim Cook', 60, 1, 2, 3, height='180') # gender与height仍然是命名关键字参数
# 结果
Name: Tim Cook, Age: 60, Gender: male, Height: 180
综上所述,所有参数可以任意组合使用,但是定义的顺序必须是以下这种顺序。
位置参数、默认参数、*args、命名关键字参数、**kwargs
可变参数*args与关键字参数**kwargs通常是组合在一起使用的,如果一个函数的形参为*args与**kwargs,那么代表该函数可以接收任意形式、任意长度的参数
# 在该函数内部还可以把接收到的参数传给另外一个函数(这在装饰器的实现中大有用处的哦)
def func(x, y, z):
print(x, y, z)
def wrapper(*args, **kwargs):
func(*args, **kwargs)
wrapper(1, z=3, y=2)
# 结果
1 2 3
按照上述的写法,在为函数 wrapper 传参的时候,其实遵循的是函数func的参数规则,调用函数wrapper的过程分析如下:
位置实参1被接收,以元组的形式保存下来,赋值给args,直白地讲就是args=(1,),关键字实参z=3, y=2被接收,以字典的形式保存下来,赋值给kwargs,直白地讲就是kwargs={'z': 3, 'y': 2}
执行func(*args, **kwargs),也就是func((1,), {'z': 3, 'y': 2}),等同于func(1, z=3, y=2)
提示:*args、**kwargs中的args和kwargs被替换成其他名字并没有语法错误,但是使用args、kwargs是约定俗成的一件事.
Namespaces are one honking great idea – let’s do more of those!(命名空间非常有用,我们应当多加利用)