chore: clean repository history
This commit is contained in:
commit
e1fb2929c0
661 files changed
+167488
No files matched your search
@@ -0,0 +1,227 @@
|
||||
---
|
||||
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]]
|
||||
Reference in new issue
Block a user