在这篇文章中我们将详细讲讲函数参数相关知识。
我们将学习一下内容:
- 实参和参数的区别
- 位置和关键字参数
- 默认参数
- 可变长度参数(
*args
和**kwargs
) - 容器解包成函数参数
- 本地与全局参数
- 参数传递(通过值还是通过引用?)
实参和参数
- 参数是定义函数时在括号内定义或使用的变量
- 实参是调用函数时为这些参数传递的值
代码:
def print_name(name): # name 是参数
print(name)
print_name('Alex') # 'Alex' 是实参
结果:
Alex
位置和关键字参数
我们可以将参数作为位置参数或关键字参数传递。
关键字参数的一些好处可以是:
- 我们可以通过它们的名字来调用参数,以使其更清楚它们代表什么
- 我们可以重新排列参数位置,使它们最易读
代码:
def foo(a, b, c):
print(a, b, c)
# 位置参数
foo(1, 2, 3)
# 关键字实参
foo(a=1, b=2, c=3)
foo(c=3, b=2, a=1) # 可以打乱顺序
# 混合使用
foo(1, b=2, c=3)
# 混合使用时,关键字实参必须出现在位置实参之前
# foo(1, b=2, 3) # ❌
# foo(1, b=2, a=3) # ❌ 冲突
结果:
1 2 3
1 2 3
1 2 3
1 2 3
默认参数
函数可以有带有预定义值的默认参数。 该参数可以省略,然后将默认值传递给函数,或者该参数可以与不同的值一起使用。 请注意,默认参数必须定义在非默认参数之后。
代码:
def foo(a, b, c, d=4):
print(a, b, c, d)
foo(1, 2, 3, 4)
foo(1, b=2, c=3, d=100)
# ❌ 不允许默认参数在非默认参数之前
# def foo(a, b=2, c, d=4):
# print(a, b, c, d)
结果:
1 2 3 4
1 2 3 100
可变参数(*args
和 **kwargs
)
- 如果用一个星号 (
*
) 标记参数,则可以将任意数量的位置参数传递给函数(通常称为*args
)。 - 如果用两个星号 (
**
) 标记参数,则可以将任意数量的关键字参数传递给此函数(通常称为**kwargs
)。
代码:
def foo(a, b, *args, **kwargs):
print(a, b)
for arg in args:
print(arg)
for kwarg in kwargs:
print(kwarg, kwargs[kwarg])
# 1,2:位置参数,3,4,5:args参数,6,7:kwargs参数
foo(1, 2, 3, 4, 5, six=6, seven=7)
print()
# *args和**kwargs是可变的,也可以省略。
foo(1, 2, three=3)
结果:
1 2
3
4
5
six 6
seven 7
1 2
three 3
强制关键字参数
有时您希望一些参数必须是关键字参数,不能是位置参数。
您可以通过两种方法实现:
-
在函数参数列表中写入'
*
',则之后的所有参数都必须作为关键字参数传递。 -
在可变参数后面定义参数一定是关键字参数。
代码:
# ✅方法一: 用*号分割
def foo(a, b, *, c, d):
print(a, b, c, d)
foo(1, 2, c=3, d=4)
# foo(1, 2, 3, 4) # ❌:c,d是不可省略的关键字参数
# ✅方法二: 在*args或**kwargs之后定义
def foo(*args, last):
for arg in args:
print(arg)
print(last)
foo(8, 9, 10, last=50)
结果:
1 2 3 4
8
9
10
50
参数解包
- 如果列表的长度与函数参数的数量相匹配,则可以将列表或元组解压缩为带有一个星号 (
*
) 的参数。 - 同理,字典可以解压成带有两个星号(
**
)的参数,长度和键与函数参数匹配。
代码:
def foo(a, b, c):
print(a, b, c)
my_list = [4, 5, 6] # 列表或者元组
foo(*my_list) # 参数拆包
my_dict = {"a": 1, "b": 2, "c": 3} # 字典
foo(**my_dict)
# my_dict = {'a': 1, 'b': 2, 'd': 3} # ❌ 关键字参数不匹配
结果:
4 5 6
1 2 3
局部变量和全局变量
Global variables can be accessed within a function body, but to modify them,
we first must state global var_name
in order to change the global variable.
代码:
def foo1():
print("打印全局变量:", number) # number函数内没有定义,生成一个局部变量,值和全局变量一样
# number = 1 # ❌ 不允许修改未声明的全局变量
def foo2():
global number # 声明全局变量,修改才有效
number = 3
print("打印函数内全局变量:", number)
number = 0
print("打印全局变量:", number)
foo1()
print("执行完foo1函数后,打印全局变量:", number)
foo2() # 修改全局变量
print("执行完foo2函数后,打印全局变量:", number)
结果:
打印全局变量: 0
打印全局变量: 0
执行完foo1函数后,打印全局变量: 0
打印函数内全局变量: 3
执行完foo2函数后,打印全局变量: 3
如果我们不写 global var_name
并为与全局变量同名的变量分配一个新值,
这将在函数内创建一个局部变量。
全局变量保持不变。
参数传递
Python 使用一种称为“Call-by-Object”或“Call-by-Object-Reference”的机制。 必须考虑以下规则:
- 传入的参数其实是一个对象的引用(但是引用是按值传递的)
- 可变和不可变数据类型之间的区别
这意味着:
- 可变对象(例如列表、字典)可以在方法内更改。
- 但是如果在方法中重新绑定引用,外部引用仍然会指向原来的对象。
- 不可变对象(例如 int、string)不能在方法内更改。
- 但是可变对象中包含的不可变对象可以在方法中重新分配。
1. 不可变对象(例如 int、string)不能在方法内更改。
代码:
def foo(x):
x = 5 # x是不可变对象。
var = 10
print(f"函数执行前:var={var}")
foo(var) # 执行foo函数,参数var会被更改为5
print(f"函数执行后:var={var}")
结果:
函数执行前:var=10
函数执行后:var=10
2. 可变对象(例如列表、字典)可以在方法内更改。
代码:
def foo(列表):
列表.append(4)
A = [1, 2, 3]
print(f'函数执行前: A = {A}')
foo(A)
print(f'函数执行后: A = {A}')
结果:
函数执行前: A = [1, 2, 3]
函数执行后: A = [1, 2, 3, 4]
3. 可变对象中包含的不可变对象可以在方法中重新分配。
代码:
def foo(a_list):
a_list[0] = -100
a_list[2] = "Paul"
my_list = [1, 2, "Max"]
print(f'函数执行前: my_list = {my_list}')
foo(my_list)
print(f'函数执行后: my_list = {my_list}')
结果:
函数执行前: my_list = [1, 2, 'Max']
函数执行后: my_list = [-100, 2, 'Paul']
4. 参数传递的是对象的引用。
函数内,重新绑定引用,外部引用仍然会指向原来的对象。
代码:
def foo(a_list):
a_list = [50, 60, 70] # 重新定义了a_list,为局部变量。
a_list.append(50)
my_list = [1, 2, 3]
print(f'函数执行前: my_list = {my_list}')
foo(my_list)
print(f'函数执行后: my_list = {my_list}')
结果:
函数执行前: my_list = [1, 2, 3]
函数执行后: my_list = [1, 2, 3]
小心使用可变类型的
+=
和=
操作。+=
不会重新定义变量,而=
会重新定义变量。 也就是说,+=
是对变量进行追加,而=
是重新定义变量。
代码:
def foo(a_list):
a_list += [4, 5] #⚠️ 改变了传入的参数,不是局部变量。
def bar(a_list):
a_list = a_list + [4, 5] #⚠️ 重新定义了传入的参数,是局部变量。
my_list = [1, 2, 3]
print(f'foo函数执行前: my_list = {my_list}')
foo(my_list)
print(f'foo函数执行后: my_list = {my_list}')
my_list = [1, 2, 3]
print(f'bar函数执行前: my_list = {my_list}')
bar(my_list)
print(f'bar函数执行后: my_list = {my_list}')
结果:
foo函数执行前: my_list = [1, 2, 3]
foo函数执行后: my_list = [1, 2, 3, 4, 5]
bar函数执行前: my_list = [1, 2, 3]
bar函数执行后: my_list = [1, 2, 3]