绕过mypy在动态属性设置时的“模块没有属性”错误提示

7

我有以下代码在a.py文件中:

class Tags(enum.Flag):
    NONE = 0

    A = enum.auto()
    B = enum.auto()
    C = enum.auto()

# Allow using tags.A instead of tags.Tags.A
globals().update(Tags.__members__)

但是当我将其应用于其他文件时,mypy(理所当然地)无法识别这些属性:

import tags
print(tags.A)  # Module has no attribute "A"

有没有可能在Python 3.6中绕过此问题?

已知的解决方案(这些方案对我的情况不够好):

  • 每次使用tags.A时使用# type: ignore
  • 使用tags.Tags.A
  • 在模块级别使用__getitem__(仅适用于Python 3.7以上版本)

mypy是一种静态分析器,它所知道的关于您代码的运行时修改的唯一信息是它可以在合理的时间内静态证明的内容(除了Haskellers开玩笑,没有人希望他们的类型检查器需要30分钟以上)。 - Jared Smith
1个回答

2

就我个人而言,我会修改我的导入方式为:

from tags import Tags

..这将让我能够轻松地通过 Tags.A 在任何地方引用枚举项。


但是,如果您真的想继续从模块命名空间引用这些项,一种方法是在 tags.py 中定义一个 __getattr__ 函数

class Tags(enum.Flag):
    NONE = 0

    A = enum.auto()
    B = enum.auto()
    C = enum.auto()

def __getattr__(name: str) -> Tags:
    return Tags[name]

在运行时,如果您尝试访问一些未直接定义在tags模块对象中的属性,则Python将尝试调用此函数。Mypy理解这个约定,并假设如果定义了__getattr__,则该模块是“不完整的”。因此,它会将返回类型用作您尝试访问的任何缺少的属性的类型。
但是,您可能仍然希望执行globals().update(Tags.__members__),主要是为了优化性能,以跳过在运行时实际调用__getattr__函数的步骤。
不过,如果tags.py只包含一个枚举,则此策略只适用于一个枚举。否则,您需要将返回类型设置为类似于Union[Tags, MyOtherEnum](难以操作),甚至只是Any(这样会失去使用类型检查器的好处)。
此策略还意味着mypy将无法通过考虑实际枚举值来进行更复杂的类型推断和缩小范围。不过,这主要只与使用Literal enums有关。
如果这些是问题,您可能需要采用更加强制的方法,如下所示:
from typing import TYPE_CHECKING

class Tags(enum.Flag):
    NONE = 0

    A = enum.auto()
    B = enum.auto()
    C = enum.auto()

globals().update(Tags.__members__)

if TYPE_CHECKING:
    NONE = Tags.NONE
    A = Tags.A
    B = Tags.B
    C = Tags.C

TYPE_CHECKING常量在运行时始终为false,但类型检查器认为它始终为true。

但是,如果您要花费直接告诉mypy这些变量的每一个变体的费用,那么您可能会跳过尝试自动更新全局变量并改为执行以下操作:

class Tags(enum.Flag):
    NONE = 0

    A = enum.auto()
    B = enum.auto()
    C = enum.auto()

NONE = Tags.NONE
A = Tags.A
B = Tags.B
C = Tags.C

显然,像这样重复两次是相当不理想的,但我认为没有简单的解决方法。也许你可以通过创建一个自动生成tags.py脚本来减轻这种情况,但由于不同的原因,这也是相当不理想的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接