(我对Python的类型注释和mypy相当陌生,所以我将详细描述我的问题,以避免遇到XY问题)
我有两个抽象类,它们交换任意但固定类型的值:
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Generic, TypeVar
T = TypeVar('T') # result type
class Command(ABC, Generic[T]):
@abstractmethod
def execute(self, runner: Runner[T]) -> T:
raise NotImplementedError()
class Runner(ABC, Generic[T]):
def run(self, command: Command[T]) -> T:
return command.execute(self)
在这个接口的实现中,Command
子类需要访问我的Runner
子类的属性(假设该命令可以适应具有不同功能的运行程序):
class MyCommand(Command[bool]):
def execute(self, runner: Runner[bool]) -> bool:
# Pseudo code to illustrate dependency on runner's attributes
return runner.magic_level > 10
class MyRunner(Runner[bool]):
magic_level: int = 20
这与预期一样有效,但不能满足mypy的要求:
mypy_sandbox.py:24: error: "Runner[bool]" has no attribute "magic_level" [attr-defined]
显然,mypy是正确的:magic_level
属性是在MyRunner
中定义的,而不是在Runner
(这是execute
的参数类型)中定义的。因此,接口太通用了——命令不需要与任何运行程序一起使用,只需要与某些运行程序一起使用。因此,让我们对第二个类型变量进行Command
泛型,以捕获受支持的runner类:
R = TypeVar('R') # runner type
T = TypeVar('T') # result type
class Command(ABC, Generic[T, R]):
@abstractmethod
def execute(self, runner: R) -> T:
raise NotImplementedError()
class Runner(ABC, Generic[T]):
def run(self, command: Command[T, Runner[T]]) -> T:
return command.execute(self)
class MyCommand(Command[bool, MyRunner]):
def execute(self, runner: MyRunner) -> bool:
# Pseudo code to illustrate dependency on runner's attributes
return runner.magic_level > 10
# MyRunner defined as before
这让mypy感到满意,但当我尝试使用代码时,mypy再次抱怨:
if __name__ == '__main__':
command = MyCommand()
runner = MyRunner()
print(runner.run(command))
mypy_sandbox.py:35: error: Argument 1 to "run" of "Runner" has incompatible type "MyCommand"; expected "Command[bool, Runner[bool]]" [arg-type]
这次我甚至不理解错误:MyCommand
是Command[bool, MyRunner]
的一个子类,而MyRunner
是Runner[bool]
的一个子类,那么为什么MyCommand
与Command[bool, Runner[bool]]
不兼容呢
如果mypy感到满意,我可能可以实现一个Command
子类,其中Runner
子类使用T
的“不同值”(因为R
与T
无关),而不必抱怨mypy。我尝试了R = TypeVar('R', bound='Runner[T]')
,但这又引发了另一个错误:
error: Type variable "mypy_sandbox.T" is unbound [valid-type]
如何对其进行类型注释,以便可以进行上述扩展,但仍能正确地进行类型检查?
当前的注释确实是一个矛盾:
Runner
只允许Command
形式的Command[T, Runner[T]]
李>MyCommand
的execute
方法只接受带有magic_level
的任何“Runner[bool]
”李>因此,
MyCommand
不是一个Command[bool, Runner[bool]]
–它不接受没有magic_level
的任何“Runner[bool]
”。这迫使MyPy拒绝替换,即使其原因发生得更早这个问题可以通过将
R
参数化为Runner
的自类型来解决。这避免了通过基类Runner[T]
强制Runner
对Command
进行参数化,而是通过Runner[T]
的实际子类型对其进行参数化相关问题 更多 >
编程相关推荐