摘要
- 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 的名称指向对象。一旦你内化了这一点,变更性、垃圾回收、相等性和优化行为都将变得清晰明了。