Python 是传值还是传引用?
这是一个非常经典且容易让人困惑的问题。
严格来说,Python 既不是纯粹的“传值”(Pass by Value),也不是纯粹的“传引用”(Pass by Reference)。
准确的定义是:传对象引用(Pass by Object Reference),或者叫 按赋值传递(Pass by Assignment)。
核心概念:变量是标签,不是盒子
在 C 或 Java 等语言中,变量通常被视为存储数据的“盒子”。但在 Python 中,变量是指向对象的“标签”或“引用”。
当你调用函数时,Python 实际上是将对象的引用(内存地址)传给了函数的参数。
函数内部的行为取决于你传递的对象类型是“可变”还是“不可变”:
1. 传递不可变对象(Immutable Objects)
类型: int, float, str, tuple, bool
表现: 看起来像“传值”。
因为这些对象本身不能被修改,如果你在函数内部试图修改它,Python 会创建一个新的对象,并将参数变量指向这个新对象。外部的变量不会受到影响。
python
def modify_number(x):
print(f"函数内修改前 x 的地址: {id(x)}")
x = 100 # 这是一个赋值操作,创建了新对象 100,x 指向了它
print(f"函数内修改后 x 的地址: {id(x)}")
print(f"函数内 x 的值: {x}")
a = 10
print(f"调用前 a 的地址: {id(a)}")
modify_number(a)
print(f"调用后 a 的值: {a}") # a 仍然是 10
结果: 外部变量 a 没有改变。
2. 传递可变对象(Mutable Objects)
类型: list, dict, set, 自定义类的实例
表现: 看起来像“传引用”。
因为对象是可变的,函数内部的参数变量和外部变量指向同一个内存地址。如果你通过这个引用修改了对象的内容(例如 append, pop, 修改字典键值),外部变量也会随之改变。
python
def modify_list(lst):
print(f"函数内 lst 的地址: {id(lst)}")
lst.append(999) # 在原对象上进行修改
print(f"函数内 lst 的值: {lst}")
my_list = [1, 2, 3]
print(f"调用前 my_list 的地址: {id(my_list)}")
modify_list(my_list)
print(f"调用后 my_list 的值: {my_list}") # my_list 变成了 [1, 2, 3, 999]
结果: 外部变量 my_list 被改变了。
3. 特殊情况:可变对象被重新赋值
这是最容易混淆的地方。即使传递的是可变对象(如列表),如果你在函数内部对其进行了重新赋值(使用 = 号),那么连接就会断开,外部变量不会改变。
python
def reassign_list(lst):
# 这里使用了 = 号,意味着 lst 被指向了一个全新的列表对象
lst = [100, 200]
print(f"函数内 lst 的值: {lst}")
my_list = [1, 2, 3]
reassign_list(my_list)
print(f"调用后 my_list 的值: {my_list}") # 仍然是 [1, 2, 3]
总结
Python 的参数传递机制只有一种:传对象引用。
- 如果对象是不可变的(Immutable): 你无法修改原对象,任何修改操作都会生成新对象。(效果类似传值)
- 如果对象是可变的(Mutable):
- 如果你修改对象的内容(如
append),外部会变。(效果类似传引用) - 如果你重写/赋值变量(如
x = []),外部不会变。
- 如果你修改对象的内容(如