Skip to content

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)  # OK

Protocol(结构化子类型):

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 ​

特性ProtocolABC(抽象基类)
类型检查结构化(鸭子类型)名义化(继承)
显式继承不需要必须
运行时检查可选(@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 等。


参考:PEP 544 – Protocols

练习:答案

用心记录每一步成长