在动态属性设置上绕过 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 中有什么方法可以绕过这个吗?
已知解决方案(对我来说不够好):
- 每次使用
tags.A
时都使用# type: ignore
- 使用
tags.Tags.A
- 在模块级别使用
__getitem__
(仅适用于 Python 3.7)
就个人而言,我会做的是修改我的导入来做:
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
的脚本来缓解这种情况,但这也不是最理想的,只是出于不同的原因。
我在 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)
就个人而言,我会做的是修改我的导入来做:
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
的脚本来缓解这种情况,但这也不是最理想的,只是出于不同的原因。