Python 到底是值传递还是引用传递?一个赋值实验讲透,别再背结论了
Python 到底是值传递还是引用传递一个赋值实验讲透别再背结论了系列Python 底层设计逻辑类型原理拆解型分发CSDN / 掘金 / 51CTO / 阿里云 / 知乎(回答)字数约 1600 字结论先行Python 既不是值传递也不是引用传递而是传对象引用pass by object reference。函数收到的不是实参的副本也不是实参本身而是同一个对象的引用——效果上类似引用的值传递。所以可变类型list、dict在函数里改内容外面看得见不可变类型int、str、tuple在函数里改变量外面看不见。把这个结论背下来没用看完下面这个实验你一辈子忘不了。1. 一个实验先说反直觉的现象defadd_one(x):xx1# 对数字加一defappend_one(lst):lst.append(1)# 往列表里塞一个元素a10add_one(a)print(a)# 10没变b[1,2]append_one(b)print(b)# [1, 2, 1]变了同样是把参数传进函数a纹丝不动b却被改了。如果 Python 是值传递b不该变如果是引用传递a也该跟着变。两个都不符合——所以这个问题才吵了这么多年。别急先记住这个现象变的是 list不变的是 int。规律就藏在类型里。2. 本质Python 的变量不是盒子是标签理解这一切只需要接受一个设定Python 的变量名不存值它只是一个贴在对象上的标签名字绑定。a10这行代码的底层动作是右边先造出一个整数对象10然后让名字a指向它。a本身不占地方存10它只是个引用相当于便利贴。赋值语句c a更不是复制了一个盒子——它只是把新标签c也贴到同一个对象上a[1,2,3]ca c.append(4)print(a)# [1, 2, 3, 4]——a 和 c 贴的是同一个对象这就是为什么is和必须分开比的是对象里的值is比的是是不是同一个对象。c is a在这里是True。关键认知变量是标签那改变量就有两种完全不同的含义——把标签撕下来贴到别的对象上重新绑定还是钻进对象里面改内容。这两件事对可变/不可变类型的效果天差地别。3. 传参传的是标签的复印件调用函数时发生的事可以用一句话说清函数收到的是实参引用的副本——一个新标签贴到同一个对象上。defappend_one(lst):# lst 是新标签和 b 贴同一个列表lst.append(1)# 钻进对象改内容 → b 看得见defadd_one(x):# x 是新标签和 a 贴同一个 10xx1# 撕下 x贴到一个新对象 11 上 → a 无感现在回头看第 1 节的两个现象全部解释通了函数里的动作实际发生了什么外部看得见吗lst.append(1)通过标签钻进对象改内容看得见对象被改了x x 1重新绑定x指向新对象看不见a还贴着老对象注意一个细节x x 1里的 1其实也没修改整数对象——整数是不可变的只会造出一个新对象。所以严格说不可变类型在函数里连改内容这个操作都不存在你只能换绑。4. mutable vs immutable一张表说清类型常见成员能钻进对象改内容吗传参后的表现不可变int、float、str、tuple、frozenset不能函数内重新赋值外部无感可变list、dict、set、bytearray能函数内改内容外部同步看见这张表就是全部规则。所谓Python 是值传递还是引用传递的争论八成是把这两种表现混为一谈了。再加一层保险即使传进去的是可变对象只要你在函数里写lst [新列表]那也是重新绑定——外部照样看不见。判定标准永远是那句这行代码是改内容还是换标签。5. 经典陷阱默认参数为什么记仇defbug(item,target[]):target.append(item)returntargetprint(bug(1))# [1]print(bug(2))# [1, 2]说好的默认空列表呢陷阱根源默认参数只在函数定义时求值一次那个[]从始至终是同一个对象——所有没传target的调用都在往同一个列表里塞东西。正确写法用None当哨兵函数体内再创建deffixed(item,targetNone):iftargetisNone:target[]target.append(item)returntarget这个坑能坑住人靠的正是可变对象 引用共享两个知识点叠加。反过来不可变类型做默认参数如def f(n0)就安全——反正没人能改它。6. 同类陷阱 不总是等于 对可变类型是就地修改调用__iadd__对不可变类型是重新绑定a[1,2]ba a[3]# 就地改a 和 b 还是同一个对象print(b)# [1, 2, 3]x1yx x1# 重新绑定y 无感print(y)# 1同样一个行为分裂成两半。面试官爱问这个不是爱抠字眼是在考察你有没有可变/不可变这根弦。7. 一句话总结变量是贴在对象上的标签赋值是贴新标签不是复制内容传参传的是引用的副本——新标签贴老对象能不能影响外部只看一件事改内容可变还是换标签重新绑定所以 Python 的准确叫法是传对象引用pass by object reference。把这四句话想明白比背值传递/引用传递任何一个结论都管用。下一篇《Python 的垃圾回收到底回收了谁引用计数分代回收一次讲清》——一个del之后对象在内存里经历了什么。关注不迷路。