Python Protocol如何同时兼容Optional与非可选同类型属性的实现类?
这个需求可以实现,核心原因是Protocol的可变实例属性默认是**不变(invariant)**的,只要调整Protocol定义让val属性支持协变即可,有两种可行方案:
方案1:将val声明为只读属性(推荐)
如果你不需要在func中修改val的值,这是最简洁的方案,不需要改动任何实现类的代码:
from typing import Protocol, Optional class Foo(Protocol): # 用@property声明为只读属性,天然支持协变 @property def val(self) -> Optional[int]: ... def func(obj: Foo) -> None: if obj.val is not None: print(obj.val) # 原有实现类不需要任何修改 class ImplA: val: int class ImplB: val: Optional[int] a = ImplA() b = ImplB() func(a) # 类型检查正常通过 func(b) # 类型检查正常通过
原理说明:直接写val: Optional[int]时,类型检查器默认认为这是可修改的实例属性,要求类型严格匹配。用@property声明后,类型检查器会判定属性为只读,此时属性类型是协变的,只要实现类的属性类型是声明类型的子类型就可以兼容,int本身就是Optional[int]的子类型,因此可以正常匹配。
方案2:使用协变泛型参数
如果你需要保留val的可写属性,并且可以保证不会在func中给val赋值None,可以用协变的泛型参数实现:
from typing import Protocol, Optional, TypeVar # 定义边界为Optional[int]的协变类型变量 T = TypeVar('T', bound=Optional[int], covariant=True) class Foo(Protocol[T]): val: T # 入参要求为协变基类型Foo[Optional[int]] def func(obj: Foo[Optional[int]]) -> None: if obj.val is not None: print(obj.val) # 注意:禁止执行obj.val = None,否则传入ImplA实例时会出现运行时错误 class ImplA: val: int class ImplB: val: Optional[int] a = ImplA() b = ImplB() func(a) # 类型检查正常通过 func(b) # 类型检查正常通过
如果你确实需要在func中修改val,允许赋值None,那么这个需求本身不成立——因为ImplA的val不支持None类型的赋值,此时只能调整ImplA的定义或者使用Union。
内容的提问来源于stack exchange,提问作者PIG208
相关产品推荐
相关产品推荐

