1. 前置知识:理解对象和引用 #
在深入学习参数传递之前,我们需要先理解几个基础概念。
1.1 什么是对象 #
在 Python 中,一切都是对象。数字、字符串、列表、字典,甚至函数本身都是对象。每个对象都有:
- 类型(type):决定了对象能做什么
- 值(value):对象存储的数据
- 身份标识(id):对象在内存中的唯一地址
1.2 变量是什么 #
变量实际上是对对象的引用(可以理解为"标签"或"指针")。当你写 a = 5 时,Python 创建了一个整数对象 5,然后让变量 a 指向这个对象。
# 创建一个整数对象,让变量 a 指向它
a = 5
# 查看对象的内存地址(身份标识)
print(f"变量 a 指向的对象地址: {id(a)}")
# 查看对象的类型
print(f"对象类型: {type(a)}")
# 查看对象的值
print(f"对象的值: {a}")
# 输出示例:
# 变量 a 指向的对象地址: 140712345678912
# 对象类型: <class 'int'>
# 对象的值: 51.3 多个变量可以指向同一个对象 #
# 创建两个变量,都指向同一个对象
a = [1, 2, 3]
b = a # b 和 a 指向同一个列表对象
# 查看它们是否指向同一个对象
print(f"a 的地址: {id(a)}")
print(f"b 的地址: {id(b)}")
print(f"a 和 b 指向同一个对象: {a is b}")
# 通过 b 修改列表
b.append(4)
# a 也会看到变化,因为它们指向同一个对象
print(f"a 的值: {a}")
print(f"b 的值: {b}")
# 输出:
# a 的地址: 140712345678912
# b 的地址: 140712345678912
# a 和 b 指向同一个对象: True
# a 的值: [1, 2, 3, 4]
# b 的值: [1, 2, 3, 4]2. Python 参数传递的本质 #
Python 的参数传递方式是 "按对象引用传递" 或 "按共享传递"。这意味着:
- 当你调用函数时,传递的是对象的引用,而不是对象本身
- 函数内的参数变量和函数外的变量可能指向同一个对象
- 对可变对象的修改会影响原始对象,对不可变对象的"修改"实际上是创建了新对象
2.1 示例:理解引用传递 #
# 定义一个函数,尝试修改参数
def modify(x):
"""
尝试修改传入的参数
"""
# 打印函数内参数 x 的初始地址
print(f"函数内初始 x 的地址: {id(x)}")
# 尝试修改 x(实际上是创建新对象)
x = 10
# 打印修改后 x 的地址
print(f"函数内修改后 x 的地址: {id(x)}")
# 创建一个变量
a = 5
# 打印函数调用前 a 的地址
print(f"函数调用前 a 的地址: {id(a)}")
# 调用函数
modify(a)
# 打印函数调用后 a 的值和地址
print(f"函数调用后 a 的值: {a}") # a 的值没有改变
print(f"函数调用后 a 的地址: {id(a)}") # a 的地址没有改变
# 输出示例:
# 函数调用前 a 的地址: 140712345678912
# 函数内初始 x 的地址: 140712345678912 (x 和 a 指向同一个对象)
# 函数内修改后 x 的地址: 140712345678944 (x 现在指向新对象 10)
# 函数调用后 a 的值: 5 (a 仍然指向原来的对象)
# 函数调用后 a 的地址: 140712345678912 (地址没有改变)关键理解:函数内的 x = 10 并没有修改原来的对象,而是让 x 指向了一个新的对象 10。原来的变量 a 仍然指向对象 5。
3. 可变对象 vs 不可变对象 #
这是理解参数传递的关键区别。Python 中的对象分为两类:
3.1 不可变对象(Immutable) #
不可变对象一旦创建,就不能被修改。如果要对它进行"修改"操作,Python 会创建一个新对象。
常见的不可变对象类型:
- 数字(int, float, complex)
- 字符串(str)
- 元组(tuple)
- 布尔值(bool)
- frozenset
3.2 示例:不可变对象的行为 #
# 定义一个函数,尝试修改不可变对象
def modify_immutable(x):
"""
尝试修改不可变对象(字符串)
"""
# 打印函数内初始 x 的地址
print(f"函数内初始 x 的地址: {id(x)}")
# 尝试"修改"字符串(实际上是创建新对象)
x += " World"
# 打印修改后 x 的地址(地址会改变)
print(f"函数内修改后 x 的地址: {id(x)}")
print(f"函数内 x 的值: {x}")
# 创建一个字符串变量
s = "Hello"
# 打印函数调用前 s 的地址
print(f"函数调用前 s 的地址: {id(s)}")
# 调用函数
modify_immutable(s)
# 打印函数调用后 s 的值和地址
print(f"函数调用后 s 的值: {s}") # 原始字符串没有改变
print(f"函数调用后 s 的地址: {id(s)}") # 地址没有改变
# 输出示例:
# 函数调用前 s 的地址: 140712345678912
# 函数内初始 x 的地址: 140712345678912 (x 和 s 指向同一个对象)
# 函数内修改后 x 的地址: 140712345678944 (x 现在指向新对象)
# 函数内 x 的值: Hello World
# 函数调用后 s 的值: Hello (原始字符串没有改变)
# 函数调用后 s 的地址: 140712345678912 (地址没有改变)3.3 可变对象(Mutable) #
可变对象可以在原地修改,修改后仍然是同一个对象。
常见的可变对象类型:
- 列表(list)
- 字典(dict)
- 集合(set)
- 自定义对象
3.4 示例:可变对象的行为 #
# 定义一个函数,修改可变对象
def modify_mutable(lst):
"""
修改可变对象(列表)
"""
# 打印函数内初始 lst 的地址
print(f"函数内初始 lst 的地址: {id(lst)}")
# 修改列表(在同一个对象上操作)
lst.append(4)
# 打印修改后 lst 的地址(地址不会改变)
print(f"函数内修改后 lst 的地址: {id(lst)}")
print(f"函数内 lst 的值: {lst}")
# 创建一个列表变量
my_list = [1, 2, 3]
# 打印函数调用前 my_list 的地址
print(f"函数调用前 my_list 的地址: {id(my_list)}")
# 调用函数
modify_mutable(my_list)
# 打印函数调用后 my_list 的值和地址
print(f"函数调用后 my_list 的值: {my_list}") # 原始列表被修改了!
print(f"函数调用后 my_list 的地址: {id(my_list)}") # 地址没有改变
# 输出示例:
# 函数调用前 my_list 的地址: 140712345678912
# 函数内初始 lst 的地址: 140712345678912 (lst 和 my_list 指向同一个对象)
# 函数内修改后 lst 的地址: 140712345678912 (仍然是同一个对象)
# 函数内 lst 的值: [1, 2, 3, 4]
# 函数调用后 my_list 的值: [1, 2, 3, 4] (原始列表被修改了!)
# 函数调用后 my_list 的地址: 140712345678912 (地址没有改变)关键理解:对可变对象的修改是在原对象上进行的,所以函数外的变量也会看到变化。
4. 参数传递的四种形式 #
Python 提供了多种参数传递方式,让我们可以灵活地调用函数。
4.1 位置参数 #
位置参数是最基本的参数传递方式,按照函数定义时的顺序传递参数。
# 定义一个计算幂的函数
def power(base, exponent):
"""
计算 base 的 exponent 次方
base: 底数
exponent: 指数
"""
return base ** exponent
# 使用位置参数调用函数
# 第一个参数 2 传给 base,第二个参数 3 传给 exponent
result = power(2, 3)
# 打印结果
print(f"2 的 3 次方 = {result}")
# 输出:
# 2 的 3 次方 = 84.2 关键字参数 #
关键字参数通过参数名传递,顺序可以任意。
# 定义一个描述人物的函数
def describe_person(name, age, city):
"""
描述一个人的信息
name: 姓名
age: 年龄
city: 城市
"""
return f"{name} is {age} years old, living in {city}"
# 使用关键字参数调用函数
# 顺序可以任意,只要参数名正确
info = describe_person(age=25, city="Beijing", name="Alice")
# 打印结果
print(info)
# 输出:
# Alice is 25 years old, living in Beijing4.3 混合使用位置参数和关键字参数 #
# 定义一个函数,既有位置参数也有关键字参数
def greet(name, greeting="Hello", punctuation="!"):
"""
生成问候语
name: 姓名(位置参数)
greeting: 问候语(关键字参数,有默认值)
punctuation: 标点符号(关键字参数,有默认值)
"""
return f"{greeting}, {name}{punctuation}"
# 只传递位置参数,使用默认值
result1 = greet("Alice")
print(result1)
# 覆盖默认值
result2 = greet("Bob", "Hi", "!!!")
print(result2)
# 混合使用:位置参数 + 关键字参数
result3 = greet("Charlie", punctuation="?")
print(result3)
# 输出:
# Hello, Alice!
# Hi, Bob!!!
# Hello, Charlie?4.4 默认参数的陷阱 #
默认参数有一个重要的特性:默认值只计算一次。这可能导致意外的行为。
# 危险示例:使用可变对象作为默认参数
def append_to_list(item, lst=[]):
"""
将 item 添加到列表 lst 中
注意:这是一个有问题的实现!
"""
# 将 item 添加到列表
lst.append(item)
# 返回列表
return lst
# 第一次调用
result1 = append_to_list(1)
print(f"第一次调用结果: {result1}")
# 第二次调用(期望得到 [2],但实际得到 [1, 2])
result2 = append_to_list(2)
print(f"第二次调用结果: {result2}")
# 第三次调用(期望得到 [3],但实际得到 [1, 2, 3])
result3 = append_to_list(3)
print(f"第三次调用结果: {result3}")
# 输出:
# 第一次调用结果: [1]
# 第二次调用结果: [1, 2] (不是期望的 [2]!)
# 第三次调用结果: [1, 2, 3] (不是期望的 [3]!)问题原因:默认参数 lst=[] 在函数定义时只创建一次,之后所有调用都共享同一个列表对象。
正确做法:使用 None 作为默认值,在函数内部创建新对象。
# 正确做法:使用 None 作为默认值
def append_to_list_fixed(item, lst=None):
"""
将 item 添加到列表 lst 中
这是正确的实现方式
"""
# 如果 lst 是 None,创建一个新列表
if lst is None:
lst = []
# 将 item 添加到列表
lst.append(item)
# 返回列表
return lst
# 第一次调用
result1 = append_to_list_fixed(1)
print(f"第一次调用结果: {result1}")
# 第二次调用(现在得到期望的 [2])
result2 = append_to_list_fixed(2)
print(f"第二次调用结果: {result2}")
# 第三次调用(现在得到期望的 [3])
result3 = append_to_list_fixed(3)
print(f"第三次调用结果: {result3}")
# 输出:
# 第一次调用结果: [1]
# 第二次调用结果: [2] (正确!)
# 第三次调用结果: [3] (正确!)5. 可变长度参数 #
有时候,我们不知道函数会接收多少个参数。Python 提供了 *args 和 **kwargs 来处理这种情况。
5.1 *args - 接收任意数量的位置参数 #
*args 可以接收任意数量的位置参数,它们会被收集到一个元组中。
# 定义一个可以接收任意数量参数的求和函数
def sum_all(*args):
"""
计算所有参数的和
*args: 接收任意数量的位置参数,收集到元组中
"""
# 打印 args 的类型(应该是元组)
print(f"args 的类型: {type(args)}")
# 打印 args 的内容
print(f"args 的内容: {args}")
# 使用内置的 sum 函数求和
return sum(args)
# 调用函数,传递 3 个参数
result1 = sum_all(1, 2, 3)
print(f"sum_all(1, 2, 3) = {result1}")
print()
# 调用函数,传递 5 个参数
result2 = sum_all(1, 2, 3, 4, 5)
print(f"sum_all(1, 2, 3, 4, 5) = {result2}")
# 输出:
# args 的类型: <class 'tuple'>
# args 的内容: (1, 2, 3)
# sum_all(1, 2, 3) = 6
#
# args 的类型: <class 'tuple'>
# args 的内容: (1, 2, 3, 4, 5)
# sum_all(1, 2, 3, 4, 5) = 155.2 **kwargs - 接收任意数量的关键字参数 #
**kwargs 可以接收任意数量的关键字参数,它们会被收集到一个字典中。
# 定义一个可以接收任意数量关键字参数的函数
def print_info(**kwargs):
"""
打印所有关键字参数
**kwargs: 接收任意数量的关键字参数,收集到字典中
"""
# 打印 kwargs 的类型(应该是字典)
print(f"kwargs 的类型: {type(kwargs)}")
# 遍历字典,打印所有键值对
for key, value in kwargs.items():
print(f"{key}: {value}")
# 调用函数,传递关键字参数
print_info(name="Alice", age=25, city="Beijing")
# 输出:
# kwargs 的类型: <class 'dict'>
# name: Alice
# age: 25
# city: Beijing5.3 混合使用所有参数类型 #
在实际应用中,我们经常需要混合使用各种参数类型。参数顺序很重要:
标准顺序:位置参数 → *args → 关键字参数 → **kwargs
# 定义一个包含所有参数类型的函数
def func(a, b, *args, option=True, **kwargs):
"""
演示所有参数类型的混合使用
a, b: 位置参数
*args: 可变位置参数
option: 关键字参数(有默认值)
**kwargs: 可变关键字参数
"""
# 打印位置参数
print(f"位置参数 a: {a}, b: {b}")
# 打印可变位置参数
print(f"可变位置参数 args: {args}")
# 打印关键字参数
print(f"关键字参数 option: {option}")
# 打印可变关键字参数
print(f"可变关键字参数 kwargs: {kwargs}")
# 调用函数
func(1, 2, 3, 4, 5, option=False, x=10, y=20)
# 输出:
# 位置参数 a: 1, b: 2
# 可变位置参数 args: (3, 4, 5)
# 关键字参数 option: False
# 可变关键字参数 kwargs: {'x': 10, 'y': 20}6. 参数解包 #
参数解包允许我们将列表、元组或字典"展开"后传递给函数。
6.1 列表/元组解包(*) #
使用 * 可以将列表或元组解包为位置参数。
# 定义一个需要三个参数的函数
def func(a, b, c):
"""
计算三个数的和
"""
return a + b + c
# 创建一个列表
numbers = [1, 2, 3]
# 使用 * 解包列表,相当于 func(1, 2, 3)
result = func(*numbers)
# 打印结果
print(f"func(*{numbers}) = {result}")
# 输出:
# func(*[1, 2, 3]) = 66.2 字典解包(**) #
使用 ** 可以将字典解包为关键字参数。
# 定义一个需要关键字参数的函数
def func(name, age, city):
"""
格式化个人信息
"""
return f"{name}, {age}, {city}"
# 创建一个字典
person = {"name": "Alice", "age": 25, "city": "Beijing"}
# 使用 ** 解包字典,相当于 func(name="Alice", age=25, city="Beijing")
result = func(**person)
# 打印结果
print(result)
# 输出:
# Alice, 25, Beijing6.3 混合解包 #
可以同时使用 * 和 ** 解包。
# 定义一个函数
def func(a, b, c, name, age):
"""
演示混合解包
"""
return f"数字: {a}, {b}, {c} | 人物: {name}, {age}"
# 创建列表和字典
numbers = [1, 2, 3]
person = {"name": "Alice", "age": 25}
# 同时使用 * 和 ** 解包
result = func(*numbers, **person)
# 打印结果
print(result)
# 输出:
# 数字: 1, 2, 3 | 人物: Alice, 257. 实际应用示例 #
下面是一个完整的实际应用示例,展示如何在实际项目中使用这些参数传递技巧。
7.1 示例:灵活的数据处理函数 #
# 定义一个灵活的数据处理函数
def process_data(data, *, validate=True, log_level="INFO", **options):
"""
处理数据,支持多种选项
data: 要处理的数据(位置参数)
validate: 是否验证数据(关键字参数,只能通过关键字传递)
log_level: 日志级别(关键字参数)
**options: 其他选项(可变关键字参数)
"""
# 如果启用验证,打印验证信息
if validate:
print(f"[{log_level}] 正在验证数据...")
# 打印处理信息
print(f"正在处理 {len(data)} 个数据项")
# 处理额外选项
if "timeout" in options:
print(f"超时时间设置为 {options['timeout']} 秒")
if "max_retries" in options:
print(f"最大重试次数: {options['max_retries']}")
# 实际的数据处理逻辑(这里简单地将每个元素乘以 2)
result = [item * 2 for item in data]
# 返回处理结果
return result
# 准备数据
data = [1, 2, 3, 4, 5]
# 调用函数,使用各种参数
result = process_data(
data, # 位置参数
validate=True, # 关键字参数
log_level="DEBUG", # 关键字参数
timeout=30, # 可变关键字参数
max_retries=3 # 可变关键字参数
)
# 打印结果
print(f"处理结果: {result}")
# 输出:
# [DEBUG] 正在验证数据...
# 正在处理 5 个数据项
# 超时时间设置为 30 秒
# 最大重试次数: 3
# 处理结果: [2, 4, 6, 8, 10]8. 常见陷阱和注意事项 #
8.1 陷阱 1:意外修改可变对象 #
# 定义一个函数,意外修改了传入的列表
def add_item(item, lst):
"""
将 item 添加到列表
注意:这会修改原始列表!
"""
lst.append(item)
return lst
# 创建一个列表
my_list = [1, 2, 3]
# 调用函数
result = add_item(4, my_list)
# 打印结果
print(f"函数返回值: {result}")
# 原始列表也被修改了(这可能不是我们想要的)
print(f"原始列表: {my_list}")
# 输出:
# 函数返回值: [1, 2, 3, 4]
# 原始列表: [1, 2, 3, 4] (原始列表被修改了!)解决方案:如果不想修改原始对象,先创建副本。
# 改进版本:不修改原始列表
def add_item_safe(item, lst):
"""
将 item 添加到列表(不修改原始列表)
"""
# 创建列表的副本
new_lst = lst.copy()
# 或者使用 new_lst = lst[:]
# 在副本上操作
new_lst.append(item)
# 返回新列表
return new_lst
# 创建一个列表
my_list = [1, 2, 3]
# 调用函数
result = add_item_safe(4, my_list)
# 打印结果
print(f"函数返回值: {result}")
# 原始列表没有被修改
print(f"原始列表: {my_list}")
# 输出:
# 函数返回值: [1, 2, 3, 4]
# 原始列表: [1, 2, 3] (原始列表没有被修改)8.2 陷阱 2:默认参数使用可变对象 #
这个陷阱在前面已经详细讲解过,这里再强调一次。
# 错误示例
def bad_function(item, lst=[]): # 危险!
lst.append(item)
return lst
# 正确示例
def good_function(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst9. Python 函数参数传递规则 #
9.1. 参数定义顺序规则(函数定义时) #
Python 定义函数时,参数的书写顺序必须遵循一定规范。通常应先书写普通位置参数(无默认值),之后是 *args(可变数量的位置参数),再是有默认值的关键字参数(即命名参数),最后是 **kwargs(可变数量的关键字参数)。如果顺序写错,会导致语法错误。比如,有默认值的参数不能出现在没有默认值的位置参数之前,否则 Python 无法解析参数传递的意图。
def func(位置参数, *args, 关键字参数=默认值, **kwargs):
pass必须顺序:
- 普通位置参数(无默认值)
*args(可变位置参数)- 关键字参数(有默认值)
**kwargs(可变关键字参数)
示例:
# 正确
def func(a, b, *args, c=10, **kwargs):
pass
# 错误:有默认值的参数不能在无默认值参数之前
def func(a=10, b): # SyntaxError
pass9.2. 参数传递顺序规则(函数调用时) #
在调用函数时,实参的传递也有顺序规定。所有的位置参数必须在关键字参数前面传递,否则会导致语法错误。即先按顺序给出所有需要位置传递的参数,后面才可以通过参数名“key=value”的方式传递关键字参数。这样做是为了让 Python 明确知道每个值对应哪个参数名。
def greet(name, greeting, punctuation):
return f"{greeting}, {name}{punctuation}"
# 正确:全部位置参数
greet("Charlie", "Hello", "!")
# 正确:全部关键字参数
greet(name="Charlie", greeting="Hello", punctuation="!")
# 正确:位置参数在前,关键字参数在后
greet("Charlie", "Hello", punctuation="!")
greet("Charlie", greeting="Hello", punctuation="!")
# 错误:位置参数在关键字参数之后
greet(name="Charlie", "Hello", "!") # SyntaxError
greet("Charlie", greeting="Hello", "!") # SyntaxError(如果还有位置参数)9.3. 必须参数 vs 可选参数 #
在函数定义中,没有默认值的参数称为“必须参数”,调用时必须赋值;带有默认值的参数称为“可选参数”,在调用时可以选择性地赋值或省略,如果省略会自动填充默认值。注意如果缺少了必须参数,Python 会抛出 TypeError 错误。
def func(required1, required2, optional1=10, optional2=20):
pass
# 必须参数必须传递
func(1, 2) # 正确
func(1) # 错误:缺少 required2
# 可选参数可以不传
func(1, 2) # 使用默认值
func(1, 2, 30) # optional1=30, optional2=20
func(1, 2, 30, 40) # optional1=30, optional2=409.4. 关键字参数传递规则 #
可选参数和带默认值的参数可以通过“关键字参数”的方式指定传递,且可以跳过某些可选参数,给出你想设置的参数。而且多个关键字参数的顺序可以随意交换,不影响结果。这极大地增强了函数调用的灵活性和可读性。
def func(a, b, c=10, d=20):
pass
# 可以跳过中间的可选参数
func(1, 2, d=40) # a=1, b=2, c=10(默认), d=40
# 可以任意顺序传递关键字参数
func(1, 2, d=40, c=30) # 正确
func(a=1, b=2, d=40, c=30) # 正确9.5. 重复传递参数规则 #
同一个参数只能被赋值一次,否则会报错。这包括既用位置参数赋值又用关键字参数赋值(重复)或者一个关键字参数名写两次(重复)。Python 禁止这种情况,以免导致参数值不明确。
def func(a, b, c):
pass
# 错误:不能重复传递同一个参数
func(1, 2, a=3) # TypeError: got multiple values for argument 'a'
func(1, b=2, b=3) # SyntaxError: keyword argument repeated9.6. 可变参数规则 #
可变参数允许函数接受任意数量的位置参数(*args)或关键字参数(**kwargs)。*args 会把多余的位置实参收集成元组,**kwargs 会把多余的关键字实参收集成字典。如果在 *args 之后出现新的参数(如 b),必须用关键字方式来传递,否则会报错。
def func(a, *args, b=10, **kwargs):
pass
# 正确使用
func(1, 2, 3, 4, b=20, x=100, y=200)
# a=1, args=(2,3,4), b=20, kwargs={'x':100, 'y':200}
# 错误:*args 之后的位置参数必须用关键字传递
def func(a, *args, b): # b 必须用关键字传递
pass
func(1, 2, 3, 4) # 错误:缺少 b
func(1, 2, 3, 4, b=5) # 正确9.7. 解包参数规则 #
函数调用时,可以利用 * 或 ** 操作符解包序列(如列表/元组)或字典,将其自动“分解”为单独的参数传递给函数。这对于参数数量不固定或参数来源灵活的场景非常实用。
def func(a, b, c):
pass
# 使用 * 解包位置参数
args = (1, 2, 3)
func(*args) # 等同于 func(1, 2, 3)
# 使用 ** 解包关键字参数
kwargs = {'a': 1, 'b': 2, 'c': 3}
func(**kwargs) # 等同于 func(a=1, b=2, c=3)
# 混合使用
func(1, *[2, 3]) # 正确
func(1, **{'b': 2, 'c': 3}) # 正确
func(*[1, 2], c=3) # 正确9.8. 仅位置参数和仅关键字参数(Python 3.8+) #
从 Python 3.8 开始,可以用 / 指定“仅位置参数”,用 * 指定“仅关键字参数”。/ 之前的参数只能用位置方式传递,* 之后的参数只能用关键字方式传递。这种写法更细致地控制参数的传递方式,提高了接口的可读性和安全性。
# 使用 / 和 * 分隔参数
def func(pos_only, /, normal, *, kwd_only):
pass
# 正确
func(1, 2, kwd_only=3) # pos_only=1, normal=2, kwd_only=3
func(1, normal=2, kwd_only=3) # 正确
# 错误
func(pos_only=1, 2, kwd_only=3) # pos_only 只能用位置传递
func(1, 2, 3) # kwd_only 必须用关键字传递9.9. 默认参数陷阱 #
Python 的默认参数只在函数定义时计算一次,如果默认参数是可变对象(比如列表),每次调用实际上用的是同一个对象。这会导致意想不到的行为。一般推荐将默认参数写为 None,并在函数体内重新创建对象,避免副作用。
# ⚠️ 注意:默认参数只计算一次
def func(items=[]): # 危险!
items.append(1)
return items
func() # [1]
func() # [1, 1] # 不是 [1]!
# 正确做法
def func(items=None):
if items is None:
items = []
items.append(1)
return items9.10. 参数传递优先级 #
当你既给参数提供了默认值,又在调用时显示传递了这个参数,显式传递的参数值会覆盖默认值,也就是“谁最后赋值谁生效”。这也是多数编程语言的一致做法,可帮助你灵活配置参数行为。
def func(a, b=10, c=20):
pass
# 优先级:显式传递 > 默认值
func(1, 2, 3) # a=1, b=2, c=3
func(1, 2) # a=1, b=2, c=20
func(1, c=30) # a=1, b=10, c=309.11 快速记忆口诀 #
- 定义顺序:位置 → *args → 关键字 → **kwargs
- 调用顺序:位置参数必须在关键字参数之前
- 必须参数:无默认值的参数必须传递
- 可选参数:有默认值的参数可以不传
- 不能重复:同一参数不能传递两次
- 关键字灵活:关键字参数可以任意顺序传递
10. 最佳实践总结 #
- 理解对象引用:记住 Python 传递的是对象引用,不是对象本身
- 区分可变和不可变对象:对可变对象的修改会影响原始对象
- 避免修改传入的可变对象:除非这是函数的目的,否则应该创建副本
- 默认参数使用不可变对象:使用
None作为默认值,在函数内部创建新对象 - 合理使用 *args 和 kwargs**:提高函数的灵活性
- 参数顺序要正确:位置参数 →
*args→ 关键字参数 →**kwargs - 函数设计原则:函数应该只做一件事,并做好它
11. 总结 #
理解 Python 的参数传递机制对于编写正确、高效的代码至关重要。关键要点:
- 传递的是对象引用:函数调用时传递的是对象的引用,不是对象本身
- 不可变对象:对不可变对象的"修改"实际上是创建了新对象,原始对象不变
- 可变对象:对可变对象的修改是在原对象上进行的,会影响所有指向该对象的变量
- 默认参数陷阱:不要使用可变对象作为默认参数,使用
None代替 - 灵活的参数形式:合理使用位置参数、关键字参数、
*args和**kwargs