在大屏或电脑上体验更佳——动画和操作区域更宽敞。
输入

变量与修改

赋值让名称指向对象,修改则改变已有对象。两个名称若指向同一个可变对象,都能看到对象的变化;给其中一个名称重新赋值,不会带走另一个名称的绑定。

可以把名称看成行李标签,对象看成行李箱。两个标签系在同一个箱子上,改变箱内物品会影响两个标签的持有者;把一个标签改系到另一个箱子,只影响这个标签。函数修改传入列表时,调用者也能看到变化,原因就在共享对象。

变量与修改如何工作

Python 变量是绑定到对象的名称。a = [1, 2] 创建列表对象,再让 a 指向它。名称不装着列表,而是提供访问它的路径。

b = a 不复制列表,只把 b 绑定到同一个对象。此时 a 和 b 是别名:两个名称,一个列表。通过任一名称修改列表,改变的都是同一个共享对象。

执行 b.append(3),列表从 [1, 2] 变成 [1, 2, 3],两条绑定箭头不动,因此通过 a 也能读到新内容。

执行 b = [9] 则创建新列表,只把 b 改绑过去。原列表不变,a 仍指向它。

  • 修改对象:对象内容变化,绑定保持。
  • 赋值绑定:名称的指向变化,已有对象不因重新绑定而改变。

仅看语法并不总能判断行为。列表可变,整数不可变。整数示例中的 b += 1 看似原地更新,但 Python 不能修改整数对象 7,只能得到表示 8 的另一个整数对象,再重新绑定 b。a 仍读到 7。

逐步演示

默认使用可变列表示例。

  1. 第一行创建符号对象 L1,内容为 [1, 2]。分配阶段尚无名称指向它。
  2. 同一行把 a 绑定到 L1,图中出现一个名称和一个对象。
  3. b = a 增加第二条绑定,没有创建第二个列表。状态是 a -> L1、b -> L1。
  4. b.append(3) 修改 L1,内容成为 [1, 2, 3],两条绑定都仍指向 L1。
  5. 求值 [9] 创建另一个列表 L2。此时赋值尚未完成,两个名称仍指向 L1。
  6. 赋值把 b 从 L1 改绑到 L2。a 留在 L1,其内容仍是 [1, 2, 3]。
  7. 最后的打印沿两条绑定读取,输出 [1, 2, 3] [9] False。
  8. 结束画面保留两个对象:a -> L1、b -> L2。

观察对象和箭头:修改时内容变、箭头不动;重新绑定时一条箭头移动、原对象内容不变。不要在脑中把这两种操作合并。

再切换到不可变整数示例。同样先建立别名,随后 b += 1 获得值 8 并改绑 b,因为表示 7 的对象不能被修改。

为什么 += 可能重新绑定

+= 不保证原地修改。内置列表使用它扩展同一个列表对象;内置整数使用它计算结果,再重新绑定名称。运算符相同,对象类型决定状态如何变化。

值相等的两个对象不一定是同一个对象。相等性比较值,身份比较是否到达同一个对象。

本课打印 a is b 是为了展示最终绑定关系。重新绑定 b 后,两个名称指向不同对象,所以结果为 False。普通 Python 代码比较值应使用 ==。运行时可能复用某些不可变对象,因此用 is 比较数字或字符串容易出错。

函数接收可变对象时也存在别名。函数向列表参数追加元素,调用者会看到变化,因为双方都能访问同一个对象;函数只给局部参数重新赋值,则不改变调用者的绑定。说明行为时,应明确改变的是对象还是绑定。

边界情况

  • 两个名称可以共享一个可变对象,通过任一名称修改,另一方都能看到。
  • 重新绑定最后一个可达名称后,对象可能变得不可达,随后被回收。回收与赋值是不同机制。
  • 整数、字符串、元组等不可变对象不能原地改变,看似更新的表达式可能计算新值并重新绑定名称。
  • 元组不可变,但可以包含可变列表。元组槽位不变,槽位指向的列表仍可修改。
  • a == b 比较值,a is b 比较对象身份。

常见错误

  • 把每个变量画成独占一个值的盒子。两个名称共享列表时,这个模型会失效,应画名称到对象的绑定。
  • 以为 b = a 复制了可变对象。需要独立状态时,应显式复制。
  • 以为 += 必然修改原对象。它的行为取决于对象类型和操作实现。
  • 用 is 比较普通值。它适合 None 等哨兵的身份判断,普通值通常用 ==。
  • 只说函数“改变了变量”。应指出是绑定移动,还是对象内容变化。

本课的简化

L1、L2、I7、I8 是教学标签,不是内存地址。实际运行时还管理分配、整数复用、引用计数或其他垃圾回收机制,以及具体对象布局。这些机制影响存储和生命周期,但不改变本课规则:名称引用对象,赋值改变绑定,修改作用于可变对象。