LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

Python 到底是值传递还是引用传递?一个赋值实验讲透,别再背结论了

zhenglin
2026年10月14日 15:1 本文热度 38

结论先行:Python 既不是值传递,也不是引用传递,而是"传对象引用"(pass by object reference)。函数收到的不是实参的副本,也不是实参本身,而是同一个对象的引用——效果上类似"引用的值传递"。所以可变类型(list、dict)在函数里改内容,外面看得见;不可变类型(int、str、tuple)在函数里"改"变量,外面看不见。把这个结论背下来没用,看完下面这个实验,你一辈子忘不了。


1. 一个实验:先说反直觉的现象

def add_one(x):

    x = x + 1          # 对数字"加一"


def append_one(lst):

    lst.append(1)      # 往列表里塞一个元素


a = 10

add_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 的变量名不存值,它只是一个贴在对象上的标签(名字绑定)。

a = 10

这行代码的底层动作是:右边先造出一个整数对象 10,然后让名字 a 指向它。a 本身不占地方存"10",它只是个引用,相当于便利贴。

赋值语句 c = a 更不是"复制了一个盒子"——它只是把新标签 c 也贴到同一个对象上:

a = [1, 2, 3]

c = a

c.append(4)

print(a)      # [1, 2, 3, 4]——a 和 c 贴的是同一个对象

这就是为什么 is 和 == 必须分开:== 比的是对象里的值,is 比的是"是不是同一个对象"。c is a 在这里是 True。

关键认知:变量是标签,那"改变量"就有两种完全不同的含义——把标签撕下来贴到别的对象上(重新绑定),还是钻进对象里面改内容。这两件事,对可变/不可变类型的效果天差地别。


3. 传参:传的是"标签的复印件"

调用函数时发生的事,可以用一句话说清:

函数收到的是实参引用的副本——一个新标签,贴到同一个对象上。

def append_one(lst):     # lst 是新标签,和 b 贴同一个列表

    lst.append(1)        # 钻进对象改内容 → b 看得见


def add_one(x):          # x 是新标签,和 a 贴同一个 10

    x = x + 1            # 撕下 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. 经典陷阱:默认参数为什么"记仇"

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))就安全——反正没人能改它。


6. 同类陷阱:+= 不总是等于 +

+= 对可变类型是"就地修改"(调用 __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

同样一个 +=,行为分裂成两半。面试官爱问这个,不是爱抠字眼,是在考察你有没有"可变/不可变"这根弦。

7. 一句话总结

  • 变量是贴在对象上的标签,赋值是贴新标签,不是复制内容;

  • 传参传的是引用的副本——新标签贴老对象;

  • 能不能影响外部,只看一件事:改内容(可变)还是换标签(重新绑定);

  • 所以 Python 的准确叫法是 传对象引用(pass by object reference)。

把这四句话想明白,比背"值传递/引用传递"任何一个结论都管用。

阅读原文:点击这里​


该文章在 2026/10/14 15:01:32 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-1  粤公网安备44030602007207号