chore: clean repository history
This commit is contained in:
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/特殊方法]]
|
||||
Reference in new issue
Block a user