Python 类中的 __slots__ 节省内存

FreeGuideOnline 40阅读 2026-07-06

python class Point: def init(self, x, y): self.x = x self.y = y

p = Point(1.0, 2.0) print(p.dict) # {'x': 1.0, 'y': 2.0}


每个 `Point` 实例都会有一个独立的字典 `__dict__`,用于存放 `x` 和 `y`。定义一千个 `Point` 对象,就会分配一千个字典。

我们可以用 `sys.getsizeof()` 来观察对象的大致大小:

```python
import sys

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1.0, 2.0)
print(sys.getsizeof(p))        # 基础对象大小
print(sys.getsizeof(p.__dict__))  # 字典大小

实际内存消耗远不止 getsizeof 显示的数字,因为它不计算字典内部键值对引用的对象本身。但可以看到每个实例都会额外持有一个不小的字典结构。

使用 __slots__ 节省内存

基本用法

在类中定义 __slots__ 为一个包含属性名字符串的可迭代对象(一般用元组)。这告诉 Python:这个类的实例只允许有这些属性,且不再创建 __dict__

class PointWithSlots:
    __slots__ = ('x', 'y')

    def __init__(self, x, y):
        self.x = x
        self.y = y

p2 = PointWithSlots(1.0, 2.0)
print(p2.x, p2.y)   # 正常访问
# print(p2.__dict__)  # AttributeError: 'PointWithSlots' object has no attribute '__dict__'

尝试给实例添加未声明的属性会直接抛出 AttributeError

p2.z = 3.0    # AttributeError

这种显示声明属性的方式,同时带来了防止属性拼写错误的好处。

内存对比测试

我们可以用更精确的工具对比内存占用:

from pympler import asizeof

class PointNormal:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class PointSlots:
    __slots__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

normal_objects = [PointNormal(i, i) for i in range(100000)]
slots_objects = [PointSlots(i, i) for i in range(100000)]

print("普通对象总大小:", asizeof.asizeof(normal_objects))
print("__slots__ 对象总大小:", asizeof.asizeof(slots_objects))

测试结果会显示,使用 __slots__ 后总内存占用可能减少 40% ~ 60%。对于单个实例,节省的比例甚至更高,因为对象的固定开销中字典占了一大块。当实例数量庞大时,节省的内存非常可观。

__slots__ 的高级特性与注意事项

1. __slots__ 与继承

子类若没有定义 __slots__,会重新获得 __dict__,并且可以任意添加属性。

class Shape:
    __slots__ = ('name',)

class Circle(Shape):
    pass  # 没有定义 __slots__

c = Circle()
c.name = "circle"
c.radius = 5   # 可以添加,因为有了 __dict__

如果希望子类也保持无字典的特性,子类必须也定义 __slots__,其中包含父类的插槽和自身新增的插槽。

class Circle(Shape):
    __slots__ = ('radius',)   # 包含了父类的 'name' 吗?不,只需要写子类新增的
    def __init__(self, name, radius):
        self.name = name       # name 来自父类 __slots__
        self.radius = radius

关键规则:子类 __slots__ 的值会自动与父类的 __slots__ 合并。所以子类只需声明自己独有的属性名即可。

如果父类没有 __slots__,子类就算定义了 __slots__ 也无法阻止 __dict__ 的创建。因为父类实例布局依赖于字典,子类必须兼容。

2. 弱引用和 __weakref__ 插槽

默认情况下,定义了 __slots__ 的类不允许对实例进行弱引用(weakref)。如果你需要使用弱引用,必须显式把 __weakref__ 添加到 __slots__ 中:

import weakref

class Node:
    __slots__ = ('value', '__weakref__')
    def __init__(self, value):
        self.value = value

n = Node(10)
ref = weakref.ref(n)
print(ref())   # 打印 Node 对象