← 返回 Day 50 正文

Day 50 · 图解

Day 050 — 槽(slots)图解

内存布局对比

graph TB subgraph "普通类实例 (PointNormal)" A1["PyObject_HEAD (16 bytes)"] B1["__dict__ pointer"] C1["dict object (~232 bytes)"] D1["'x': 1.0"] E1["'y': 2.0"] F1["'z': 3.0"] A1 --> B1 B1 --> C1 C1 --> D1 C1 --> E1 C1 --> F1 end subgraph "__slots__ 类实例 (PointSlots)" A2["PyObject_HEAD (16 bytes)"] B2["slot[0]: x (8 bytes)"] C2["slot[1]: y (8 bytes)"] D2["slot[2]: z (8 bytes)"] A2 --> B2 A2 --> C2 A2 --> D2 end
graph TB
    subgraph "普通类实例 (PointNormal)"
        A1["PyObject_HEAD (16 bytes)"]
        B1["__dict__ pointer"]
        C1["dict object (~232 bytes)"]
        D1["'x': 1.0"]
        E1["'y': 2.0"]
        F1["'z': 3.0"]
        A1 --> B1
        B1 --> C1
        C1 --> D1
        C1 --> E1
        C1 --> F1
    end

    subgraph "__slots__ 类实例 (PointSlots)"
        A2["PyObject_HEAD (16 bytes)"]
        B2["slot[0]: x (8 bytes)"]
        C2["slot[1]: y (8 bytes)"]
        D2["slot[2]: z (8 bytes)"]
        A2 --> B2
        A2 --> C2
        A2 --> D2
    end

内存节省对比

┌─────────────────────────────────────────────┐
│        100,000 个对象内存对比                 │
├─────────────────────────────────────────────┤
│                                             │
│  普通类:                                     │
│  ├─ 对象本身: ~64 bytes × 100,000 = 6.4 MB  │
│  ├─ __dict__: ~232 bytes × 100,000 = 23.2 MB│
│  └─ 总计: ~29.6 MB                          │
│                                             │
│  __slots__ 类:                               │
│  ├─ 对象本身: ~32 bytes × 100,000 = 3.2 MB  │
│  ├─ 无 __dict__                              │
│  └─ 总计: ~3.2 MB                           │
│                                             │
│  节省: ~26.4 MB (89.2%)                     │
│                                             │
└─────────────────────────────────────────────┘

属性访问性能

┌─────────────────────────────────────────────┐
│         属性访问性能对比                      │
├───────────────────────┬─────────────────────┤
│  普通类                │  __slots__ 类        │
├───────────────────────┼─────────────────────┤
│  obj.x → dict lookup  │  obj.x → slot load  │
│  ~100 ns              │  ~90 ns             │
├───────────────────────┼─────────────────────┤
│  obj.x = val          │  obj.x = val        │
│  ~120 ns              │  ~95 ns             │
├───────────────────────┼─────────────────────┤
│  支持动态属性          │  不支持动态属性       │
│  有 __dict__           │  无 __dict__         │
└───────────────────────┴─────────────────────┘

继承规则

┌─────────────────────────────────────────────┐
│            __slots__ 继承规则                 │
├─────────────────────────────────────────────┤
│                                             │
│  class Base:                                │
│      __slots__ = ('x', 'y')                 │
│                                             │
│  class Child(Base):                         │
│      __slots__ = ('z',)  ✅ 声明新槽位       │
│      # x, y 继承自 Base                     │
│      # z 是自己的新槽位                       │
│                                             │
│  class BadChild(Base):                      │
│      # ❌ 忘记声明 __slots__                 │
│      # 会恢复 __dict__,失去优化效果          │
│                                             │
└─────────────────────────────────────────────┘

常见陷阱

flowchart TD A["使用 __slots__"] --> B{需要弱引用?} B -->|是| C["添加 '__weakref__'"] B -->|否| D{需要 pickle?} D -->|是| E["实现 __getstate__/__setstate__"] D -->|否| F{子类也需要?} F -->|是| G["子类声明 __slots__"] F -->|否| H["⚠️ 会恢复 __dict__"]
flowchart TD
    A["使用 __slots__"] --> B{需要弱引用?}
    B -->|是| C["添加 '__weakref__'"]
    B -->|否| D{需要 pickle?}
    D -->|是| E["实现 __getstate__/__setstate__"]
    D -->|否| F{子类也需要?}
    F -->|是| G["子类声明 __slots__"]
    F -->|否| H["⚠️ 会恢复 __dict__"]