函数的参数
上一节已经会定义和调用函数,但示例中的参数还比较简单。学生信息管理系统继续扩展后,同一个函数可能要接收固定数据、可选数据,或者数量不确定的数据。
如何让调用者更准确地传入数据?如果函数收到的是列表或字典,修改它会不会影响函数外部?本节围绕这些问题整理函数参数的常见用法。
形参与实参
先分清两个名称:
- 形参:定义函数时,写在函数名后面括号里的参数
- 实参:调用函数时,真正传入函数的数据
def compare_numbers(number1, number2):
if number1 > number2:
return f"{number1} > {number2}"
if number1 == number2:
return f"{number1} == {number2}"
return f"{number1} < {number2}"
x = 4
y = 5
print(compare_numbers(x, y))
print(compare_numbers(5, 5))在这个例子中:
number1、number2是形参x、y和调用时写入的5是实参
如果少传或多传了参数,调用时通常会出现 TypeError:
compare_numbers(5) # TypeError:缺少一个参数对象引用与数据修改
把列表或数字传入函数后,函数内部的修改是否会影响外部变量?Python 调用函数时,会把对象的引用传给形参,可以结合“不可变对象”和“可变对象”来理解。
不可变对象
整数、浮点数、字符串、布尔值和元组都属于不可变对象。
number = 100
def increment(value, step):
value += step
print("函数内部:", value)
increment(number, 10)
print("函数外部:", number)运行结果:
函数内部: 110
函数外部: 100value += step 会让局部变量 value 指向一个新的整数,不会修改外部的 number。
如果希望外部拿到计算结果,应使用 return:
def increment(value, step):
return value + step
number = 100
number = increment(number, 10)
print(number)可变对象
列表、字典和集合属于可变对象。函数内部修改对象本身,外部也能看到变化:
numbers = [100]
def increment_first(data, step):
data[0] += step
increment_first(numbers, 10)
print(numbers)运行结果:
[110]再看一个交换列表元素的函数:
def exchange(data, index1, index2):
temp = data[index1]
data[index1] = data[index2]
data[index2] = temp
names = ["张三", "李四", "王五"]
exchange(names, 0, 2)
print(names)运行结果:
['王五', '李四', '张三']exchange() 没有返回新列表,但它直接修改了传入的原列表。
判断有没有修改原数据
只给形参重新赋值,通常不会改变外部变量;通过下标、键或可变对象的方法修改内容,通常会影响原对象。
固定参数
函数的形参数量和名称已经确定时,最常用的是位置参数和关键字参数;部分参数还可以设置默认值,让调用更灵活。
位置与关键字传参
位置参数: 实参会按照顺序依次交给形参:
def calculate_y(x, k, b):
return k * x + b
print(calculate_y(5, 5, 6))调用 calculate_y(5, 5, 6) 时:
5传给x5传给k6传给b
位置参数写起来简单,但参数较多时容易记错顺序。
关键字参数: 调用函数时,可以写出形参名称:
def calculate_y(x, k, b):
return k * x + b
print(calculate_y(x=5, k=5, b=6))
print(calculate_y(b=6, x=5, k=5))关键字参数有两个特点:
- 参数含义更清楚
- 调用时可以调整顺序
位置参数和关键字参数可以混合使用,但位置参数必须写在关键字参数前面:
print(calculate_y(5, k=5, b=6))同一个参数不能接收两次数据:
calculate_y(5, x=6, k=5, b=6) # TypeError:x 得到了两个值默认参数
如果某个参数经常使用同一个值,可以在定义函数时设置默认值:
def calculate_y(x, k=5, b=6):
return k * x + b
print(calculate_y(5))
print(calculate_y(5, 2))
print(calculate_y(5, 2, 3))
print(calculate_y(x=5, b=10))调用时没有传入默认参数,就使用定义时设置的值;如果传入了新值,就覆盖默认值。
没有默认值的参数要写在有默认值的参数前面:
def calculate_y(x, k=5, b=6):
return k * x + b案例:计算总评成绩
需求:期中成绩默认占 40%,期末成绩占 60%,同时允许调用时调整期中权重。
参考答案
def calculate_final_score(mid_score, final_score, mid_rate=0.4):
score = mid_score * mid_rate + final_score * (1 - mid_rate)
return score
print(f"{calculate_final_score(88, 79):.2f}")
print(f"{calculate_final_score(88, 79, 0.3):.2f}")
print(
f"{calculate_final_score(mid_score=88, final_score=79, mid_rate=0.3):.2f}"
)可变默认值共享
默认参数在函数定义时创建,并不是每次调用都重新创建。列表、字典等可变对象作为默认值时,可能在多次调用之间共享数据。
下面这种写法容易出问题:
def add_name(name, names=[]):
names.append(name)
return names
print(add_name("张三"))
print(add_name("李四"))运行结果:
['张三']
['张三', '李四']如果每次调用都需要新列表,可以把默认值设为 None:
def add_name(name, names=None):
if names is None:
names = []
names.append(name)
return names不定长参数
参数数量不确定时,可以使用 *args 收集位置参数,使用 **kwargs 收集关键字参数。
位置参数收集
如果位置参数的数量不确定,可以在形参前加 *,使用 *args 收集这些参数:
def show_args(*args):
print("类型:", type(args))
print("内容:", args)
show_args()
show_args(3, 2, 1, "开始")运行结果:
类型: <class 'tuple'>
内容: ()
类型: <class 'tuple'>
内容: (3, 2, 1, '开始')args 是常用名称,也可以换成其他变量名。关键是形参前面的 *,它会把收到的位置参数收集成元组。
如果前面还有普通形参,*args 只收集剩余的位置参数:
def show_args(first, second, *args):
print("first:", first)
print("second:", second)
print("args:", args)
show_args(1, 2, 3, 4, 5, 6)运行结果:
first: 1
second: 2
args: (3, 4, 5, 6)案例:求若干个数的最大值
需求:接收任意多个数字并返回最大值。
功能分析:先用第一个数作为最大值,再遍历 *args 收集的剩余数据。
参考答案
def find_max(first, *numbers):
max_value = first
for number in numbers:
if number > max_value:
max_value = number
return max_value
print(find_max(1))
print(find_max(1, 2, 11, 2, 5))这个函数至少要接收一个位置参数。第一个数交给 first,剩余数据被收集到 numbers 元组中。
关键字参数收集
如果关键字参数的数量不确定,可以在形参前加 **,使用 **kwargs 收集这些参数:
def show_student(**kwargs):
print("类型:", type(kwargs))
print("内容:", kwargs)
show_student(name="张三", age=18, city="长沙")运行结果:
类型: <class 'dict'>
内容: {'name': '张三', 'age': 18, 'city': '长沙'}kwargs 是 keyword arguments 的缩写。**kwargs 会把收到的关键字参数收集成字典。
普通参数、*args 和 **kwargs 也可以组合使用:
def show_data(title, *args, **kwargs):
print("标题:", title)
print("位置参数:", args)
print("关键字参数:", kwargs)
show_data("学生信息", 88, 92, name="张三", age=18)收集与解包:
* 和 ** 出现在不同位置时,作用正好相反:
| 位置 | * 的作用 | ** 的作用 |
|---|---|---|
| 定义函数时 | 把多个位置参数收集成元组 | 把多个关键字参数收集成字典 |
| 调用函数时 | 把可遍历对象拆成多个位置参数 | 把字典拆成多个关键字参数 |
调用时使用星号(*)
def calculate_total(chinese, math, english):
return chinese + math + english
scores = [88, 92, 85]
print(calculate_total(*scores))*scores 相当于依次传入 88、92、85。
调用时使用双星号(**)
def show_student(name, age, city):
print(f"姓名:{name},年龄:{age},城市:{city}")
student = {"name": "张三", "age": 18, "city": "长沙"}
show_student(**student)字典的键要和函数的形参名对应。**student 相当于调用:
show_student(name="张三", age=18, city="长沙")案例:实现简化版 print()
print() 可以接收任意多个位置参数,还可以通过 sep 和 end 控制分隔符与结尾。
print(1)
print(1, 2, 3, 4)
print(1, 2, 3, 4, sep=",")
print(1, 2, 3, 4, sep=",", end="输出结束\n")需求:实现一个简化版 changed_print(),支持 print() 的三项能力:
- 使用
*args接收任意多个内容 - 使用
sep设置内容之间的分隔符 - 使用
end设置最后的结尾
功能分析:用 *args 收集要输出的内容,用关键字参数接收 sep 和 end,再按顺序完成输出。
def print_one(value):
print(value, end="")
def changed_print(*args, sep=" ", end="\n"):
for index, value in enumerate(args):
print_one(value)
if index != len(args) - 1:
print_one(sep)
print_one(end)
changed_print(1)
changed_print(1, 2, 3, 4)
changed_print(1, 2, 3, 4, sep=",")
changed_print(1, 2, 3, 4, sep=",", end="输出结束\n")运行结果:
1
1 2 3 4
1,2,3,4
1,2,3,4输出结束sep 和 end 写在 *args 后面,调用时必须使用关键字传入。因此,sep="," 和 end="输出结束\n" 都是关键字参数。
函数对象
如果函数本身也需要被保存、传递或返回,就要把它当作对象处理。函数可以赋值给变量,也可以作为参数或返回值使用。
函数赋值:
def calculate_y(x):
return 5 * x + 6
operation = calculate_y
print(operation(5))这里没有写小括号,因此赋值的是函数本身。operation 和 calculate_y 指向同一个函数对象。
函数作为参数:
def calculate_y(x):
return 5 * x + 6
def run_operation(operation, value):
return operation(value)
print(run_operation(calculate_y, 5))调用时传入的是 calculate_y,不是 calculate_y()。前者表示函数本身,后者表示调用函数后得到的结果。
函数作为返回值:
def add(number1, number2):
return number1 + number2
def get_operation():
return add
operation = get_operation()
print(operation(10, 20))这一部分先能看懂即可。后面学习更复杂的函数用法时,还会再次遇到。
小结
本节围绕五个问题掌握函数参数:
- 如何传递固定数据?使用位置参数或关键字参数。
- 如何提供可选数据?使用默认参数,但不要直接使用可变对象作为默认值。
- 如何接收数量不确定的位置参数?使用
*args,它会收集成元组。 - 如何接收数量不确定的关键字参数?使用
**kwargs,它会收集成字典。 - 定义时的
*、**用于收集,调用时用于解包。
还要注意:函数接收的是对象引用,修改传入的可变对象会影响原数据。
练习
- 编写函数,接收
x、y并返回两数之和,再分别使用位置参数和关键字参数调用 - 编写计算总评成绩的函数,把期中成绩权重设置为默认参数
- 编写函数,使用
*args计算任意多个数的和 - 编写函数,使用
**kwargs打印所有关键字参数 - 使用一个列表和一个字典,分别练习调用函数时的
*和**解包