面向对象与类型系统:元类 / 描述符 / 鸭子类型
# 第 13 章 面向对象与类型系统
# 目录介绍
Python 的 OOP 比 Java/C++ 更灵活——类本身也是对象、属性访问可定制、类型检查基于行为而非继承。
# 13.1 元类的底层机制
类也是对象——class 语句本质上是调用 type() 创建了一个对象:
# 用 class 语法
class Person:
name = 'Alice'
# 等价于 type() 调用
Person = type('Person', (), {'name': 'Alice'})
# ↑类名 ↑基类元组 ↑类属性字典
2
3
4
5
6
7
type 既是工厂函数也是元类——它创建了所有类(包括它自己)。
# 13.1.1 类也是对象
class Demo:
def __new__(cls, *a, **kw):
print(f"1. __new__ 创建实例,cls={cls.__name__}")
return super().__new__(cls) # 必须返回实例!
def __init__(self):
print(f"2. __init__ 初始化实例")
Demo()
# 1. __new__ 创建实例,cls=Demo
# 2. __init__ 初始化实例
2
3
4
5
6
7
8
9
10
__new__负责分配内存返回实例(静态方法),__init__负责设置属性(实例方法)。单例模式就在__new__中实现。
# 13.1.2 new 与 init
元类 = 类的类。当执行 class MyClass(meta=MyMeta): 时,Python 调用 MyMeta.__new__ 创建这个类对象。
#!/usr/bin/env python3
"""用元类实现 ORM 模型——自动收集字段定义"""
import re
class ModelMeta(type):
def __new__(mcs, name, bases, namespace):
if name == 'Model': # 跳过基类
return super().__new__(mcs, name, bases, namespace)
# 自动收集字段定义
namespace['_fields'] = {
k: v for k, v in namespace.items()
if isinstance(v, Field)
}
namespace.setdefault('_table', name.lower() + 's')
return super().__new__(mcs, name, bases, namespace)
class Field:
def __init__(self, field_type, required=False):
self.type = field_type; self.required = required
class Model(metaclass=ModelMeta):
def __init__(self, **kwargs):
for name, field in self._fields.items():
value = kwargs.get(name)
if field.required and value is None:
raise ValueError(f"{name} is required")
setattr(self, name, value)
# ---- 使用 ----
class User(Model):
name = Field(str, required=True)
age = Field(int)
u = User(name='Alice', age=30)
print(u._fields) # {'name': <Field>, 'age': <Field>}
print(u._table) # 'users'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 13.1.3 自定义元类实战
多继承时,Python 用 C3 线性化算法 计算 MRO——保证子类优先、基类顺序一致:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.__mro__)
# (D, B, C, A, object) ← C3 线序:D→B→C→A→object
# super() 沿 MRO 链向上查找
class E:
def greet(self): print("E")
class F(E):
def greet(self): print("F"); super().greet() # 找 MRO 中 E 之后的类
2
3
4
5
6
7
8
9
10
11
12
13
C3 核心规则:
L[C] = C + merge(L[B1], ..., L[Bn], B1...Bn)。你不必手算——cls.__mro__随时可查。
# 13.2 描述符协议详解
描述符协议 = __get__ / __set__ / __delete__。实现了这些方法的对象可以控制另一个类的属性访问行为。
property 就是内置描述符——它为你包装了 getter/setter/deleter 三个函数。
# 13.2.1 基础描述符
数据描述符 vs 非数据描述符:实现了
__set__的描述符叫"数据描述符"(如property),优先级高于实例字典;只有__get__的是"非数据描述符",实例字典优先级更高。
#!/usr/bin/env python3
"""描述符——类型校验器"""
class Typed:
def __init__(self, name, expected_type):
self.name = name; self.type = expected_type
self.data = {} # 属主实例→值 映射
def __get__(self, obj, objtype=None):
if obj is None: return self # 类级别访问返回描述符本身
return self.data.get(obj)
def __set__(self, obj, value):
if not isinstance(value, self.type):
raise TypeError(f"{self.name} must be {self.type.__name__}")
self.data[obj] = value
class Person:
name = Typed('name', str)
age = Typed('age', int)
p = Person()
p.name = 'Alice' # ✅
# p.age = '30' # ❌ TypeError
p.age = 30 # ✅
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 13.2.2 类型校验
#!/usr/bin/env python3
"""描述符实现惰性缓存——只计算一次"""
class CachedProperty:
def __init__(self, func):
self.func = func
self.name = func.__name__
def __get__(self, obj, objtype=None):
if obj is None: return self
value = self.func(obj)
# 关键:把结果存在实例 __dict__ 中——
# 下次访问时实例属性优先于描述符(非数据描述符的情况)
obj.__dict__[self.name] = value
return value
class Heavy:
@CachedProperty
def data(self):
print(" [computing...]")
import time; time.sleep(1)
return sum(range(10**6))
h = Heavy()
print(h.data) # [computing...] → 499999500000
print(h.data) # 499999500000 ← 直接返回,无计算
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
数据描述符 vs 非数据描述符:实现了 __set__ 的描述符叫"数据描述符"(如 property),优先级高于实例字典;只有 __get__ 的是"非数据描述符"(如 classmethod),实例字典优先级更高——这就是 cached_property 能工作的原因。
# 13.3 鸭子类型与抽象基类
鸭子类型:"如果它走路像鸭子、叫声像鸭子,那它就是鸭子"——只要对象有需要的属性和方法,Python 就接受它,不需要显式继承。
抽象基类(ABC):反过来——显式声明接口,调用方可以依赖 isinstance 检查。
# 13.2.3 惰性缓存
# 不要求继承任何基类——只要有 read() 就能用
def process(reader):
return reader.read().upper()
# 文件对象
with open('test.txt') as f:
print(process(f)) # ✅
# 内存字符串——只要 wrap 一个类
class StringReader:
def __init__(self, data): self.data = data
def read(self): return self.data
print(process(StringReader('hello'))) # ✅ 鸭子类型!
2
3
4
5
6
7
8
9
10
11
12
13
14
# 13.3.1 鸭子类型
import abc
class AbstractCache(abc.ABC):
@abc.abstractmethod
def get(self, key): ...
@abc.abstractmethod
def set(self, key, value): ...
class RedisCache(AbstractCache): # ❌ 忘记实现 get/set → TypeError
pass
# 使用 ABC 的好处:ide 提示、文档约束、isinstance 检查
assert issubclass(RedisCache, AbstractCache) # 如果实现了抽象方法
2
3
4
5
6
7
8
9
10
11
12
13
# 13.3.2 抽象基类
Protocol 让鸭子类型获得静态类型检查的支持:
from typing import Protocol
class SupportsClose(Protocol):
def close(self) -> None: ...
def cleanup(resource: SupportsClose) -> None:
resource.close()
# 不需要继承 SupportsClose——只要有 close() 方法就能通过 mypy 检查
class MyResource:
def close(self): print("closed")
cleanup(MyResource()) # ✅ mypy 不会报错
2
3
4
5
6
7
8
9
10
11
12
13
# 13.3.3 collections.abc
from collections.abc import MutableSequence
# 只要实现 5 个抽象方法就能获取完整的 list 接口!
class MyList(MutableSequence):
def __init__(self): self._data = []
def __len__(self): return len(self._data)
def __getitem__(self, i): return self._data[i]
def __setitem__(self, i, v): self._data[i] = v
def __delitem__(self, i): del self._data[i]
def insert(self, i, v): self._data.insert(i, v)
ml = MyList(); ml.append(1); ml.extend([2,3])
print(ml.count(1)) # 0 — count 由 ABC 自动提供
print(isinstance(ml, MutableSequence)) # True
2
3
4
5
6
7
8
9
10
11
12
13
14
# 13.4 类型系统全景总结
| 机制 | 检查时机 | 语法 | 场景 |
|---|---|---|---|
| 元类 | 类定义时 | class X(meta=M) | 框架/ORM |
| 描述符 | 属性访问时 | __get__/__set__ | property/类型校验 |
| 鸭子类型 | 运行时 | 无——纯约定 | Python 默认风格 |
| ABC | 实例化/isinstance | @abstractmethod | 框架接口定义 |
| Protocol | 静态检查(mypy) | class X(Protocol) | 结构子类型 |
| 多重继承+MRO | 方法调用时 | class D(B,C) | Mixin 复用 |
super() | 方法调用时 | super().method() | 沿 MRO 链协作 |
选择建议:优先鸭子类型;需要强制接口时用 ABC;需要静态检查时用 Protocol;框架/元编程才碰元类。