Python 是动态类型语言, 只在运行时做 Duck Typing 检查.
- 利: 灵活, 方便
- 弊: 代码混乱, 缺少规范
标准自带两类接口支持: abc 和 typing.Protocol, 有他们协助给天马行空的程序员套上枷锁, Python 的大工程才可以"上道"
abc
abc 就是 Abstract Base Class, 虚基类. 跟 Java, C++ 中的虚基类是一个意思, 可以对派生类提供实例化时的动态检查, 确保虚拟接口 (abstractmethod) 都有实现
import abcclass Base(abc.ABC): @abstractmethod def foo(self, s: str): """abc interface demo """class Invalid(Base): pass class Child(Base): def foo(self): pass c = Child()assert isinstance(c, Base)# TypeError: Can't instantiate abstract class Invalid with abstract methods fooi = Invalid()
也提供了非侵入式的虚基类关联方法
from abc import ABCclass MyABC(ABC): passMyABC.register(tuple)assert issubclass(tuple, MyABC)assert isinstance((), MyABC)
- 检查时机: 在运行中当派生类实例化时
- 检查范围: 只确保 abstractmethod 是否在派生类中有相同函数名实现, 并不检查实现的参数和返回值是否相同. 只看名字不比签名
- 代码影响: 侵入式, 需要继承. 也有手工非侵入式方案
typing.Protocol
structure subtyping (static duck-typing)
import typingclass Countable(typing.Protocol): def count(self, who: str) -> int: """support count """ class Counter: def count(self, who: str) -> int: return 0 c = Counter()def f(c: Countable): c.count("bill")
- 检查时机: 静态类型检查接口使用方, 例如 mypy
- 检查范围: 确保实现类按照签名实现了接口的全部函数
- 代码影响: 非侵入式, 不需要继承
比较
abc 类似 c++ 中的虚基类, typing.Protocol 则好比 c++ 中的 concept.
当然, Python 是动态语言, 在 typing.runtime_checkable 和 abc.abstractmethod 加成后, typing.Protocol 动静两相宜
import typing@typing.runtime_checkableclass Countable(typing.Protocol): @abc.abstractmethod def count(self, who: str) -> int: """support count """ class Counter: def count(self, who: str) -> int: return 0 assert issubclass(Counter, Countable)c = Counter()assert isinstance(c, Countable)def f(c: Countable): assert isinstance(c, Countable) print(c.count("bill")) f(c)class InvalidCounter(Countable): def c(self): pass # TypeError: Can't instantiate abstract class InvalidCounter with abstract methods counti = InvalidCounter()
上面这个终极解决方案兼有两者的优点:
- 静态类型检查时会确保是否在派生类中有相同签名的实现
- 动态运行时, 会检查是否同名函数存在
- 代码影响: 自动非侵入式, 不需要继承, 也无需手工注册