你有没有遇到过这种情况:
def add_user(users, name, age):
users[name] = age
return users
user_list = {"张三": 25}
result = add_user(user_list, "李四", 30)
print(user_list) # 你猜输出什么?
你兴高采烈地写下这段代码,以为结果就是 {"张三": 25},结果一运行...
{"张三": 25, "李四": 30} # WTF?为什么我的user_list变了?
然后你百度了一圈,Stack Overflow翻了三页,最后发现... Python的参数传递根本不是你想的那样!
今天咱们就来彻底搞懂这个让人头大的玩意儿。
先给你上一课:Python参数传递的真相
很多教程都说:"Python参数传递是值传递",或者"Python参数传递是引用传递"。
都是错的!
Python的参数传递有个专门的术语,叫做:对象引用传递(Object Reference Passing)。
翻译成人话就是:你传给函数的不是值本身,也不是对象的引用,而是指向对象的一个引用。
听起来像不像绕口令?别急,本小姐用海底捞的例子给你解释清楚:
# 想象你有一个海底捞等位牌(变量)
# 这个等位牌上有桌号(对象的内存地址)
def 修改等位信息(等位牌):
# 你拿着等位牌,但你可以选择:
# 1. 在原桌加菜(修改原对象)
# 2. 换到新桌(让等位牌指向新对象)
pass
两大阵营:可变对象 vs 不可变对象
Python的对象分两种,这也是Python函数参数传递绕不开的坑:
不可变对象(Immutable)
- 数字(int, float)
- 字符串(str)
- 元组(tuple)
- 布尔值(bool)
可变对象(Mutable)
- 列表(list)
- 字典(dict)
- 集合(set)
- 自定义类
记住这个铁律:
- 传不可变对象:函数内部修改不会影响外部
- 传可变对象:函数内部修改会影响外部(但在函数内部重新赋值不会)
来看具体例子:
# 不可变对象示例
def change_number(num):
num = num + 1
print(f"函数内部:{num}")
x = 10
change_number(x)
print(f"函数外部:{x}")
# 输出:
# 函数内部:11
# 函数外部:10 # 没变!
# 可变对象示例
def change_list(lst):
lst.append(999)
print(f"函数内部:{lst}")
my_list = [1, 2, 3]
change_list(my_list)
print(f"函数外部:{my_list}")
# 输出:
# 函数内部:[1, 2, 3, 999]
# 函数外部:[1, 2, 3, 999] # 变了!
五种参数定义方式,你用对了吗?
Python函数参数有5种写法,每种都有它的使用场景:
1. 位置参数(Positional Arguments)
最基础的写法:
def send_email(to, subject, content):
print(f"发给:{to}")
print(f"主题:{subject}")
print(f"内容:{content}")
# 调用时必须按顺序
send_email("老板@gmail.com", "加薪申请", "老板,我想涨工资...")
2. 关键字参数(Keyword Arguments)
调用时指定参数名,顺序可以乱:
send_email(
content="老板,我想涨工资...",
to="老板@gmail.com",
subject="加薪申请"
)
3. 默认参数(Default Arguments)
给参数设置默认值,不传就用默认的:
def register_user(username, email, active=True, role="普通用户"):
print(f"用户:{username}")
print(f"邮箱:{email}")
print(f"状态:{'活跃' if active else '禁用'}")
print(f"角色:{role}")
register_user("张三", "zhangsan@qq.com") # active和role用默认值
register_user("李四", "lisi@qq.com", active=False) # 只改active
⚠️ 致命警告:默认参数的坑!
# ❌ 千万不要这样写!
def add_item(item, items=[]): # 这里有个大坑!
items.append(item)
return items
add_item("苹果") # 返回 ["苹果"]
add_item("香蕉") # 返回 ["苹果", "香蕉"] # WTF?为什么苹果还在?
# ✅ 正确写法
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
原因是:默认参数在函数定义时就创建了,不是在调用时!
4. 可变位置参数(*args)
接收任意数量的位置参数:
def calculate_sum(*numbers):
print(f"收到 {len(numbers)} 个数字")
total = sum(numbers)
return total
print(calculate_sum(1, 2, 3)) # 6
print(calculate_sum(10, 20, 30, 40, 50)) # 150
print(calculate_sum()) # 0
5. 可变关键字参数(**kwargs)
接收任意数量的关键字参数:
def create_user_profile(**user_info):
print("用户信息:")
for key, value in user_info.items():
print(f" {key}: {value}")
create_user_profile(name="张三", age=25, city="北京", hobby="编程")
参数组合的正确姿势
当多种参数混用时,顺序很关键:
def 函数名(
位置参数,
*可变位置参数,
关键字参数,
**可变关键字参数
):
pass
def complex_function(a, b, *args, c=10, d=20, **kwargs):
print(f"a: {a}, b: {b}")
print(f"args: {args}")
print(f"c: {c}, d: {d}")
print(f"kwargs: {kwargs}")
complex_function(1, 2, 3, 4, 5, c=100, extra_param="额外参数")
输出:
a: 1, b: 2
args: (3, 4, 5)
c: 100, d: 20
kwargs: {'extra_param': '额外参数'}
实战避坑指南
坑1:不要在函数内部修改可变参数
# ❌ 危险写法
def process_data(data):
data.sort() # 直接修改原数据!
return data
# ✅ 安全写法
def process_data(data):
new_data = data.copy() # 先复制
new_data.sort()
return new_data
坑2:谨慎使用可变对象作为默认参数
(上面已经讲过了,再强调一遍)
坑3:注意参数类型检查
def calculate_discount(price, discount_rate):
if not isinstance(price, (int, float)):
raise TypeError("price必须是数字")
if not isinstance(discount_rate, (int, float)):
raise TypeError("discount_rate必须是数字")
if not 0 <= discount_rate <= 1:
raise ValueError("discount_rate必须在0-1之间")
return price * (1 - discount_rate)
坑4:合理使用类型提示
from typing import List, Dict, Optional, Union
def user_profile(
name: str,
age: int,
emails: List[str],
metadata: Optional[Dict[str, Union[str, int]]] = None
) -> Dict[str, any]:
"""
创建用户档案
Args:
name: 用户名
age: 年龄
emails: 邮箱列表
metadata: 额外信息(可选)
Returns:
用户档案字典
"""
if metadata is None:
metadata = {}
return {
"name": name,
"age": age,
"emails": emails,
"metadata": metadata
}
最佳实践总结
- 优先使用不可变对象作为参数,避免副作用
- 默认参数用None,不要用可变对象
- 适当使用类型提示,提高代码可读性
- 函数只做一件事,参数控制在5个以内
- 合理使用返回值,不要试图在函数中"顺便"修改参数
记住一句话:函数参数就像给朋友的礼物,送出去了就不是你的了,别老想着去修改它。
下次再遇到Python函数参数的坑,你就知道该怎么避开了。
你在项目里遇到过哪些Python参数传递的坑?评论区聊聊,看看有没有更骚的操作!