python - 通过共享的任意类型耦合的抽象类的类型注释

标签 python type-hinting mypy python-typing

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

这次我什至不明白错误:MyCommandCommand[bool, MyRunner]的子类,而MyRunnerRunner[bool] 的子类,那么为什么 MyCommandCommand[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] .
  • MyCommandexecute 方法仅接受任何具有 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/

相关文章:

php - Symfony 的文档暗示多重类型提示是可能的。为什么?

python-3.x - 抽象数据类的混合之间的冲突

Python 类型提示 Callable with one known positional type 然后是 *args 和 **kwargs

python - Spark withColumn() 执行幂函数

python - 检查 R 是否从 python 安装

python - Python 3.5中的类型提示是什么?

php - PHP7 中的可空返回类型

python - 为 os.DirEntry 添加类型提示

python - 为什么在 Python 2.x 中使用 msvcrt 时会出现 IOError?

python - 在 Vagrant Ubuntu 20.04 VM 上安装 pyenv