Python变量是引用还是值拷贝

Hazel
Hazel
发布时间:2026-10-25 | 4人浏览过
原创

很多初学 Python 的开发者在编写代码时,常会遇到看似矛盾的现象:把一个整数变量赋给另一个变量并修改后者,前者不受任何影响,宛如“值拷贝”;但如果对一个列表执行相同操作,修改后者却会导致前者同步改变,宛如“传引用”。针对这个问题,明确的核心结论是:Python 既不是传统语言意义上的值拷贝,也不是纯粹的变量引用,而是统一采用“对象引用的名字绑定”(Call by Object Reference,亦称 Pass by Sharing)。在 Python 中,变量从来不直接存储数据值,它只是贴在堆内存对象上的一个“名字标签”;赋值操作只是改变标签的绑定指向,而后续产生的不同行为,完全取决于所指向的对象本身是不可变对象还是可变对象。

一、核心模型:变量是便利贴标签,数据是堆中的实体

在 C、C++ 等编译型语言中,定义一个变量 int a = 10; 意味着在内存栈中开辟了一块固定大小的存储空间,变量名 a 直接代表这块空间,空间内写入数字 10。执行 int b = a; 时,系统开辟新的存储空间并将 10 完整复制一份存入,这是典型的值拷贝机制。

而在 Python 的运行模型中,所有数据(数值、字符串、列表、函数乃至类本身)都是在堆内存中独立分配的对象。执行 a = 10 时,Python 先在内存中创建一个整型对象 10,然后把变量名 a 像便利贴一样贴到该对象上,建立绑定关系。执行 b = a 时,Python 并没有复制整型对象 10,也没有创建变量别名,而是让变量名 b 也贴到同一个整型对象 10 上。

Python变量是引用还是值拷贝
图 1:传统值存储模型与 Python 对象名字绑定模型的内存结构对比

我们可以使用 Python 内置函数 id() 来直观验证这一点。id() 返回对象在内存中的唯一身份标识(在 CPython 实现中对应对象的内存虚拟地址):

a = 1000
b = a
print(id(a))  # 示例输出: 140321287123280
print(id(b))  # 示例输出: 140321287123280
print(a is b) # 示例输出: True

输出完全一致的 id 以及 a is b == True 证明了此时 a 和 b 共享同一个内存对象,没有任何内存复制发生。

二、不可变对象:为什么会产生“值拷贝”的错觉?

既然 b = a 共享了引用,为什么接下来执行 b = b + 1 后,a 却完全不变呢?原因在于:Python 中的数值(int、float)、字符串(str)、元组(tuple)、布尔值(bool)和冻结集合(frozenset)属于不可变对象(Immutable Objects)。

不可变对象一旦在内存中生成,其内部数据绝对不可被修改。任何试图修改它的操作(包括 +=、+、字符串拼接等),本质上都是由 Python 解释器在内存中开辟新的地址创建全新对象,并将左侧的变量名重新绑定到这个新对象上。原有的对象丝毫不受影响。

Python变量是引用还是值拷贝
图 2:不可变对象修改时触发的内存重分配与变量标签重新绑定过程

代码层面验证:

a = 1000
b = a
print(id(a), id(b))  # 两者 id 完全相同

b += 1
print(id(a), id(b))  # b 的 id 已发生改变,指向全新对象
print(a)             # 输出: 1000(不受影响)
print(b)             # 输出: 1001

因此,表面看起来如同“修改 b 没有影响 a 的值拷贝行为”,本质上是不可变对象强制引发的“重新赋值绑定”。

三、可变对象:就地修改与别名效应

相比之下,列表(list)、字典(dict)、集合(set)以及大多数自定义类的实例属于可变对象(Mutable Objects)。可变对象支持在当前内存块内直接增删改数据,这一过程称为“就地修改”(In-place Mutation)。

当两个变量绑定了同一个可变对象时,通过任意一个变量调用就地修改方法(如 append()、pop()、extend()),该对象自身的内部状态就会改变。由于并没有发生重新赋值,另一个变量依然贴在这个对象上,因此会立即“感知”到修改:

Python变量是引用还是值拷贝
图 3:对可变对象执行“就地修改”与“重新赋值”的本质区别

理解这一点至关重要:即使对象是可变的,等号赋值操作 = 永远是创建或改变绑定,绝不等于就地修改。请仔细辨析以下两段代码的差异:

