由共享的任意类型耦合的抽象 类 的类型注释
Type annotations for abstract classes that are coupled by a shared, arbitrary type
(我对 Python 的类型注释和 mypy 比较陌生,所以我详细描述了我的问题以避免 运行 变成 XY问题)
我有两个抽象 classes,它们交换任意但固定类型的值:
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
subclass需要访问我的Runner
subclass的一个属性(假设命令可以适应具有不同能力的跑步者):
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
的参数类型) .所以界面太通用了——一个命令不需要与任何跑步者一起工作,只需要与一些跑步者一起工作。因此,让我们在第二个类型 var 上使 Command
通用,以捕获受支持的跑步者 class:
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
is a subclass of Command[bool, MyRunner]
, MyRunner
is a subclass Runner[bool]
,那么为什么 MyCommand
与 Command[bool, Runner[bool]]
不兼容?
如果 mypy 满意我可能会实现一个 Command
subclass 和一个 Runner
subclass 为 [=33 使用“不同的值” =](因为 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]]
。
Command[bool, Runner[bool]]
的 execute
方法接受 any Runner[bool]
.
MyCommand
的execute
方法只接受任何“Runner[bool]
带有magic_level
”。
因此,MyCommand
不是 Command[bool, Runner[bool]]
– 它不接受任何“Runner[bool]
没有 magic_level
” .这会强制 MyPy 拒绝替换,即使它的原因发生得更早。
这个问题可以通过将 R
参数化为 Runner
的自身类型来解决。这避免了强制 Runner
由 基类 Runner[T]
参数化 Command
,而是通过实际的 子类型 [=48] 对其进行参数化=] 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 的类型注释和 mypy 比较陌生,所以我详细描述了我的问题以避免 运行 变成 XY问题)
我有两个抽象 classes,它们交换任意但固定类型的值:
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
subclass需要访问我的Runner
subclass的一个属性(假设命令可以适应具有不同能力的跑步者):
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
的参数类型) .所以界面太通用了——一个命令不需要与任何跑步者一起工作,只需要与一些跑步者一起工作。因此,让我们在第二个类型 var 上使 Command
通用,以捕获受支持的跑步者 class:
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
is a subclass of Command[bool, MyRunner]
, MyRunner
is a subclass Runner[bool]
,那么为什么 MyCommand
与 Command[bool, Runner[bool]]
不兼容?
如果 mypy 满意我可能会实现一个 Command
subclass 和一个 Runner
subclass 为 [=33 使用“不同的值” =](因为 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]]
。Command[bool, Runner[bool]]
的execute
方法接受 anyRunner[bool]
.MyCommand
的execute
方法只接受任何“Runner[bool]
带有magic_level
”。
因此,MyCommand
不是 Command[bool, Runner[bool]]
– 它不接受任何“Runner[bool]
没有 magic_level
” .这会强制 MyPy 拒绝替换,即使它的原因发生得更早。
这个问题可以通过将 R
参数化为 Runner
的自身类型来解决。这避免了强制 Runner
由 基类 Runner[T]
参数化 Command
,而是通过实际的 子类型 [=48] 对其进行参数化=] 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)