由共享的任意类型耦合的抽象类的类型注释

2024-05-19 20:54:06 发布

您现在位置:Python中文网/ 问答频道 /正文

(我对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]

这次我甚至不理解错误:MyCommandCommand[bool, MyRunner]的一个子类,而MyRunnerRunner[bool]的一个子类,那么为什么MyCommandCommand[bool, Runner[bool]]不兼容呢

如果mypy感到满意,我可能可以实现一个Command子类,其中Runner子类使用T的“不同值”(因为RT无关),而不必抱怨mypy。我尝试了R = TypeVar('R', bound='Runner[T]'),但这又引发了另一个错误:

error: Type variable "mypy_sandbox.T" is unbound  [valid-type]

如何对其进行类型注释,以便可以进行上述扩展,但仍能正确地进行类型检查?


Tags: self类型executedeftype子类commandclass
1条回答
网友
1楼 · 发布于 2024-05-19 20:54:06

当前的注释确实是一个矛盾:

  • Runner只允许Command形式的Command[T, Runner[T]]
  • {}的{}方法接受任何{}
  • MyCommandexecute方法只接受带有magic_level的任何“Runner[bool]

因此,MyCommand不是一个Command[bool, Runner[bool]]–它不接受没有magic_level的任何“Runner[bool]”。这迫使MyPy拒绝替换,即使其原因发生得更早


这个问题可以通过将R参数化为Runner的自类型来解决。这避免了通过基类Runner[T]强制RunnerCommand进行参数化,而是通过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)

相关问题 更多 >