Python中如何给__import__动态引入的文件添加库(在线等,解决 V 币感谢)

现在我这边有个问题,由于存在多个不规则插件,所以在入口文件处,通过__import__加载路径对多个插件进行引用。

不过由于某些原因,调用插件时,插件并没有能继承入口文件中已经引入的库(包括公共库和 diy 的第三方库)。

所以现在需要想法子,需要给插件动态引入库,不然各个插件会因为缺失库报错。这里实在不想人工给每个插件都 import 一遍一坨库,法子有点蠢而且不实用。

解决尝试:

本来想试试 setattr 啥的,不过好像只能赋值,不能引用库。
又尝试给多个子级目录添加了__init__.py ,再在里面 import 库,也没有生效。

请问大佬们有没有相应的解决方案?


Python中如何给__import__动态引入的文件添加库(在线等,解决 V 币感谢)

29 回复

有个不是很优雅的实现方法,假设你的插件在 /plugin 目录下,你在程序启动时创建(如果已经存在则删除后重建)一个 /plugin 目录,然后把源 plugin 目录下的文件一个个的拷贝过去,在拷贝的时候,只要是.py 文件就在开头追加一段你需要的"import xxx;import xxx",最后再用__import_("._plugin/xxxxx")


# 核心解决方案:使用importlib的import_module替代__import__,并通过sys.modules管理模块
import sys
import importlib

def dynamic_import_with_deps(module_name, dependency_paths=None):
    """
    动态导入模块并添加依赖路径
    
    Args:
        module_name: 要导入的模块名(如"my_module")
        dependency_paths: 需要添加到sys.path的依赖路径列表
    """
    # 1. 添加依赖路径到Python搜索路径
    if dependency_paths:
        original_sys_path = sys.path.copy()  # 备份原始路径
        for path in dependency_paths:
            if path not in sys.path:
                sys.path.insert(0, path)  # 优先搜索添加的路径
    
    try:
        # 2. 动态导入模块
        module = importlib.import_module(module_name)
        
        # 3. 如果模块需要重新加载(已存在的情况)
        if module_name in sys.modules:
            module = importlib.reload(module)
            
        return module
        
    finally:
        # 4. 恢复原始sys.path(可选,根据需求决定)
        if dependency_paths:
            sys.path = original_sys_path

# 使用示例
if __name__ == "__main__":
    # 假设需要导入的模块在自定义路径中
    custom_paths = [
        "/path/to/your/library",
        "/another/path/with/dependencies"
    ]
    
    # 动态导入模块
    try:
        my_module = dynamic_import_with_deps("my_module", custom_paths)
        print(f"成功导入模块: {my_module}")
        print(f"模块位置: {my_module.__file__}")
    except ModuleNotFoundError as e:
        print(f"导入失败: {e}")
    except Exception as e:
        print(f"其他错误: {e}")

# 替代方案:直接操作sys.modules(高级用法)
def direct_module_load(module_path):
    """直接通过文件路径加载模块"""
    import importlib.util
    
    spec = importlib.util.spec_from_file_location("dynamic_module", module_path)
    module = importlib.util.module_from_spec(spec)
    sys.modules["dynamic_module"] = module  # 注册到sys.modules
    spec.loader.exec_module(module)
    return module

关键点说明:

  1. 优先使用importlib:比直接使用__import__()更清晰、更符合现代Python实践
  2. 路径管理:通过sys.path.insert(0, path)临时添加库路径,确保模块能找到其依赖
  3. 模块缓存:注意sys.modules的缓存机制,需要时使用reload()
  4. 路径恢复:添加的路径建议在finally块中恢复,避免污染全局环境

一句话总结:用importlib.import_module配合sys.path管理依赖路径。

某些原因到底是啥原因

以前这么做过,感觉有点傻…这边有几千个文件,每个都加,就算是批量也感觉有点难看。

main.py


# -- coding: utf-8 --

import sys
import time

plugin_util = import(“plugins.util”, globals(), locals(), ‘util’)


plugins/util.py

import sys

g_vars = globals()

for name in sys.modules:
g_vars[name] = sys.modules[name]

print(time.clock())

你是指这个意思吗?把 sys 中的 modules 展开到 plugin 的全局变量里?

感谢回复,另外简单表述下,比如存在 plugins/util.py 文件,本来这文件可能本身没有 import sys 或者其他第三方库,我想办法需要在 main.py 里面,把 import sys,赋给 plugins/util.py [可能有很多 plugins,都需要赋给] ,然后保证在调用 plugins/util.py 时候不会出错。

