如何无需显式断言,在运行时强制Python Protocol类定义合规?
运行时检查Protocol合规性的替代方案
以下几种方案可以在不使用@typing.runtime_checkable和显式assert isinstance的前提下,在运行时验证工厂方法返回的实例是否符合Protocol定义:
方案一:手动实现协议验证函数
编写通用的验证函数,遍历Protocol中的抽象方法,检查实例是否具备对应方法且签名完全匹配。借助inspect模块可以精准对比方法的参数、默认值和返回类型。
import inspect from typing import Protocol, abstractmethod class Thing(Protocol): @abstractmethod def do_thing(self, thing_id: int, action: str, monitor: bool = True) -> None: raise NotImplementedError def validate_protocol(instance: object, protocol: type[Protocol]) -> None: # 提取协议中所有抽象方法 abstract_methods = {} for name, attr in protocol.__dict__.items(): if isinstance(attr, abstractmethod): abstract_methods[name] = attr for method_name, proto_method in abstract_methods.items(): # 检查实例是否存在该方法 if not hasattr(instance, method_name): raise RuntimeError(f"实例不符合{protocol.__name__}协议:缺少方法「{method_name}」") inst_method = getattr(instance, method_name) # 对比方法签名 try: proto_sig = inspect.signature(proto_method) inst_sig = inspect.signature(inst_method) except ValueError: raise RuntimeError(f"无法解析方法「{method_name}」的签名") if proto_sig != inst_sig: raise RuntimeError( f"方法「{method_name}」签名不匹配:" f"预期格式 {proto_sig},实际为 {inst_sig}" ) # 修改工厂方法,在返回前执行验证 def provider_factory(some_data: dict) -> Thing: if some_data['type'] == 'TypeA': instance = TypeAProcessor(some_data) elif some_data['type'] == 'TypeB': instance = TypeBProcessor(some_data) else: raise ValueError("不支持的类型") validate_protocol(instance, Thing) return instance
方案二:用装饰器自动验证工厂返回值
将验证逻辑封装为装饰器,直接应用在工厂方法上,无需修改工厂内部的分支逻辑,代码更简洁易维护。
# 复用上面定义的validate_protocol函数 def enforce_protocol(protocol: type[Protocol]): def decorator(factory_func): def wrapper(*args, **kwargs): instance = factory_func(*args, **kwargs) validate_protocol(instance, protocol) return instance return wrapper return decorator # 给工厂方法添加装饰器,自动触发验证 @enforce_protocol(Thing) def provider_factory(some_data: dict) -> Thing: if some_data['type'] == 'TypeA': return TypeAProcessor(some_data) elif some_data['type'] == 'TypeB': return TypeBProcessor(some_data) # ...其他类型分支
方案三:动态代理类包装实例
创建代理类包裹工厂返回的实例,在初始化代理时完成协议验证,也可选在调用方法时再次校验签名。这种方式可以对实例的方法调用做额外拦截处理。
# 复用上面定义的validate_protocol函数 class ProtocolProxy: def __init__(self, instance: object, protocol: type[Protocol]): self._instance = instance # 初始化时完成全量协议验证 validate_protocol(instance, protocol) def __getattr__(self, name): attr = getattr(self._instance, name) # 如果是方法,返回包装后的方法(可选:可在此处再次校验签名) if callable(attr): def wrapped_method(*args, **kwargs): # 可选:此处可以再次调用inspect检查签名 return attr(*args, **kwargs) return wrapped_method return attr # 修改工厂方法返回代理对象 def provider_factory(some_data: dict) -> Thing: if some_data['type'] == 'TypeA': instance = TypeAProcessor(some_data) elif some_data['type'] == 'TypeB': instance = TypeBProcessor(some_data) # ...其他分支 return ProtocolProxy(instance, Thing)
内容的提问来源于stack exchange,提问作者Danielle M.
相关产品推荐
相关产品推荐

