我正在寻找用 mypy 做一些标准的多态性,我以前从未使用过它,而且到目前为止它并不直观。
基类
class ContentPullOptions:
pass
class Tool(Protocol):
async def pull_content(self, opts: ContentPullOptions) -> str | Dict[str, Any]: ...
子类
class GoogleSearchOptions(ContentPullOptions):
query: str
sites: List[str]
class GoogleSearchTool(Tool):
async def pull_content(
self,
opts: GoogleSearchOptions,
) -> str | Dict[str, Any]:
失败:
error: Argument 1 of "pull_content" is incompatible with supertype "Tool"; supertype defines the argument type as "ContentPullOptions"
什么是像这样在 mypy 中通过类型检查进行基本继承的最可维护和干净的方法?
我尝试了自定义类型、强制转换等。但一切都感觉有点混乱和不清楚。
解决方案
也许这仍然违反了里氏原理
from typing import TypeVar, Protocol, Dict, Any, List, Callable
# Define T as contravariant
T_contra = TypeVar('T_contra', bound=ContentPullOptions, contravariant=True)
class ContentPullOptions:
pass
class Tool(Protocol[T_contra]):
async def pull_content(self, opts: T_contra) -> str | Dict[str, Any]: ...
class GoogleSearchOptions(ContentPullOptions):
query: str
sites: List[str]
class GoogleSearchTool:
async def pull_content(
self,
opts: GoogleSearchOptions,
) -> str | Dict[str, Any]:
# Implementation here
pass
你遇到的 mypy 错误是因为你试图在
GoogleSearchTool
中专门化
pull_content
方法的参数类型,使其比
Tool
协议中定义的更窄。在静态类型语言中,这违反了里氏替换原则,该原则指出子类型应该能够替换其父类型而不会导致问题。
你可以使用以下方法之一在 mypy 中解决此问题并实现多态性:
1. 使用类型变量并保持
pull_content
的参数类型不变:
from typing import TypeVar, Protocol, Dict, Any, List
T = TypeVar('T', bound='ContentPullOptions')
class ContentPullOptions:
pass
class Tool(Protocol[T]):
async def pull_content(self, opts: T) -> str | Dict[str, Any]: ...
class GoogleSearchOptions(ContentPullOptions):
query: str
sites: List[str]
class GoogleSearchTool(Tool[GoogleSearchOptions]):
async def pull_content(self, opts: GoogleSearchOptions) -> str | Dict[str, Any]:
# Implementation here
pass
此解决方案将类型变量
T
引入
Tool
协议,允许专门化
ContentPullOptions
的子类。
GoogleSearchTool
然后被声明为
Tool[GoogleSearchOptions]
,指示它使用
GoogleSearchOptions
作为
pull_content
的参数类型。
2. 使
Tool
中的参数类型更通用:
如果不需要在
Tool
级别指定具体类型,则可以使用
Any
类型:
from typing import Protocol, Dict, Any, List
class ContentPullOptions:
pass
class Tool(Protocol):
async def pull_content(self, opts: Any) -> str | Dict[str, Any]: ...
class GoogleSearchOptions(ContentPullOptions):
query: str
sites: List[str]
class GoogleSearchTool(Tool):
async def pull_content(self, opts: GoogleSearchOptions) -> str | Dict[str, Any]:
# Implementation here
pass
此解决方案避免了对类型变量的需求,但在
Tool
级别丧失了类型安全性。
最佳方法取决于你的具体需求和你想在代码中实现的类型安全级别。如果需要在
Tool
级别保持类型安全性,第一个解决方案是更好的选择。但是,如果不需要这种级别的类型安全性,则可以使用第二个解决方案。