chore: clean repository history

This commit is contained in:
xixu-me committed 2026-06-05 21:13:11 +08:00
commit e1fb2929c0
661 files changed
+167488

No files matched your search

@@ -0,0 +1,296 @@
---
doc_type: short
full_text: sources/02_Classes_encapsulation.md
---
# 02_Classes_encapsulation 总结
本文介绍 Python 中类与对象的封装方式,重点说明公共接口与内部实现的区别,以及 Python 如何通过命名约定、属性管理、`property` 和 `__slots__` 来实现较弱但实用的封装。
## 核心主题
### 公共接口与私有实现
类的一个重要作用是封装对象的数据和内部实现细节,同时向外部提供稳定的公共接口。外部代码应通过公共接口操作对象,而不应依赖对象的内部结构。
不过,Python 的对象系统非常开放:
- 可以轻易查看对象内部属性;
- 可以随意修改对象属性;
- 没有强制性的私有成员访问控制机制。
因此,Python 的封装主要依赖程序员遵守约定,而不是语言强制执行。这与 python encapsulation 和 object oriented programming 密切相关。
## 私有属性约定
Python 中,以下划线 `_` 开头的名称通常被视为“私有”或“内部实现细节”:
```python
class Person(object):
def __init__(self, name):
self._name = name
```
这种私有性只是约定,并不阻止外部访问:
```python
p = Person('Guido')
p._name = 'Dave'
```
一般规则是:变量、函数、模块名只要以下划线开头,就表示它们不属于公共接口。直接使用这类名称通常意味着代码正在依赖实现细节,应优先寻找更高层的公共功能。
## 简单属性的问题
普通 Python 类可以直接暴露属性:
```python
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
```
这种写法简单直接,但缺点是无法限制属性值类型:
```python
s = Stock('IBM', 50, 91.1)
s.shares = 100
s.shares = "hundred"
s.shares = [1, 0, 0]
```
如果希望 `shares` 始终是整数,就需要引入某种属性管理机制。
## 访问器方法的问题
一种传统做法是使用 getter/setter 方法:
```python
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.set_shares(shares)
self.price = price
def get_shares(self):
return self._shares
def set_shares(self, value):
if not isinstance(value, int):
raise TypeError('Expected an int')
self._shares = value
```
这种方式可以加入类型检查,但会破坏原有调用方式:
```python
s.shares = 50
```
必须改为:
```python
s.set_shares(50)
```
这会影响已有代码的兼容性。
## property:受管理的属性
Python 提供 `@property` 机制,使方法可以像普通属性一样访问,同时仍能在读取或赋值时执行自定义逻辑。
```python
class Stock:
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
```
现在,普通属性访问会触发 getter/setter:
```python
s = Stock('IBM', 50, 91.1)
s.shares # 调用 @property
s.shares = 75 # 调用 @shares.setter
```
重要特点:
- 外部代码仍然使用 `s.shares`,无需改成 `s.get_shares()` 或 `s.set_shares()`;
- 类内部的 `self.shares = shares` 同样会触发 setter;
- 实际数据通常保存在私有属性中,如 `_shares`;
- 除 property 本身外,类中其他代码仍可以继续使用公共属性名 `shares`。
这一模式是 Python 中实现 managed attributes 的常见方式。
## 计算属性
`property` 也常用于把计算结果包装成属性。例如股票成本可以由 `shares * price` 计算得到:
```python
class Stock:
@property
def cost(self):
return self.shares * self.price
```
这样调用者可以写:
```python
s = Stock('GOOG', 100, 490.1)
s.cost
```
而不是:
```python
s.cost()
```
这让对象接口更加统一:普通数据属性和计算属性都可以通过无括号的属性访问方式获得。
## 统一访问原则
如果一个对象既有数据属性,又有计算方法,接口可能显得不一致:
```python
s.cost() # 方法
s.shares # 数据属性
```
使用 `property` 后,调用方式可以统一为:
```python
s.cost
s.shares
```
这种设计隐藏了“数据是存储的还是计算的”这一实现细节,使公共接口更稳定。这一点体现了封装的核心价值:调用者不需要知道内部实现。
## 装饰器语法
`@property` 使用的是 Python 装饰器语法:
```python
@property
def cost(self):
return self.shares * self.price
```
`@` 表示把紧随其后的函数定义交给某个装饰器处理。这里 `property` 会把方法转换成属性描述符。该主题可进一步关联到 python decorators。
## __slots__:限制属性集合
`__slots__` 可以限制实例允许拥有的属性名:
```python
class Stock:
__slots__ = ('name', '_shares', 'price')
def __init__(self, name, shares, price):
self.name = name
```
如果尝试设置未声明的属性,会抛出 `AttributeError`:
```python
s.prices = 410.2
# AttributeError: 'Stock' object has no attribute 'prices'
```
`__slots__` 的作用包括:
- 防止拼写错误导致意外创建新属性;
- 限制对象使用方式;
- 减少对象内存占用;
- 在某些数据结构类中提高运行效率。
不过,本文强调 `__slots__` 主要是性能和内存优化工具,而不是日常封装的首选手段。多数普通类不需要使用它。该主题可关联到 python slots 和 python object memory。
## 练习要点
### 练习 5.6:简单 property
将 `Stock.cost()` 方法改为 `cost` 属性,使其调用方式从:
```python
s.cost()
```
变为:
```python
s.cost
```
改动后,`s.cost()` 将不再可用,因为 `cost` 已经是属性值,而不是可调用方法。相关程序如 `pcost.py` 也需要同步移除括号。
### 练习 5.7:property 与 setter
把 `shares` 改为受管理属性:
- 实际值存储在 `_shares`;
- 通过 `@property` 读取;
- 通过 `@shares.setter` 设置;
- setter 中检查值必须是整数;
- 非整数赋值时抛出 `TypeError`。
目标行为:
```python
s.shares = 50
s.shares = 'a lot' # TypeError
```
### 练习 5.8:添加 __slots__
为 `Stock` 添加 `__slots__`,验证:
- 不能随意添加新属性,如 `s.blah = 42`;
- 使用 `__slots__` 后,实例通常不再有普通的 `__dict__`;
- 这说明对象内部表示发生变化,内存使用更高效。
## 关键结论
- Python 的封装主要依赖命名约定,而非强制访问控制。
- `_name` 这类前导下划线名称表示内部实现细节,不应被外部代码直接依赖。
- `property` 可以在保持属性访问语法不变的情况下加入验证、计算和封装逻辑。
- `property` 有助于提供统一接口,隐藏数据是存储的还是计算的。
- `__slots__` 可以限制属性集合并优化内存,但不应滥用。
- 私有属性、property 和 slots 都是有特定用途的工具,大多数日常代码不需要过度使用。
## 相关概念
- python encapsulation
- object oriented programming
- managed attributes
- python properties
- python decorators
- python slots
- python object memory
## Related Concepts
- [[concepts/Python-property-属性]]
- [[concepts/Python-slots]]
- [[concepts/Python-封装与访问约定]]
- [[concepts/类与对象]]
- [[concepts/Python-对象模型]]
- [[concepts/库接口设计]]
- [[concepts/动态属性访问]]
- [[concepts/异常处理]]
- [[concepts/字典与数据建模]]
- [[concepts/Python-命名空间与作用域]]
- [[concepts/特殊方法]]