基础类型与内存模型
可变 vs 不可变类型
| 不可变 (Immutable) | 可变 (Mutable) |
|---|---|
| int, float, bool | list |
| str | dict |
| tuple | set |
| frozenset | bytearray |
| bytes | 自定义类(默认) |
为什么 str 不可变?
- 可以安全地作为 dict 的 key(hash 值不变)
- 线程安全
- Python 会对短字符串做**字符串驻留(interning)**优化
a = "hello"
b = "hello"
print(a is b) # True(驻留优化,同一对象)
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4] -- 同一引用!内存管理
引用计数(Reference Counting)
每个对象维护一个引用计数器,当计数归零时立即回收。
import sys
a = []
print(sys.getrefcount(a)) # 2(a 本身 + getrefcount 参数)
b = a
print(sys.getrefcount(a)) # 3
del b
print(sys.getrefcount(a)) # 2缺点:无法处理循环引用
a = []
b = []
a.append(b)
b.append(a)
del a, b # 引用计数不为 0,但对象无法访问 → 内存泄漏分代垃圾回收(Generational GC)
Python 用分代 GC 解决循环引用问题:
- 三代:第 0 代(新对象)、第 1 代、第 2 代
- 新对象在第 0 代,存活越久晋升到越高代
- 高代 GC 触发频率更低(长期存活对象很少成为垃圾)
gc模块可手动控制
import gc
gc.collect() # 手动触发 GC
gc.disable() # 关闭 GC(性能敏感场景)
gc.get_threshold() # (700, 10, 10) 默认触发阈值深拷贝 vs 浅拷贝
import copy
original = [[1, 2], [3, 4]]
# 浅拷贝:复制容器,但元素仍是引用
shallow = copy.copy(original)
shallow[0].append(99)
print(original) # [[1, 2, 99], [3, 4]] ← 被影响了!
# 深拷贝:递归复制所有对象
deep = copy.deepcopy(original)
deep[0].append(99)
print(original) # [[1, 2, 99], [3, 4]] ← 不受影响浅拷贝的几种方式:
lst = [1, 2, 3]
lst2 = lst[:] # 切片
lst3 = list(lst) # list()
lst4 = lst.copy() # .copy()
import copy
lst5 = copy.copy(lst) # copy.copy()赋值、is、== 的区别
=:绑定变量名到对象(不是复制)is:判断同一对象(id 相同)==:判断值相等(调用__eq__)
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True(值相等)
print(a is b) # False(不同对象)
# 小整数缓存(-5 ~ 256)
x = 256
y = 256
print(x is y) # True(CPython 缓存优化)
x = 257
y = 257
print(x is y) # False(超出缓存范围)常见面试题
Q: a = b = [] 和 a, b = [], [] 的区别?
a = b = [] # a 和 b 指向同一个列表!
a.append(1)
print(b) # [1]
a, b = [], [] # a 和 b 是两个不同的空列表
a.append(1)
print(b) # []Q: 为什么不要用可变对象作默认参数?
# 错误写法:默认参数在函数定义时只创建一次!
def append_to(element, to=[]):
to.append(element)
return to
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2] ← 不是 [2]!
# 正确写法
def append_to(element, to=None):
if to is None:
to = []
to.append(element)
return to