引用计数:Python 的主回收机制
每个对象都有"被引用次数"——计数到 0 立即销毁:
import sys
x = [1, 2, 3]
print(sys.getrefcount(x)) # 2(包括 getrefcount 自己的临时引用)
y = x # 多一个引用
print(sys.getrefcount(x)) # 3
del y # 减一
print(sys.getrefcount(x)) # 2
循环引用:引用计数搞不定
class Node:
def __init__(self):
self.next = None
a = Node()
b = Node()
a.next = b
b.next = a # 循环引用!
del a
del b
# 引用计数算上 a.next/b.next 永远不为 0 → 不会释放
gc 模块:处理循环引用
Python 的 垃圾回收器(gc) 定期扫描循环引用并打破:
import gc
gc.collect() # 立刻跑一次
gc.get_count() # 三代分别有多少待检查对象
gc.set_threshold(700, 10, 10)
gc.disable() # 关闭(除非你知道在做什么)
99% 不需要管 gc——它自动跑。
weakref:弱引用避免循环
import weakref
class Cache:
def __init__(self):
self._data = weakref.WeakValueDictionary()
def set(self, k, v):
self._data[k] = v
def get(self, k):
return self._data.get(k)
cache = Cache()
class Big:
pass
obj = Big()
cache.set("key", obj)
print(cache.get("key")) # <Big object>
del obj # obj 被销毁
print(cache.get("key")) # None 缓存自动清空
WeakValueDictionary 不会增加 value 的引用计数——key 还在但 value 消失。适合做缓存。
sys.getsizeof:单个对象多大
import sys
sys.getsizeof([]) # 56
sys.getsizeof([1, 2, 3]) # 88
sys.getsizeof("hello") # 54
sys.getsizeof("中文") # 64
注意:只算对象自身,不算它引用的对象。
big_list = [list(range(1000)) for _ in range(1000)]
sys.getsizeof(big_list) # 8056 (只算外层列表)
# 实际占用:每个内层 list ~8KB × 1000 = 8MB
算"递归大小":pympler
pip install pympler
from pympler import asizeof
asizeof.asizeof(big_list) # 算所有引用对象的总大小
排查内存泄漏:tracemalloc
import tracemalloc
tracemalloc.start()
# ... 跑你的代码 ...
snapshot = tracemalloc.take_snapshot()
top = snapshot.statistics("lineno")
for stat in top[:10]:
print(stat)
打印消耗内存最多的 10 行代码。
slots:减少类的内存
class WithoutSlots:
def __init__(self, x, y):
self.x = x
self.y = y
class WithSlots:
__slots__ = ("x", "y") # 固定属性集
def __init__(self, x, y):
self.x = x
self.y = y
import sys
sys.getsizeof(WithoutSlots(1, 2)) # ~48 + __dict__
sys.getsizeof(WithSlots(1, 2)) # ~32,无 __dict__
百万级实例时省下大量内存。
实战:常见的内存泄漏
- 全局缓存无限长大
CACHE = {} # 长时间运行后会爆
def get(k):
if k not in CACHE:
CACHE[k] = expensive(k)
return CACHE[k]
修复:用 functools.lru_cache(maxsize=...),或 WeakValueDictionary。
- 闭包持有大对象
def make_handler(big_data):
def handler(event):
return event in big_data # handler 持有 big_data
return handler
handler 存在一天,big_data 就跟着存一天。
- 循环引用 + del
定义了 __del__ 的对象在循环引用里永远不会被 gc 回收——避免在有循环引用的类上写 __del__,用 __exit__ / 上下文管理器代替。
下一篇讲并发概览:线程 / 进程 / 异步。