← 返回题目列表

Python 中变量、对象和引用是什么关系?

高频 简单 第 5 / 22 题 更新于 2026/07/25
Python变量对象引用

简化版

Python 里的变量不是盒子,而是名字;对象才是真正存放数据的实体,变量只是绑定到对象的引用。执行 b = a 时,不会复制对象,只是让 b 这个名字也指向 a 指向的同一个对象。

详细版

Python 的赋值语义可以理解为“名字绑定对象”:

a = [1, 2, 3]
b = a
b.append(4)
print(a)  # [1, 2, 3, 4]

这里 ab 指向同一个列表对象,所以通过 b 修改列表,a 看到的也是修改后的结果。

但如果重新绑定变量:

a = [1, 2, 3]
b = a
b = [9, 9, 9]
print(a)  # [1, 2, 3]

b = [9, 9, 9] 是让 b 指向新列表,不会影响 a 原来指向的列表。

面试里要讲清三个词:

  • 对象:内存里的数据实体,例如列表、字符串、整数、字典。
  • 变量:对象的名字,不是对象本身。
  • 引用:变量和对象之间的指向关系。

这套模型能解释很多 Python 易错点:可变对象共享、默认参数坑、浅拷贝、函数传参效果等。

完整版教学

一、变量不是盒子,而是名字绑定

很多初学者会把变量想成盒子:

a 盒子里装着 [1, 2, 3]

这个模型在 Python 里容易误导。更准确的模型是:

[1, 2, 3] 是对象
a 是贴在对象上的标签

赋值语句:

a = [1, 2, 3]

意思不是“把列表放进 a 这个盒子”,而是“创建一个列表对象,然后让名字 a 绑定到它”。

a = [1, 2, 3]

名字表: a ──────> 列表对象 [1, 2, 3]

这个模型能解释 Python 里大量现象:赋值不复制对象,函数传参不复制对象,浅拷贝只复制外层容器,可变对象原地修改会被多个名字看到。面试时只要先把“名字、引用、对象”三层拆开,后面很多追问都顺了。

二、多个变量可以指向同一个对象

看这个例子:

a = {"name": "Tom"}
b = a
b["age"] = 18

print(a)  # {'name': 'Tom', 'age': 18}

ab 不是两个字典,它们是同一个字典对象的两个名字。只要对象本身被修改,所有指向它的变量都会看到变化。

可以用 id() 粗略观察对象身份:

print(id(a) == id(b))  # True

id() 返回对象身份标识。在 CPython 中常表现为内存地址相关的值,但面试里说“对象身份标识”更准确,不要把它绝对等同于语言层面的内存地址。

数字上可以这样理解:一开始有 1 个字典对象、2 个名字指向它;b["age"] = 18 没有创建第二个字典,只是在原字典里多放了一个键值对。因此从 ab 看过去,看到的都是同一个被修改后的对象。

三、修改对象和重新绑定变量是两回事

这两个操作特别容易混:

a = [1, 2, 3]
b = a

b.append(4)     # 修改对象
b = [9, 9, 9]   # 重新绑定变量

append 是对原列表对象做原地修改,所以 a 能看到变化;b = [9, 9, 9] 是让 b 指向新对象,不会动原列表。

可以画成:

修改前:a ─┐
          ├──> [1, 2, 3]
      b ─┘

b 重新绑定后:
a ───> [1, 2, 3]
b ───> [9, 9, 9]

对可变对象,appendextenddict.__setitem__ 这类操作会改变对象内容;赋值语句则改变名字绑定。这个区别不是语法小细节,而是判断副作用边界的核心。

代码做了什么a 是否受影响
b = a让 b 绑定同一对象共享对象
b.append(4)修改共享列表对象
b = [9, 9, 9]b 重新绑定新对象
a = a + [4]创建新列表并让 a 指向它b 不跟着变

四、函数传参也是名字绑定

Python 函数传参常被描述成“传对象引用”。更准确地说,是把实参对象绑定到形参名字上。

def add_item(items):
    items.append("new")

data = []
add_item(data)
print(data)  # ['new']

函数内部的 items 和外部的 data 指向同一个列表对象,所以原地修改会影响外部。

但重新绑定形参不会影响外部变量:

def reset(items):
    items = []

data = [1, 2]
reset(data)
print(data)  # [1, 2]

items = [] 只是让局部名字 items 指向新列表,不会改变 data 的绑定。

流程图如下:

调用 add_item(data)

外部 data ─┐
           ├──> []
形参 items ┘

items.append("new") 修改同一个列表,所以 data 能看到

这也解释了为什么 Python 里常说“既不是传统值传递,也不是 C++ 那种引用传递”,更稳的说法是对象共享调用或赋值绑定调用:形参是新名字,绑定到实参对象;修改对象可见,重新绑定形参不可见。

五、可变对象和不可变对象的表现差异

同样是 +=,可变对象和不可变对象可能表现不同:

a = [1, 2]
b = a
a += [3]
print(b)  # [1, 2, 3]

x = "ab"
y = x
x += "c"
print(y)  # "ab"

列表的 += 通常走原地扩展,ab 共享的列表对象被修改;字符串不可变,x += "c" 创建新字符串并让 x 绑定过去,y 仍指向老字符串。这里不是变量规则变了,而是对象类型的可变性不同。

六、这套模型能解释哪些高频问题

理解“名字绑定对象”之后,很多题都会变简单:

  • a is b 判断两个名字是否指向同一对象。
  • a == b 判断两个对象的值是否相等。
  • 可变对象原地修改会影响共享引用。
  • copy.copy() 只复制外层容器,内部对象仍可能共享。
  • 默认参数在函数定义时创建,所以可变默认值会被多次调用共享。
  • 不可变对象不能原地修改,所谓“修改字符串”本质是创建新字符串并重新绑定变量。

记忆钩子:赋值语句改的是“名字指向谁”,方法调用常常改的是“对象里面有什么”;先分清这两件事,再判断外部能不能看到变化。

七、常见误区与追问

  • 误区:b = a 会复制一份对象。 它只增加一个名字绑定,除非显式 copy,否则还是同一个对象。
  • 误区:函数里给形参重新赋值能改变外部变量。 形参是局部名字,重新绑定只影响局部名字,不影响调用方名字。
  • 误区:可变对象传参和不可变对象传参规则不同。 传参都是名字绑定到对象,差异来自对象能否原地修改。
  • 追问:id() 是不是内存地址? 语言层面应说对象身份标识;CPython 中常与内存地址相关,但不应泛化到所有实现。
  • 追问:为什么列表 += 会影响别名而字符串不会? 列表可原地扩展,字符串不可变只能创建新对象再绑定。
  • 追问:如何避免共享引用带来的联动修改? 需要显式拷贝,嵌套可变结构还要考虑深拷贝或按需重建。

八、加强记忆

把 Python 赋值记成“名字贴标签”:对象是内存中的实体,变量是贴在对象上的名字,引用是名字到对象的指向关系。b = a 是多贴一个标签,不是复制对象;原地修改会被所有标签看到,重新绑定只把当前标签贴到别的对象上。这个模型能贯穿 is/==、可变性、函数传参、默认参数和拷贝问题。