🧩 第五章 理解内存结构:程序员的基本素养
📄 一句话概括

y = x 只复制地址——id 相同、引用计数 3、改 y.w 时 x.w 一起变成 99。

📝 本节和前面几节的关系

5.8 说实例在堆上,这一节说变量里到底存了什么。

一、存的是地址

变量里存的不是物体本身,是它的地址(引用、指针)。 所以 x = Rect(6, 4) 干了两件事:在堆上开一块,把这块的地址放进 x。 这就是 5.1 那四步里【找变量】那一步的全部内容。

二、实测

y = x 之后 id(x) == id(y) 是 True——两个名字,一个物体。 x 的引用计数是 3(x、y、getrefcount 自己那一份)。 y.w = 99 之后 x.w 也是 99——因为改的是同一块。 名字多了不算复制,只是多了一个指向它的箭头。

三、赋值不是复制

赋值只搬地址,代价是一次指针拷贝,跟对象多大无关。 所以传参也是传地址:把对象传进函数,函数里改字段,外面看得见。 这是效率的来源,也是 bug 的来源——你以为传了个副本,其实传了把钥匙。

四、这引出 5.10

想真复制怎么办?得显式动手:copy.copy 或 copy.deepcopy。 而这两个还不一样:浅的只复制外层,深的连内层一起。 所以 5.10 讲的是【复制】这件事本身有多容易踩坑。

五、动手 Demo:在变量中存储实例的指针

让两个变量指向同一个实例,验证它们其实是同一个物体。 再改一边,看另一边会不会跟着变。

x = Rect(6, 4)
y = x
print(id(x) == id(y))      # True:同一个物体
print(sys.getrefcount(x))  # 3:x、y、传参
y.w = 99
print(x.w)                 # 99:一起变了

实跑输出:

--------------------------------------------------------------------
在变量中存储实例的指针
--------------------------------------------------------------------

  变量里存的**不是对象本身,是它的地址**。所以赋值不复制对象:

      x = Rect(6, 4)
      y = x
      id(x) 与 id(y) 相同吗      True
      x 的引用计数                3
      y.w = 99 之后 x.w           99  ← 一起变了

  写法                 复制了什么                   改一边另一边
  ---------------------------------------------------------------
  y = x                只复制了地址                 跟着变
  copy.copy(x)         对象本体(内层还是共用)     内层会变
  copy.deepcopy(x)     对象连内层一起               互不影响

  **`y = x` 之后,两个名字指向同一块堆内存**——改哪个都是改同一处。
  **想真正多一份,必须显式复制;而复制还有【浅】和【深】两种。**
  **这就是 5.10 要专门讲的那个坑。**

实测:id 相同、引用计数 3、改 y.w 之后 x.w 也变成 99。 赋值只搬地址,不复制物体——这既是效率的来源,也是【以为传了副本其实传了钥匙】的来源。

30 秒实验

  1. 把对象传进一个函数,在函数里改字段,看外面变不变。
  2. 用 id() 对比【重新赋值】和【复制】两种写法。

常见误区

  1. 以为 y = x 复制了对象。只复制地址。
  2. 以为传参是复制。传的也是地址。
自测三问(点开对答案) 1. 变量里存的是什么? —— 存的是对象在堆上的地址(引用/指针),不是对象本身。 2. y = x 之后实测是什么? —— id 相同、引用计数 3、改 y.w 后 x.w 一起变成 99——两个名字一个物体。 3. 为什么这既是效率也是 bug 来源? —— 赋值只搬地址,代价与对象大小无关,所以快;但你以为传了副本,其实传了把钥匙,改一处影响一片。

相关笔记