Python中如何给__import__动态引入的文件添加库(在线等,解决 V 币感谢)
现在我这边有个问题,由于存在多个不规则插件,所以在入口文件处,通过__import__加载路径对多个插件进行引用。
不过由于某些原因,调用插件时,插件并没有能继承入口文件中已经引入的库(包括公共库和 diy 的第三方库)。
所以现在需要想法子,需要给插件动态引入库,不然各个插件会因为缺失库报错。这里实在不想人工给每个插件都 import 一遍一坨库,法子有点蠢而且不实用。
解决尝试:
本来想试试 setattr 啥的,不过好像只能赋值,不能引用库。
又尝试给多个子级目录添加了__init__.py ,再在里面 import 库,也没有生效。
请问大佬们有没有相应的解决方案?
Python中如何给__import__动态引入的文件添加库(在线等,解决 V 币感谢)
有个不是很优雅的实现方法,假设你的插件在 /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
关键点说明:
- 优先使用importlib:比直接使用
__import__()更清晰、更符合现代Python实践 - 路径管理:通过
sys.path.insert(0, path)临时添加库路径,确保模块能找到其依赖 - 模块缓存:注意
sys.modules的缓存机制,需要时使用reload() - 路径恢复:添加的路径建议在finally块中恢复,避免污染全局环境
一句话总结:用importlib.import_module配合sys.path管理依赖路径。
某些原因到底是啥原因
以前这么做过,感觉有点傻…这边有几千个文件,每个都加,就算是批量也感觉有点难看。
引用六楼的回复,依据 你这样子应该是行不通的。如果可以这样做,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 ),排錯花了老半天。

