Python 3.x mypy importlib模块函数

Python 3.x mypy importlib模块函数,python-3.x,mypy,Python 3.x,Mypy,我使用importlib在运行时导入模块。这些模块是我的应用程序的插件,必须实现一个或多个模块级功能。我已经开始在我的应用程序中添加类型注释,我从mypy语句中得到一个错误 模块没有“生成配置”属性 其中,“生成配置”是模块功能之一 在本例中,模块中只需要有generate_配置功能。该函数接受一个dict参数 def generate_configuration(data: Dict[str, DataFrame]) -> None: ... 我一直在寻找如何指定模块的接口,但我能找到

我使用importlib在运行时导入模块。这些模块是我的应用程序的插件,必须实现一个或多个模块级功能。我已经开始在我的应用程序中添加类型注释,我从mypy语句中得到一个错误

模块没有“生成配置”属性

其中,“生成配置”是模块功能之一

在本例中,模块中只需要有generate_配置功能。该函数接受一个dict参数

def generate_configuration(data: Dict[str, DataFrame]) -> None: ...
我一直在寻找如何指定模块的接口,但我能找到的只是类接口。有人能给我指一些说明如何做到这一点的文档吗?我的谷歌fu在这一点上让我失望

加载此模块的代码如下所示。错误由最后一行生成

plugin_directory = os.path.join(os.path.abspath(directory), 'Configuration-Generation-Plugins')
plugins = (
    module_file
    for module_file in Path(plugin_directory).glob('*.py')
)
sys.path.insert(0, plugin_directory)
for plugin in plugins:
    plugin_module = import_module(plugin.stem)
    plugin_module.generate_configuration(directory, points_list)

importlib.import\u模块的类型注释仅返回
types.ModuleType

发件人:

这意味着
插件模块
的显示类型是
模块
——它没有您的特定属性

由于
mypy
是一个静态分析工具,它无法知道该导入的返回值是否有特定的接口

以下是我的建议:

  • 为您的模块创建一个类型接口(它不必实例化,它只会帮助mypy解决问题)

  • 制作一个导入模块的函数,您可能需要添加
    #type:ignore
    ,但如果您使用
    uuu import\uuuuu
    而不是
    import\u module
    ,则可能可以避免此限制

    def import_module_with_interface(modname: str) -> ModuleInterface:
        return __import__(modname, fromlist=['_trash'])  # might need to ignore the type here
    
  • 喜欢这些类型:)

  • 我用来验证这个想法的示例代码:

    class ModuleInterface:
        @staticmethod
        def compute_foo(bar: str) -> str: ...
    
    
    def import_module_with_interface(modname: str) -> ModuleInterface:
        return __import__(modname, fromlist=['_trash'])
    
    
    def myf() -> None:
        mod = import_module_with_interface('test2')
        # mod.compute_foo()  # test.py:12: error: Too few arguments for "compute_foo" of "ModuleInterface"
        mod.compute_foo('hi')
    

    我做了更多的研究,最终确定了一个稍微不同的解决方案,它使用

    解决方案仍然使用中的静态方法定义

    导入模块的代码然后使用()设置正确的类型

    plugin_directory = os.path.join(os.path.abspath(directory), 'Configuration-Generation-Plugins')
    plugins = (
        module_file
        for module_file in Path(plugin_directory).glob('*.py')
        if not module_file.stem.startswith('lib')
    )
    sys.path.insert(0, plugin_directory)
    for plugin in plugins:
        plugin_module = cast(ConfigurationGenerationPlugin, import_module(plugin.stem))
        plugin_module.generate_configuration(directory, points_list)
    

    为了让mypy开心,我不知道我对不得不向代码中添加ConfigurationGenerationPlugin类或cast()调用有何感受。但是,我现在将坚持使用它。

    如果没有看到相关代码,我们很难知道如何帮助您?你的函数类型签名是什么样的?相邻的几行是什么?@AnthonySottile我添加了更多的细节和函数签名。对不起,原来没有说得更清楚。你能不能也添加错误指向的那行?谢谢你指导我完成这项工作。我已经添加了生成错误的代码。谢谢。这为我指明了一个新的方向。但是,我没有创建类型化函数来导入插件模块,而是添加了一个内联类型声明。我会将您的回答标记为正确答案,并在下面添加一个新的回答,显示我的最终解决方案。
    class ModuleInterface:
        @staticmethod
        def compute_foo(bar: str) -> str: ...
    
    
    def import_module_with_interface(modname: str) -> ModuleInterface:
        return __import__(modname, fromlist=['_trash'])
    
    
    def myf() -> None:
        mod = import_module_with_interface('test2')
        # mod.compute_foo()  # test.py:12: error: Too few arguments for "compute_foo" of "ModuleInterface"
        mod.compute_foo('hi')
    
    from typing import Dict
    from pandas import DataFrame
    
    class ConfigurationGenerationPlugin(ModuleType):
    @staticmethod
        def generate_configuration(directory: str, points_list: Dict[str, DataFrame]) -> None: ...
    
    plugin_directory = os.path.join(os.path.abspath(directory), 'Configuration-Generation-Plugins')
    plugins = (
        module_file
        for module_file in Path(plugin_directory).glob('*.py')
        if not module_file.stem.startswith('lib')
    )
    sys.path.insert(0, plugin_directory)
    for plugin in plugins:
        plugin_module = cast(ConfigurationGenerationPlugin, import_module(plugin.stem))
        plugin_module.generate_configuration(directory, points_list)