给出如下内容:
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
引发以下错误:
file.py:7: error: Module has no attribute "Foo" [attr-defined]
我想知道一个人应该如何去类型提示这,或者如果这是一些通常会被忽略的# type: ignore[attr-defined]
(假设代码是必要的,唯一的选项是类型提示或忽略类型提示)?
为什么我在这种情况下使用importlib
使用importlib
的方式是有一些路径:
x.y.<changes>.z
其中<changes>
是动态的,但其他是固定的。我相信该模块将包含正在调用的属性,但由于<changes>
,importlib
用于导入。
可以概括为:
我不知道我将导入哪个模块,但我知道它将有一个类
Foo
在它。
正如@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)
我们在这里定义了几个接口:
BarProto
:为了满足这个接口,类型必须有一个类型为int
的属性x
。FooProto
:为了满足这个接口,一个类型必须有一个属性Bar
,这是一个类的实例满足BarProto
协议。ModProto
:为了满足这个接口,一个类型必须有一个属性Foo
,这是一个类的实例满足FooProto
协议。
然后,在导入模块时,使用typing.cast
向类型检查器断言要导入的模块满足ModProto
协议。
通过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结构子类型的信息。