Python 到底是值传递还是引用传递?一个赋值实验讲透,别再背结论了
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
结论先行:Python 既不是值传递,也不是引用传递,而是"传对象引用"(pass by object reference)。函数收到的不是实参的副本,也不是实参本身,而是同一个对象的引用——效果上类似"引用的值传递"。所以可变类型(list、dict)在函数里改内容,外面看得见;不可变类型(int、str、tuple)在函数里"改"变量,外面看不见。把这个结论背下来没用,看完下面这个实验,你一辈子忘不了。 1. 一个实验:先说反直觉的现象同样是把参数传进函数,a 纹丝不动,b 却被改了。如果 Python 是"值传递",b 不该变;如果是"引用传递",a 也该跟着变。两个都不符合——所以这个问题才吵了这么多年。别急,先记住这个现象:变的是 list,不变的是 int。规律就藏在类型里。
|
| 函数里的动作 | 实际发生了什么 | 外部看得见吗 |
|---|---|---|
lst.append(1) | 通过标签钻进对象,改内容 | 看得见(对象被改了) |
x = x + 1 | 重新绑定:x 指向新对象 | 看不见(a 还贴着老对象) |
注意一个细节:x = x + 1 里的 + 1 其实也没"修改"整数对象——整数是不可变的,+ 只会造出一个新对象。所以严格说,不可变类型在函数里连"改内容"这个操作都不存在,你只能换绑。
| 类型 | 常见成员 | 能"钻进对象改内容"吗 | 传参后的表现 |
|---|---|---|---|
| 不可变 | int、float、str、tuple、frozenset | 不能 | 函数内重新赋值,外部无感 |
| 可变 | list、dict、set、bytearray | 能 | 函数内改内容,外部同步看见 |
这张表就是全部规则。所谓"Python 是值传递还是引用传递"的争论,八成是把这两种表现混为一谈了。
再加一层保险:即使传进去的是可变对象,只要你在函数里写 lst = [新列表],那也是重新绑定——外部照样看不见。判定标准永远是那句:这行代码是"改内容"还是"换标签"。
def bug(item, target=[]):
target.append(item)
return target
print(bug(1)) # [1]
print(bug(2)) # [1, 2]?!说好的默认空列表呢
陷阱根源:默认参数只在函数定义时求值一次,那个 [] 从始至终是同一个对象——所有没传 target 的调用,都在往同一个列表里塞东西。正确写法:用 None 当哨兵,函数体内再创建:
def fixed(item, target=None):
if target is None:
target = []
target.append(item)
return target
这个坑能坑住人,靠的正是"可变对象 + 引用共享"两个知识点叠加。反过来,不可变类型做默认参数(如 def f(n=0))就安全——反正没人能改它。
+= 对可变类型是"就地修改"(调用 __iadd__),对不可变类型是"重新绑定":
a = [1, 2]
b = a
a += [3] # 就地改:a 和 b 还是同一个对象
print(b) # [1, 2, 3]
x = 1
y = x
x += 1 # 重新绑定:y 无感
print(y) # 1
同样一个 +=,行为分裂成两半。面试官爱问这个,不是爱抠字眼,是在考察你有没有"可变/不可变"这根弦。
把这四句话想明白,比背"值传递/引用传递"任何一个结论都管用。
阅读原文:点击这里