繁体   English   中英

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

[英]How to check that concrete method is respecting type-hinting for an abstract method

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

出于教育目的,我正在尝试实现一个抽象基础 class 和测试套件(来自抽象代数的概念)。 代数群的部分定义等效于类型约束,我想在 ABC 上实现该类型约束,如果具体类上的方法不符合该约束,我会抱怨。

对于逻辑and下的一组 Boolean 值,我有一个首次通过的实现,但它至少有两个问题,我希望你能帮我解决它。

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

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

其次:如何检查具体方法是否符合抽象类型提示? 我的 IDE (PyCharm) 中的类型检查器在 Line-of-interest #2 抱怨,因为它期望otherAbsGroup类型,它没有val属性。 这是意料之中的,如果我能找出第一个问题的解决方案,go 会消失,但我的 IDE 是我能找到的唯一能注意到这种差异的东西。 默认情况下, mypy对此事保持沉默,flake8 和 pylint 也是如此。 PyCharm 很好,但是如果我想将其合并到工作流程中,如果我的具体方法不符合抽象签名,我必须运行什么命令会失败?

第一个提示:如果mypy没有告诉您足够多的信息,请尝试mypy --strict

您正确地意识到基础 class 中op的类型注释不够严格,实际上与子 class 不兼容。

看看这个不起作用的例子。

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:错误:“op”的参数 1 与超类型“AbsGroup”不兼容; 超类型将参数类型定义为“AbsGroup”

您有两个选择:要么使基本注释的限制更少( Any ),要么使您的 class 成为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. 让基础 class 也继承自Generic[T]使其成为通用 class
  3. 更改op方法以获取并返回一个T
  4. 让孩子 class 从AbsGroup[Bool]继承(在 C++ 这被称为CRTP

mypy --strict静音,并且 PyCharm 正确推断op的返回类型。

编辑:

前一个子 class 定义看起来像这样class Bool(AbsGroup[Bool]): ...不带引号。 但这不起作用,并且在创建 class 时会抛出NameError

NameError:名称“布尔”未定义

这是PEP 563中所写的预期行为。

[...] 但是,类型模块中的 API 使用该语言的其他句法结构,这些 API 仍然需要使用字符串文字解决前向引用 该列表包括:[...]

  • 基类:

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM