Protocol:结构化子类型
Protocol 实现结构化子类型(structural subtyping):类型兼容性基于实际结构(方法、属性),而不是继承关系。这类似于 Go 的接口或 TypeScript 的接口。
Protocol vs 继承
传统继承(名义子类型):
python
class Animal:
def make_sound(self) -> str:
raise NotImplementedError
class Dog(Animal): # 必须显式继承
def make_sound(self) -> str:
return "Woof"
def process_animal(animal: Animal) -> None:
print(animal.make_sound())
dog = Dog()
process_animal(dog) # OKProtocol(结构化子类型):
python
from typing import Protocol
class HasSound(Protocol):
def make_sound(self) -> str:
...
class Dog: # 无需继承 HasSound
def make_sound(self) -> str:
return "Woof"
class Car: # 也无需继承
def make_sound(self) -> str:
return "Honk"
def process_sound(obj: HasSound) -> None:
print(obj.make_sound())
process_sound(Dog()) # OK
process_sound(Car()) # OK,只要有 make_sound 方法只要对象有 make_sound 方法且签名匹配,就满足 HasSound 协议。
定义 Protocol
python
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None:
"""绘制对象"""
...
class Circle:
def draw(self) -> None:
print("Drawing a circle")
class Square:
def draw(self) -> None:
print("Drawing a square")
def render(shape: Drawable) -> None:
shape.draw()
render(Circle()) # OK
render(Square()) # OK注意:Protocol 中的方法体通常用 ...(省略号),因为它只定义接口,不提供实现。
Protocol 的属性
python
from typing import Protocol
class HasName(Protocol):
name: str # 必须有 name 属性
class Person:
def __init__(self, name: str) -> None:
self.name = name
class Company:
def __init__(self, name: str) -> None:
self.name = name
def print_name(obj: HasName) -> None:
print(obj.name)
print_name(Person("Alice")) # OK
print_name(Company("Acme")) # OK运行时可检查协议
默认情况下,Protocol 只在类型检查时有效。使用 @runtime_checkable 可以在运行时检查:
python
from typing import Protocol, runtime_checkable
@runtime_checkable
class Closeable(Protocol):
def close(self) -> None:
...
class File:
def close(self) -> None:
print("Closing file")
class NotCloseable:
pass
file = File()
not_closeable = NotCloseable()
print(isinstance(file, Closeable)) # True
print(isinstance(not_closeable, Closeable)) # False注意:运行时检查只验证方法/属性存在,不检查签名是否匹配。
内置 Protocol
Python 标准库定义了许多常用 Protocol:
python
from typing import Iterable, Iterator, Sized
def process(items: Iterable[int]) -> None:
for item in items:
print(item)
process([1, 2, 3]) # list 实现了 Iterable
process((1, 2, 3)) # tuple 实现了 Iterable
process({1, 2, 3}) # set 实现了 Iterable
def get_length(obj: Sized) -> int:
return len(obj)
print(get_length([1, 2, 3])) # 3
print(get_length("hello")) # 5
print(get_length({1, 2, 3})) # 3常用内置 Protocol:
Iterable[T]:可迭代Iterator[T]:迭代器Sized:有__len__Container[T]:有__contains__Callable[..., T]:可调用
泛型 Protocol
python
from typing import Protocol, TypeVar
T = TypeVar('T')
class Comparable(Protocol):
def __lt__(self, other: T) -> bool:
...
def __le__(self, other: T) -> bool:
...
def find_max(items: list[Comparable]) -> Comparable:
return max(items)
print(find_max([1, 2, 3])) # 3
print(find_max(["a", "b", "c"])) # 'c'Protocol vs ABC
| 特性 | Protocol | ABC(抽象基类) |
|---|---|---|
| 类型检查 | 结构化(鸭子类型) | 名义化(继承) |
| 显式继承 | 不需要 | 必须 |
| 运行时检查 | 可选(@runtime_checkable) | 自动 |
| 灵活性 | 高 | 低 |
选择建议:
- 优先使用 Protocol,更灵活、解耦
- 需要共享实现时使用 ABC
实战案例:插件系统
python
from typing import Protocol
class Plugin(Protocol):
name: str
version: str
def initialize(self) -> None:
...
def execute(self, data: dict) -> dict:
...
class LoggerPlugin:
name = "Logger"
version = "1.0"
def initialize(self) -> None:
print("Logger initialized")
def execute(self, data: dict) -> dict:
print(f"Logging: {data}")
return data
class CachePlugin:
name = "Cache"
version = "2.0"
def initialize(self) -> None:
print("Cache initialized")
def execute(self, data: dict) -> dict:
# 实际缓存逻辑
return data
class PluginManager:
def __init__(self) -> None:
self.plugins: list[Plugin] = []
def register(self, plugin: Plugin) -> None:
plugin.initialize()
self.plugins.append(plugin)
def run_all(self, data: dict) -> dict:
for plugin in self.plugins:
data = plugin.execute(data)
return data
# 使用
manager = PluginManager()
manager.register(LoggerPlugin())
manager.register(CachePlugin())
result = manager.run_all({"key": "value"})小结
- Protocol 实现结构化子类型,无需显式继承
- 基于实际结构(方法、属性)判断兼容性
@runtime_checkable支持运行时检查- 比 ABC 更灵活、解耦
- 适合定义插件接口、回调契约
下一节学习 高级类型,包含 Literal、TypedDict、overload 等。
练习:答案