# 场景 1:就地修改(In-place)
list_a = [1, 2]
list_b = list_a
list_b.append(3)
print(list_a)  # 输出: [1, 2, 3],两者联动,因为修改的是同一个对象

# 场景 2:重新赋值(Re-binding)
list_a = [1, 2]
list_b = list_a
list_b = [1, 2, 3]  # 右侧方括号生成了全新的列表对象
print(list_a)  # 输出: [1, 2],list_a 不受任何影响!

四、函数传参:传值还是传引用?

这个机制在函数调用中体现得最为明显。面试或讨论中常问:“Python 函数传参是传值还是传引用?”

官方术语称之为 Call by Object Reference 或 Call by Sharing。其规则极为简明:函数传参本质上就是一次隐式的赋值操作(形参 = 实参)。形参获得了实参所指向对象的引用副本。

  • 在函数内对形参重新赋值:仅改变形参在局部作用域内的指向,外部实参绑定关系完全不受影响。
  • 在函数内就地修改形参所指向的可变对象:由于形参与外部实参指向同一底层对象,外部实参的内容会随之改变。
def modify_data(num, items):
    num = num + 10       # 重新绑定:不影响外部整数
    items.append("NEW")  # 就地修改:直接影响外部列表

val = 5
data = [1, 2]
modify_data(val, data)

print(val)   # 输出: 5 (整数未变)
print(data)  # 输出: [1, 2, 'NEW'] (列表已被就地修改)

经典陷阱:函数的默认参数如果是可变对象(如 def func(x=[])),该默认对象在函数定义时仅被创建一次并在多次调用间持久共享。如果后续调用中就地修改它,会导致意想不到的历史数据残留。正规做法是将默认参数设为 None,在函数体内部执行 if x is None: x = []。

五、需要数据独立时:浅拷贝与深拷贝

既然简单的等号赋值无法复制对象本身,当我们确实需要一份相互隔离的数据副本时,应该如何操作?Python 提供了浅拷贝(Shallow Copy)与深拷贝(Deep Copy)两种机制。

1. 浅拷贝(Shallow Copy)

浅拷贝会创建一个全新的外层容器对象,但容器内部所包含的子元素依然引用原对象中子元素的内存地址。常用实现方式包括:

  • 列表切片操作:new_list = old_list[:]
  • 类型构造函数:new_list = list(old_list) 或 new_dict = dict(old_dict)
  • 容器自带拷贝方法:new_list = old_list.copy()
  • 标准库 copy 模块:import copy; new_obj = copy.copy(old_obj)

2. 深拷贝(Deep Copy)

当容器内存在多层嵌套的可变结构(例如列表中包含字典、列表中包含列表)时,浅拷贝仅能隔离第一层。修改内层嵌套可变对象依然会导致外层联动。此时必须使用深拷贝:

import copy
deep_copied = copy.deepcopy(nested_obj)

copy.deepcopy() 会递归遍历所有子对象,将每一层容器及其包含的可变内容完整克隆一份,彻底阻断任何引用共享。

Python变量是引用还是值拷贝
图 4:Python 中赋值引用、浅拷贝与深拷贝的核心特性与选型对比

六、总结与判断清单

为了在实际编码中不再困惑,可以归纳以下三条铁律:

机制类型 典型数据类型 赋值 b = a 后的表现 如何实现真正的独立拷贝
不可变对象 int, float, str, tuple, bool, frozenset 两变量共享同一引用。修改 b 必定生成新对象,a 永远保持不变。 无需显式拷贝,直接赋值即可保证数据安全。
单层可变对象 单层 list, dict, set 两变量共享同一引用。就地修改其中一个,另一个同步改变。 使用浅拷贝:a.copy() 或 a[:]。
嵌套复合对象 包含子列表/字典的嵌套结构、自定义对象树 两变量共享同一引用。浅拷贝仅隔离第一层,内层修改依然会跨对象联动。 必须使用深拷贝:copy.deepcopy(a)。

总结而言,回答“Python变量是引用还是值拷贝”这一问题时,最准确的阐述是:Python 的变量模型既非传统意义上的物理值拷贝,也不是直接操作变量名地址的纯引用,而是“对象引用的名字绑定”。赋值操作始终传递对象引用,而代码展现出来的差异,完全由操作对象本身是否支持就地修改所决定。

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表