(我对 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)
在我实现这个接口(interface)时,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
中定义的(这是参数的类型)来执行
)。所以界面太通用了——命令不需要与任何运行器一起工作,只需要与某些运行器一起工作。因此,让我们在第二个类型 var 上使 Command
通用,以捕获支持的运行器类:
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 满意,我可能可以使用 Runner
子类实现一个 Command
子类,该子类为 T
使用“不同的值”(因为 R
不与 T
绑定(bind)),mypy 不会提示。我尝试了 R = TypeVar('R',bound='Runner[T]')
,但这又引发了另一个错误:
error: Type variable "mypy_sandbox.T" is unbound [valid-type]
如何对其进行类型注释,以便可以进行上述扩展,但仍然可以正确进行类型检查?
最佳答案
现在的注解确实是矛盾的:
Runner
仅允许采用Command[T, Runner[T]]
形式的Command
。Command[bool, Runner[bool]]
的execute
方法接受任何Runner[bool]
.MyCommand
的execute
方法仅接受任何具有magic_level
的“Runner[bool]
” ”。
因此,MyCommand
不是 Command[bool, Runner[bool]]
– 它不接受任何“Runner[bool]
没有magic_level
”。这迫使 MyPy 拒绝替换,即使其原因发生得更早。
这个问题可以通过将R
参数化为Runner
的自身类型来解决。这可以避免强制 Runner
通过基类 Runner[T]
参数化 Command
,而是通过实际的参数化Runner[T]
的子类型。
R = TypeVar('R', bound='Runner[Any]')
T = TypeVar('T') # result type
class Command(ABC, Generic[T, R]):
@abstractmethod
def execute(self, runner: R) -> T:
raise NotImplementedError()
# Runner is not generic in R
class Runner(ABC, Generic[T]):
# Runner.run is generic in its owner
def run(self: R, command: Command[T, R]) -> T:
return command.execute(self)
关于python - 通过共享的任意类型耦合的抽象类的类型注释,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64893801/