Python 变量与内存:初学者必备的深入解析

摘要

  • Python中的变量是指向内存中对象的名称,而不是存储值的盒子。
  • CPython使用引用计数 + 循环检测垃圾收集器
  • 可变对象与不可变对象会影响重新赋值和函数调用的行为。
  • 使用==进行值相等比较,使用is进行身份检查。
  • CPython通过驻留优化小整数和字符串,并在编译时执行常量折叠

1) 变量是名称,而不是盒子

大多数初学者将变量想象成“容器”,用来存放值。在Python中,变量实际上是一个名称,它引用内存中的一个对象

思考:名称 → 对象(类型,值,引用计数)

2) Python中的内存引用

给变量赋值将一个名称绑定到一个对象:

x = 10
y = x   # y 指向与 x 相同的对象

print(x, y)       # 10 10
print(id(x), id(y))  # 相同的标识
重新绑定创建一个新链接:
x = [1, 2, 3]
y = x
y.append(4)
print(x)  # [1, 2, 3, 4]

为什么这很重要: 通过一个名称修改对象可能会影响引用同一对象的其他名称。

3) CPython中的引用计数

CPython跟踪指向对象的引用数量。当计数达到0时,对象将被释放。

import sys

a = [1, 2, 3]
print(sys.getrefcount(a))  # 由于函数中的临时引用 +1
b = a
print(sys.getrefcount(a))  # 增加
del b
print(sys.getrefcount(a))  # 回落

注意:PyPy 和其他 Python 实现使用不同的垃圾回收策略。

4) 垃圾回收与循环

仅靠引用计数无法回收循环引用(A → B → A)。Python 的 循环收集器 处理这个问题:

import gc

class Node:
def __init__(self): self.ref = None

a, b = Node(), Node()
a.ref, b.ref = b, a
del a, b
collected = gc.collect()
print("已收集:", collected)

提示: 避免在循环对象中使用 __del__; 这可能会延迟垃圾回收。


5) 动态类型与静态类型

Python 是 动态类型(名称可以指向任何类型)和 强类型(没有隐式强制转换):

x = 10
x = "ten"    # 正确
# x + 5      # 类型错误

类型提示帮助人类和集成开发环境,而不是解释器。

6) 变量重新赋值

重新赋值改变的是绑定,而不是对象:

x = 10
old_id = id(x)
x = 20
print(old_id == id(x))  # False

对于可变对象,您可以进行变更或重新绑定:

nums = [1, 2]
nums = nums + [3]  # 新列表
nums.append(4)     # 同一列表

7) 可变性解释

  • 不可变: int, float, bool, str, tuple, frozenset
  • 可变: list, dict, set
s = "hi"
s += "!"       # 新字符串对象
d = {"a":1}
d["b"] = 2     # 相同的字典对象

元组可以包含可变对象:

t = ([], 42)
t[0].append("boom")
print(t)  # (['boom'], 42)

8) 函数、参数与可变性

Python 将 对象引用 传递给函数:


def mutate(lst): lst.append(99)
def rebind(lst): lst = lst + [99]

a = [1, 2, 3]
mutate(a)   # [1, 2, 3, 99]
rebind(a)   # 仍然是 [1, 2, 3, 99]

避免使用可变的默认参数:

def good(x, bucket=None):
    if bucket is None:
        bucket = []
    bucket.append(x)
    return bucket

9) 共享引用的注意事项

两个名称引用同一个可变对象时,共享更改:

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

小心复制:

import copy
x = [[1],[2]]
y = copy.copy(x)      # 浅拷贝
z = copy.deepcopy(x)  # 深拷贝

10) 相等性: ==is

    • == → 值相等
    • is → 身份(同一对象)
x = [1, 2, 3]; y = [1, 2, 3]
print(x == y, x is y)  # True, False
a = None; b = None
print(a is b)  # True

使用 is None 进行哨兵检查。

11) 一切都是对象

名称存在于 命名空间(类似字典):

x = 42
def f(): pass

print(type(x), type(f))
print(globals().keys())

LEGB 规则: 局部 → 包含 → 全局 → 内置

12) CPython 优化:整数驻留

小整数通常是共享的:

a = 256; b = 256
print(a is b)  # True

a = 257; b = 257
print(a is b)  # 可能为 False

13) CPython 优化:字符串驻留

编译时的字面量和标识符可以被驻留:

import sys

u = "".join(["status","_ok"])
v = "".join(["status","_ok"])
print(u is v)  # False
u = sys.intern(u); v = sys.intern(v)
print(u is v)  # True

14) 常量折叠与窥视优化

Python 在编译时预计算简单表达式:

x = 2*10     # 折叠为 20
y = ("a"+"b") # 折叠为 "ab"
import dis
def demo(): return 2*10
dis.dis(demo)

大O符号比窥视孔优化更重要。


快速检查清单

  • [ ] 名称指向对象,而不是值。
  • [ ] == 用于值比较,is 用于身份比较。
  • [ ] 可变对象与不可变对象。
  • [ ] 避免使用可变默认值。
  • [ ] 小心复制(copy()deepcopy())。
  • [ ] 引用计数 + 垃圾回收用于处理循环引用。
  • [ ] 字符串驻留是一个优化细节。
  • [ ] 清晰性 > 微优化。

总结

黄金法则:Python 的名称指向对象。一旦你内化了这一点,变更性、垃圾回收、相等性和优化行为都将变得清晰明了。

更多