如何检查具体方法是否尊重抽象方法的类型暗示

2024-03-28 12:14:34 发布

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

这是一个由两部分组成的问题,但第二部分取决于第一部分。你知道吗

出于教育目的,我尝试为groups(抽象代数的概念)实现一个抽象基类和测试套件。代数组定义的一部分等价于类型约束,我想在ABC上实现该类型约束,如果具体类上的方法不符合该约束,就会有人抱怨。你知道吗

对于逻辑and下的一组布尔值,我已经有了一个第一遍的实现,但是至少有两个问题,我希望你能帮我解决。你知道吗

from __future__ import annotations
from abc import ABC, abstractmethod


class AbsGroup(ABC):
    @abstractmethod
    def op(self, other: AbsGroup) -> AbsGroup:   # <-- Line-of-interest #1
        pass


class Bool(AbsGroup):

    def __init__(self, val="False"):

        if val not in ["True", "False"]:
            raise ValueError("Invalid Bool value %s" % val)

        self.val = val

    def op(self, other):
        """Logical AND"""
        if self.val == "True" and other.val == "True":  # <-- Line-of-interest #2
            return Bool("True")
        return Bool("False")

    def __eq__(self, other):
        return self.val == other.val

    def __repr__(self):
        return self.val

首先:Line of interest#1是进行类型约束的工作,但是当前的实现是错误的。它只检查方法是否接收并返回AbsGroup实例。这可以是任何AbsGroup实例。我希望它检查,对于它继承的具体类,它接收并返回该具体类的实例(因此在Bool的情况下,它接收并返回Bool的实例)。练习的重点是在一个位置执行此操作,而不必在每个具体类上专门设置它。我假设这是通过一些类型暗示泛型完成的,这些泛型比我尚未深入研究的类型暗示要深一点。我该怎么做?你知道吗

第二:如何检查具体方法是否符合抽象类型提示?myide(PyCharm)中的类型检查器在感兴趣的行#2处抱怨,因为它期望otherAbsGroup类型,而该类型没有val属性。这是意料之中的,如果我能找出第一个问题的解决方案,它就会消失,但我的IDE是我能找到的唯一注意到这种差异的东西。mypy在默认情况下是沉默的,就像flake8和pylint一样。PyCharm很好,但是如果我想把它合并到一个工作流中,如果我的具体方法不符合抽象签名,我必须运行什么命令才能失败呢?你知道吗


Tags: of实例selffalsetrue类型returndef
1条回答
网友
1楼 · 发布于 2024-03-28 12:14:34

第一个提示:如果mypy没有告诉您足够的信息,请尝试mypy strict。你知道吗

您正确地认识到基类中op的类型注释没有足够的限制性,实际上与子类不兼容。你知道吗

看看这个不起作用的例子。你知道吗

from __future__ import annotations
from abc import ABC, abstractmethod


class AbsGroup(ABC):
    @abstractmethod
    def op(self, other: AbsGroup) -> AbsGroup:
        pass


class Bool(AbsGroup):
    def __init__(self, val: str = "False") -> None:
        self.val = val

    def op(self, other: Bool) -> Bool:
        ...

我在Bool中用正确的类型注释了op,但现在mypy抱怨:

file.py:15: error: Argument 1 of "op" is incompatible with supertype "AbsGroup "; supertype defines the argument type as "AbsGroup"

您有两个选择:要么使基注释限制更少(Any),要么使类成为Generic一个:

from __future__ import annotations
from abc import ABC, abstractmethod

from typing import TypeVar, Generic


T = TypeVar('T')


class AbsGroup(Generic[T], ABC):
    @abstractmethod
    def op(self, other: T) -> T:
        pass

# EDIT: ADDED QUOTES AROUND Bool
class Bool(AbsGroup['Bool']):
    def __init__(self, val: str = "False") -> None:
        self.val = val

    def op(self, other: Bool) -> Bool:
        ...

这包括几个步骤:

  1. 创建类型变量T(类似于其他语言中的泛型类型变量)
  2. 让基类也从Generic[T]继承,使其成为泛型类
  3. 更改op方法以获取并返回T
  4. 让子类从^ {< CD12> }继承(C++中称为CRTP

这使mypy strict沉默,PyCharm正确地推断了op的返回类型。你知道吗

编辑:

前面的子类定义看起来像这样class Bool(AbsGroup[Bool]): ...没有引号。但这不起作用,在创建类时会抛出NameError

NameError: name 'Bool' is not defined

这是PEP 563中所写的预期行为。你知道吗

[...] However, there are APIs in the typing module that use other syntactic constructs of the language, and those will still require working around forward references with string literals. The list includes: [...]

  • base classes:

    class C(Tuple['<type>', '<type>']): ...

因此,即使我们使用了将来的导入,在这种情况下仍然需要引号。你知道吗

请注意:为什么要对布尔值使用字符串符号?已经有两个完美工作的实例叫做TrueFalse。这将使您的代码更简单。例如,构造函数中的check可以简化为if type(val) is bool(我不会在这里使用isinstance,因为您可能不希望val成为自定义类型?)。你知道吗

相关问题 更多 >