类型提示使用 importlib 动态导入模块

Typehint importing module dynamically using importlib

给点如下:

import importlib

module_path = "mod"
mod = importlib.import_module(module_path, package=None)
print(mod.Foo.Bar.x)

其中 mod.py 是:

class Foo:
    class Bar:
        x = 1

mypy file.py --strict raises the following error:

file.py:7: error: Module has no attribute "Foo"  [attr-defined]

我想知道应该如何进行类型提示,或者这是否是通常会被 # type: ignore[attr-defined] 忽略的东西(假设代码是必要的, 唯一的选择是类型提示或忽略类型提示)?


为什么我在这种情况下使用 importlib

importlib 的使用方式是有一些路径:

x.y.<changes>.z

其中<changes>是动态的,其他都是固定的。我相信该模块将包含正在调用的属性,但由于 <changes>importlib 用于导入。

可以概括为:

I do not know precisely which module I will be importing, but I know it will have a class Foo in it.

正如@MisterMiyagi 所暗示的那样,我认为这里的解决方案是使用结构化而不是名义上的子类型化。 Nominal 子类型是我们使用直接 class 继承来定义类型关系的地方。例如,collections.Counterdict 的子类型,因为它直接继承自 dict。然而,结构 子类型是我们根据 class 具有的某些属性或它显示的某些行为来定义类型的地方。 inttyping.SupportsFloat 的子类型,不是因为它直接继承自 SupportsFloat(不是),而是因为 SupportsFloat 被定义为某个 接口int 满足该接口。

当类型提示时,我们可以使用 typing.Protocol 定义结构类型。在这种情况下,您可以像这样满足 MyPy:

import importlib
from typing import cast, Protocol

class BarProto(Protocol):
    x: int
    
class FooProto(Protocol):
    Bar: type[BarProto]
    
class ModProto(Protocol):
    Foo: type[FooProto]

module_path = "mod"
mod = cast(ModProto, importlib.import_module(module_path, package=None))

print(mod.Foo.Bar.x)

reveal_type(mod)
reveal_type(mod.Foo)
reveal_type(mod.Foo.Bar)
reveal_type(mod.Foo.Bar.x)

我们在这里定义了几个接口:

  • BarProto:为了满足这个接口,一个类型必须有一个 x 类型的属性 int.
  • FooProto:为了满足此接口,类型必须具有属性 Bar,即 class 的实例满足 BarProto 协议。
  • ModProto:为了满足此接口,类型必须具有属性 Foo,即 class 的实例满足 FooProto 协议。

然后,在导入模块时,我们使用 typing.cast 向类型检查器断言我们正在导入的模块满足 ModProto 协议。

运行 它 through MyPy,它告诉我们它已经推断出以下类型:

main.py:18: note: Revealed type is "__main__.ModProto"  
main.py:19: note: Revealed type is "Type[__main__.FooProto]"
main.py:20: note: Revealed type is "Type[__main__.BarProto]"
main.py:21: note: Revealed type is "builtins.int"

在 python here and here 中阅读有关结构子类型的更多信息。