引用六楼的回复,依据![python 包导入机制]( https://blog.csdn.net/tz_zs/article/details/77018298) 你这样子应该是行不通的。如果可以这样做,python 的包导入将毫无安全性可言。我觉得应该从代码层面去彻底避免这个问题。

importlib 了解下

还是非常感谢,不过我这边 plugin 太多了,都去注册一下某个库文件不太现实。



两位的意思是 importlib 有个设置 globals 和 locals 的选项么?如果是的话可能有点尴尬,这特性好像是 3.x 引入的,我这边是 2.7,似乎没有这两个扩展选项。
见:
<br><a target="_blank" href="https://docs.python.org/2.7/library/importlib.html" rel="nofollow noopener">https://docs.python.org/2.7/library/importlib.html</a><br>

pkgutil + importlib

具体使用可以参考 scrapy 的以下两个模块:
walk_modules https://github.com/scrapy/scrapy/blob/master/scrapy/utils/misc.py
iter_spider_classes https://github.com/scrapy/scrapy/blob/master/scrapy/utils/spider.py

谢谢指教,不过看起来好像问题复杂化了,貌似是换了两种 import 方式么。
弱弱问句,这个能让我这边调用的插件,继承我入口文件的已经加载的库么…
主要没看到特别的说明和参数表示能做到这点…

之前我有一个需求是希望可以运行时指定映射的 model
我是 import_module 模块做的:

def migrate(model_path: str = None):
“”"
定义一个实现 orm 映射 model 到 DB 的方法
因为在 commit 之前,所有的表创建与操作实际上是在内存里
试着用实例化某个模型的方式来实现自由映射模型
避免用 create_all()来映射所有继承 Base 的模型
但不行
所以尝试动态导入模型来自由映射
main_dir/
test/
models
import_module(“test.models”)
“”"
try:
# 动态导入要映射的模型
import_module(model_path + “.” + “models” if model_path else “models”)
# 把表创建进内存
Base.metadata.create_all()
# 把内存里的表写进数据库
db_session.commit()

except (ImportError, TypeError) as e:
print(e)
db_session.rollback()

还有个比较暴力的办法
import sys,imp

f = open(“plugin/xxx.py”,‘r’)
code = “import A \n import B \n” + f.read()
f.close()
module = imp.new_module(‘plugin.xxx’)
exec code in mymodule.dict
sys.module[‘plugin.xxx’] = module

如果是 python3 的话应该这样
from types import ModuleType
import sys

f = open(“plugin/xxx.py”,‘r’)
code = “import A \n import B \n” + f.read()
f.close()
mod = ModuleType(‘plugin.xxx’, ‘’)
exec(code, mod.dict)
sys.module[‘plugin.xxx’] = module

但是上述方法在出现 plugin 之间的相互引用的时候还是容易导致错误,这个需要用 import_hook 进一步优化了

明儿再看看,我白天就是没有找到问题根源,也就是关于 celery subtask 调用插件带来的库不继承问题。
这法子估计能行,不过插件多了以后估计会比较慢,我明儿看看。实在不行只能批量加在插件头部,或者这样弄了。
兄 dei,讲真你这写的不错的。不过好像跟我需求不太一样啊。我不是只找动态映射啊,是库继承不了的问题。


主要的流程很简单:
一,是从特定模块路径递归导入模块,并将返回导入的模块列表。walk_modules 就使用了 importlib.import_module 来导入,和 pkgutil.iter_modules 来搜索子模块(自 Python 3.3 开始直接基于 importlib 实现的),没别的复杂设计。
二,就是从一导入的模块列表里寻找特定类,比如找到特定类的子类什么的,这个你可以自己决定怎么写(比如检测模块中有某个名字的方法)。

建议 plugin 的基类和 plugin 实现之类的放在不同目录,避免使用如上方法自动搜索和导入的时候出现重复操作。

这个和你已经加载的插件并不冲突,不过你可以在流程一或二阶段跳过已经加载的模块(注册下已加载的模块名字呗)。

emmmm,原谅我有点笨,我看过那两段代码,您也写的蛮清晰的。不过这个为啥能让我那边入口函数的库能够被子模块继承,这点我没太懂…

说实话,我觉得你的实现方法并不能解决题主在 3#描述的问题,你的方案只是解决了批量导入模块的问题,却没办法在导入模块前动态给模块的开头加上 import

应该是 6#刚才打错了

确实没办法。实际上我也没有找到 Python 模块注入 100%可行的办法,确切的说没有找到正常的在 import 前设置符号表的方法。如果他的插件模块在模块域就使用了未导入的模块名字,那目前我没有找到办法,如果是在类或函数时还好,批量导入时对那个模块设置下模块名字就好了。

伪代码:
def walk_modules(path):
… …
… mod = import_module(path)
… mod.sys = sys # or import_module(‘sys’)
… …

如果不愿意重构,就写个脚本批量处理下那些插件代码呗。

基本上除了我在#16 提供的使用 exec 的方法以外,想要动态重构一个模块还真的很难。你提供的方法只要模块能正常的被 import 就能把其他模块比如 sys 注入,但是如果在 import 的时候就直接报错 ImportError 之类的就无能为力了。而我提供的方法其实是完全自己手动模拟的 import 的过程,但是无法解决出现一个插件内部 import 另一个插件的情况(当然也可以在 read 之后把文件内部的 import 再 hook 一下,不过这样就非常的臃肿了)。
看楼主的描述,其实是那些个插件本身写的就有语法问题
至于楼主说的“插件居然不能继承入口文件里面引入的库文件”,其实这是个很合理的行为,每个.py 文件都有一个自己独立的作用域,换句话说,除非手动 import 否则各个模块之间应该互相不干扰,贝莱就没有所谓的入口文件的概念,自然也就不存在继承的问题

你都读入模块源代码拼接了 import 语句了,还不如写个脚本一次性处理下那些脚本。说真的,按描述看,楼主那些插件代码一塌糊涂,得重写才能避免那些问题。



讲真那些插件代码应该没问题,github 上搜罗的,不算是我的锅…
我想了想,可能是因为使用了 celery 的 subtask 调用的插件,才造成不能共享库的作用域,直接调用插件的话库本来可以共享作用域的。

emmmm,查了下,好像这条是 celery 任务自动运行所有已注册的 app 吧。我那边呢,是需要动态导入一批未知名的模块,然后再将它们注册成 celery app 再运行,估计这法子可能不大合适。



最后還是選了把需要包含的庫,寫進一個單獨的文件,然後再直接在所有插件的頭部插入 import…真香,居然還是用了老辦法…
最後還遇到個坑,workflow 的 chord 等等,win 下不支持的( celery v3.x ),排錯花了老半天。

回到顶部