如何检查具体方法是否符合抽象方法的类型提示
How to check that concrete method is respecting type-hinting for an abstract method
这是一个两部分的问题,但第二部分依赖于第一部分。
出于教育目的,我正在尝试为 groups(抽象代数的概念)实现一个抽象基础 class 和测试套件。代数群的部分定义相当于一个类型约束,我想在 ABC 上实现该类型约束,如果具体 classes 上的方法不符合该约束,我会抱怨.
我已经针对逻辑 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 是什么在做类型约束工作,但当前的实现是错误的。它只检查该方法是否接收到 returns 一个 AbsGroup
实例。这可以是任何 AbsGroup
实例。我希望它检查具体 class 它被继承,它接收 returns 该具体 class 的一个实例(所以在 Bool
的情况下它接收和 returns Bool
的实例)。练习的重点是在一个位置执行此操作,而不是必须专门在每个混凝土上设置它 class。我认为这是通过一些类型提示泛型完成的,这些泛型比我尚未深入研究的类型提示更深入一些。我该怎么做?
其次:如何检查具体方法是否符合抽象类型提示?我的 IDE (PyCharm) 中的类型检查员在 Line-of-interest #2 抱怨,因为它期望 other
是 AbsGroup
类型,而它没有val
属性。这是预料之中的,如果我能找出第一个问题的解决方案,它就会消失,但我的 IDE 是 只有 我能找到的注意到这种差异的东西。 mypy
默认情况下对此事保持沉默,flake8 和 pylint 也是如此。 PyCharm 很好,但是如果我想将其合并到工作流程中,我必须 运行 执行什么命令,如果我的具体方法不符合抽象方法,该命令会失败签名?
第一个提示:如果 mypy
告诉你的不够多,请尝试 mypy --strict
。
您正确地意识到基础 class 中 op
的类型注释不够严格,实际上与 child class 不兼容。
看看这个 not-working 例子。
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
),要么使您的 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:
...
这涉及几个步骤:
- 创建类型变量
T
(看起来类似于其他语言中的泛型类型变量)
- 让基础 class 也继承自
Generic[T]
使其成为通用的 class
- 改变
op
方法采取和return一个T
- 让 child class 继承自
AbsGroup[Bool]
(在 C++ 中这称为 CRTP)
这使 mypy --strict
沉默,PyCharm 正确推断出 op
的 return 类型。
编辑:
之前的 child class 定义看起来像这样 class Bool(AbsGroup[Bool]): ...
没有 引号。但这不起作用,并且会在创建 class:
时抛出 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>']): ...
所以在这种情况下仍然需要引号,即使我们使用了未来的导入。
请注意:为什么要对布尔值使用字符串符号?已经有两个完美工作的实例,称为 True
和 False
。这将使您的代码更加简单。例如。构造函数中的检查可以简化为 if type(val) is bool
(我不会在这里使用 isinstance
因为你不希望 val
成为自定义类型,可能吗?)。
这是一个两部分的问题,但第二部分依赖于第一部分。
出于教育目的,我正在尝试为 groups(抽象代数的概念)实现一个抽象基础 class 和测试套件。代数群的部分定义相当于一个类型约束,我想在 ABC 上实现该类型约束,如果具体 classes 上的方法不符合该约束,我会抱怨.
我已经针对逻辑 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 是什么在做类型约束工作,但当前的实现是错误的。它只检查该方法是否接收到 returns 一个 AbsGroup
实例。这可以是任何 AbsGroup
实例。我希望它检查具体 class 它被继承,它接收 returns 该具体 class 的一个实例(所以在 Bool
的情况下它接收和 returns Bool
的实例)。练习的重点是在一个位置执行此操作,而不是必须专门在每个混凝土上设置它 class。我认为这是通过一些类型提示泛型完成的,这些泛型比我尚未深入研究的类型提示更深入一些。我该怎么做?
其次:如何检查具体方法是否符合抽象类型提示?我的 IDE (PyCharm) 中的类型检查员在 Line-of-interest #2 抱怨,因为它期望 other
是 AbsGroup
类型,而它没有val
属性。这是预料之中的,如果我能找出第一个问题的解决方案,它就会消失,但我的 IDE 是 只有 我能找到的注意到这种差异的东西。 mypy
默认情况下对此事保持沉默,flake8 和 pylint 也是如此。 PyCharm 很好,但是如果我想将其合并到工作流程中,我必须 运行 执行什么命令,如果我的具体方法不符合抽象方法,该命令会失败签名?
第一个提示:如果 mypy
告诉你的不够多,请尝试 mypy --strict
。
您正确地意识到基础 class 中 op
的类型注释不够严格,实际上与 child class 不兼容。
看看这个 not-working 例子。
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
),要么使您的 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:
...
这涉及几个步骤:
- 创建类型变量
T
(看起来类似于其他语言中的泛型类型变量) - 让基础 class 也继承自
Generic[T]
使其成为通用的 class - 改变
op
方法采取和return一个T
- 让 child class 继承自
AbsGroup[Bool]
(在 C++ 中这称为 CRTP)
这使 mypy --strict
沉默,PyCharm 正确推断出 op
的 return 类型。
编辑:
之前的 child class 定义看起来像这样 class Bool(AbsGroup[Bool]): ...
没有 引号。但这不起作用,并且会在创建 class:
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>']): ...
所以在这种情况下仍然需要引号,即使我们使用了未来的导入。
请注意:为什么要对布尔值使用字符串符号?已经有两个完美工作的实例,称为 True
和 False
。这将使您的代码更加简单。例如。构造函数中的检查可以简化为 if type(val) is bool
(我不会在这里使用 isinstance
因为你不希望 val
成为自定义类型,可能吗?)。