动态属性设置时绕过 mypy 的“模块没有属性”

Inf*_*ity 7 python python-3.x python-3.6 mypy python-typing

我有以下代码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__)
Run Code Online (Sandbox Code Playgroud)

但是当我在其他文件上使用它时, mypy (正确地)无法识别属性:

import tags
print(tags.A)  # Module has no attribute "A"
Run Code Online (Sandbox Code Playgroud)

Python 3.6 有没有可能绕过这个问题?

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

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

Mic*_*x2a 2

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

from tags import Tags
Run Code Online (Sandbox Code Playgroud)

..这将使我可以通过在任何地方进行操作来引用枚举项,Tags.A而无需大惊小怪。


但是,如果您确实想继续从模块命名空间引用这些项目,一种方法是在以下位置定义一个__getattr__函数tags.py

class Tags(enum.Flag):
    NONE = 0

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

def __getattr__(name: str) -> Tags:
    return Tags[name]
Run Code Online (Sandbox Code Playgroud)

在运行时,如果您尝试访问模块对象中未直接定义的某些属性,Python 将尝试调用此函数tags。Mypy 理解此约定并假设如果__getattr__已定义,则该模块是“不完整的”。因此,它将使用返回类型作为您尝试访问的任何缺失属性的类型。

不过,您可能仍然希望globals().update(Tags.__members__)主要作为性能优化,以避免__getattr__在运行时实际调用函数。

这种策略只有在tags.py只包含一个枚举的情况下才真正有效——否则,你需要将返回类型设置为类似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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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