Files
coding-mentor-agent/kb/python-course-kb-practical-python/wiki/concepts/Python-slots.md
T

227 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
sources: [summaries/05_Object_model__00_Overview.md, summaries/02_Classes_encapsulation.md]
brief: Python __slots__ 用于限制实例属性集合,并可减少对象内存占用。
---
# Python __slots__
`__slots__` 是 Python 类中的一个特殊类属性,用于声明实例允许拥有的属性名称。它可以阻止对象动态添加未声明的属性,并让 Python 使用更紧凑的内部对象表示,从而减少内存占用。
该概念在 [[summaries/02_Classes_encapsulation]] 中作为 Python 封装机制的一部分出现,和 python encapsulation、python properties、python object memory 等主题密切相关。
## 基本含义
普通 Python 对象通常可以在运行时自由添加新属性:
```python
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
s = Stock('GOOG', 100, 490.10)
s.blah = 42 # 通常是允许的
```
这种灵活性是 Python 对象模型的一部分,但也可能带来问题:
- 拼写错误会意外创建新属性;
- 对象结构不够固定;
- 每个实例通常需要维护一个 `__dict__`,带来额外内存开销。
`__slots__` 可以改变这种行为。
## 用法
在类中定义 `__slots__`,列出允许的实例属性名:
```python
class Stock:
__slots__ = ('name', '_shares', 'price')
def __init__(self, name, shares, price):
self.name = name
self._shares = shares
self.price = price
```
这样,`Stock` 实例只能拥有 `name`、`_shares` 和 `price` 这些属性。
如果尝试设置未声明的属性,会抛出 `AttributeError`:
```python
s = Stock('GOOG', 100, 490.10)
s.name # 'GOOG'
s.blah = 42 # AttributeError
```
在 [[summaries/02_Classes_encapsulation]] 中,示例展示了类似行为:
```python
s.price = 385.15
s.prices = 410.2
# AttributeError: 'Stock' object has no attribute 'prices'
```
这里 `prices` 可能只是 `price` 的拼写错误。使用 `__slots__` 后,这类错误会更早暴露。
## 与封装的关系
`__slots__` 有时看起来像一种封装工具,因为它限制了对象可以拥有的属性集合。但需要注意:
- 它不是严格意义上的访问控制;
- 它不会让属性变成真正私有;
- 它主要限制“能添加哪些属性”,而不是“谁能访问这些属性”。
例如,即使 `_shares` 被写入 `__slots__`,外部代码仍然可以访问它:
```python
s._shares
```
这与 Python 的整体封装哲学一致:Python 更多依赖命名约定,而不是强制私有权限。以下划线开头的属性,如 `_shares`,表示内部实现细节,应由程序员自觉避免直接访问。相关内容见 python encapsulation。
## 与 property 的配合
`__slots__` 经常和 `property` 一起出现。例如,公开属性 `shares` 可以通过 property 管理,而真实数据存储在 `_shares` 中:
```python
class Stock:
__slots__ = ('name', '_shares', 'price')
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
@property
def shares(self):
return self._shares
@shares.setter
def shares(self, value):
if not isinstance(value, int):
raise TypeError('Expected int')
self._shares = value
```
这里需要注意:
- `shares` 是一个 property,不是实际存储数据的普通实例属性;
- 实际值保存在 `_shares` 中;
- 因此 `__slots__` 中应包含 `_shares`,而不是一定要包含 `shares`;
- 对 `self.shares = value` 的赋值会触发 setter;
- setter 再把验证后的值写入 `self._shares`。
这种组合体现了 Python 中常见的受管理属性模式,相关内容见 python properties 和 managed attributes。
## 对 __dict__ 的影响
普通 Python 实例通常有一个实例字典 `__dict__`,用于保存动态属性:
```python
s.__dict__
```
使用 `__slots__` 后,如果没有显式把 `__dict__` 加入 slots,实例通常不再拥有普通的 `__dict__`。这意味着属性不再通过实例字典动态存储,而是通过更固定、更紧凑的结构保存。
因此,在使用 `__slots__` 的类上访问:
```python
s.__dict__
```
通常会失败,或者显示该实例没有 `__dict__`。
这正是 `__slots__` 能减少内存占用的原因之一。相关主题见 python object memory。
## 主要用途
`__slots__` 的主要用途包括:
1. **限制属性集合**
防止外部或内部代码随意添加未声明属性。
2. **发现拼写错误**
例如把 `price` 错写成 `prices` 时,普通对象会创建新属性,而 slots 对象会报错。
3. **节省内存**
对大量实例组成的数据结构尤其有用。
4. **略微提升性能**
因为对象内部表示更紧凑,某些属性访问场景可能更高效。
不过,[[summaries/02_Classes_encapsulation]] 明确强调:`__slots__` 最常见的用途是性能和内存优化,而不是普通日常代码中的封装工具。
## 适用场景
适合使用 `__slots__` 的情况:
- 类被用作轻量数据结构;
- 程序会创建大量实例;
- 实例属性集合固定;
- 内存占用是重要问题;
- 希望避免动态添加属性造成的错误。
例如:
```python
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
```
如果程序要创建数百万个 `Point` 对象,`__slots__` 可能带来明显内存收益。
## 不适用场景
不建议使用 `__slots__` 的情况:
- 普通业务类;
- 属性集合未来可能变化;
- 需要动态添加属性;
- 希望对象支持灵活调试或扩展;
- 没有明确的内存或性能压力。
过早使用 `__slots__` 可能让类变得不够灵活,也可能给继承、调试和扩展带来额外复杂性。
## 与私有属性的区别
`__slots__` 和私有属性约定是两个不同概念:
| 机制 | 作用 | 是否强制访问控制 |
|---|---|---|
| `_name` | 表示内部实现细节 | 否 |
| `property` | 管理属性访问、验证或计算 | 部分控制赋值逻辑 |
| `__slots__` | 限制实例属性集合、优化内存 | 否 |
例如:
```python
class Stock:
__slots__ = ('name', '_shares', 'price')
```
这里 `_shares` 的下划线表示“内部使用”,而 `__slots__` 表示实例只能拥有这些属性。二者可以配合使用,但含义不同。
## 关键注意点
- `__slots__` 是类属性,不是实例属性。
- 它通常写成字符串元组,如 `('name', '_shares', 'price')`。
- 声明后,实例不能随意添加未列出的属性。
- 如果未包含 `__dict__`,实例通常没有普通实例字典。
- 它不等于私有属性,也不提供真正访问控制。
- 它最常用于大量小对象的内存优化。
- 日常代码中不应为了“看起来更封装”而滥用。
## 小结
`__slots__` 是 Python 提供的一种限制实例属性和优化对象内存布局的机制。它可以让类的实例只拥有预先声明的属性,并避免普通实例字典带来的额外开销。虽然它能帮助发现属性拼写错误,也能让对象结构更固定,但其核心价值主要在性能和内存优化,而不是强制封装。
在设计类时,应优先考虑清晰的公共接口、合理的命名约定和必要的 `property` 管理;只有在属性集合稳定且实例数量巨大时,才应考虑使用 `__slots__`。
See also: [[summaries/05_Object_model__00_Overview]]