在动态属性设置上绕过 mypy 的 "Module has no attribute"

Bypass mypy's "Module has no attribute" on dynamic attribute setting

我在 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 中有什么方法可以绕过这个吗?

已知解决方案(对我来说不够好):

就个人而言,我会做的是修改我的导入来做:

from tags import Tags

..这会让我通过在任何地方做 Tags.A 来引用枚举项,而不会大惊小怪。


但是如果你真的想继续从模块命名空间中引用这些项目,一种方法是在 tags.py:

中定义一个 __getattr__ function
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__,则模块是 "incomplete"。因此,它将使用 return 类型作为您尝试访问的任何缺失属性的类型。

尽管如此,您可能仍然希望 globals().update(Tags.__members__) 主要作为性能优化,以跳过必须在运行时实际调用 __getattr__ 函数。

此策略仅在 tags.py 仅包含一个枚举时才真正起作用——否则,您需要使 return 类型类似于 Union[Tags, MyOtherEnum](这很笨拙)甚至只是 Any (这失去了使用类型检查器的好处)。

此策略还意味着 mypy 将无法通过考虑实际枚举值来进行更复杂的类型推断和缩小。不过,这主要仅在您使用文字枚举时才有意义。


如果担心这些问题,您可能不得不求助于更强力的方法,例如:

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 常量在运行时始终为假,但类型检查器认为它始终为真。

但是如果你打算直接告诉 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 的脚本来缓解这种情况,但这也不是最理想的,只是出于不同的原因。