Typehint 使用 importlib 动态导入模块

bax*_*axx 6 python type-hinting python-importlib mypy python-typing

给出如下内容:

import importlib

module_path = "mod"
mod = importlib.import_module(module_path, package=None)
print(mod.Foo.Bar.x)
Run Code Online (Sandbox Code Playgroud)

哪里mod.py

class Foo:
    class Bar:
        x = 1
Run Code Online (Sandbox Code Playgroud)

mypy file.py --strict 引发以下错误

file.py:7: error: Module has no attribute "Foo"  [attr-defined]
Run Code Online (Sandbox Code Playgroud)

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


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

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

x.y.<changes>.z
Run Code Online (Sandbox Code Playgroud)

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

可以概括为:

我不确切地知道我将导入哪个模块,但我知道它将有一个Foo

Ale*_*ood 4

正如 @MisterMiyagi在评论中提到的,我认为这里的解决方案是使用结构性子类型,而不是名义性子类型。名义子类型是我们使用直接类继承来定义类型关系的地方。例如,collections.Counter是 的子类型,dict因为它直接继承自dict然而,结构子类型是我们根据类具有的某些属性或它显示的某些行为来定义类型的地方。int是 的子类型,typing.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)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

在此处此处阅读有关 python 结构子类型的更多信息。