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



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

出于教育目的,我正在尝试为组实现抽象基类和测试套件(抽象代数的概念)。代数群的部分定义等价于类型约束,我想在 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

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

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

第一个提示:如果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:错误:"op"的参数 1 与超类型不兼容 "AbsGroup";超类型将参数类型定义为"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. 更改要获取和返回Top方法
  4. 让子类继承自AbsGroup[Bool](C++这称为 CRTP)

这会使mypy --strict静音,PyCharm 会正确推断op的返回类型。

编辑:

以前的子类定义如下所示class Bool(AbsGroup[Bool]): ...没有引号。但这不起作用,并且在创建类时会引发NameError

名称错误: 名称"布尔"未定义

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

[...]但是,键入模块中存在使用该语言的其他语法结构的 API,这些 API 仍然需要使用字符串文本处理前向引用。该列表包括: [...]

  • 基类:

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

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

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

相关内容

  • 没有找到相关文章

最新更新