现在我这边有个问题,由于存在多个不规则插件,所以在入口文件处,通过__import__加载路径对多个插件进行引用。
不过由于某些原因,调用插件时,插件并没有能继承入口文件中已经引入的库(包括公共库和 diy 的第三方库)。
所以现在需要想法子,需要给插件动态引入库,不然各个插件会因为缺失库报错。这里实在不想人工给每个插件都 import 一遍一坨库,法子有点蠢而且不实用。
解决尝试:
本来想试试 setattr 啥的,不过好像只能赋值,不能引用库。
又尝试给多个子级目录添加了__init__.py ,再在里面 import 库,也没有生效。
请问大佬们有没有相应的解决方案?
1
wwqgtxx 2018-08-28 16:29:47 +08:00
有个不是很优雅的实现方法,假设你的插件在 /plugin 目录下,你在程序启动时创建(如果已经存在则删除后重建)一个 /_plugin 目录,然后把源 plugin 目录下的文件一个个的拷贝过去,在拷贝的时候,只要是.py 文件就在开头追加一段你需要的"import xxx;import xxx",最后再用__import__("._plugin/xxxxx")
|
2
owenliang 2018-08-28 16:30:07 +08:00
某些原因到底是啥原因
|
4
owenliang 2018-08-28 17:12:01 +08:00
|
5
xuboying 2018-08-28 17:15:56 +08:00
|
6
akmonde OP |
7
owenliang 2018-08-28 17:42:00 +08:00 1
|
8
yufpga 2018-08-28 18:13:16 +08:00
引用六楼的回复,依据![python 包导入机制]( https://blog.csdn.net/tz_zs/article/details/77018298) 你这样子应该是行不通的。如果可以这样做,python 的包导入将毫无安全性可言。我觉得应该从代码层面去彻底避免这个问题。
|
9
baojiweicn2 2018-08-28 18:27:17 +08:00 via iPhone
importlib 了解下
|
11
akmonde OP @baojiweicn2
@xuboying 两位的意思是 importlib 有个设置 globals 和 locals 的选项么?如果是的话可能有点尴尬,这特性好像是 3.x 引入的,我这边是 2.7,似乎没有这两个扩展选项。 见: ``` https://docs.python.org/2.7/library/importlib.html ``` |
12
skinny 2018-08-28 20:53:19 +08:00
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 |
13
akmonde OP @skinny 谢谢指教,不过看起来好像问题复杂化了,貌似是换了两种 import 方式么。
弱弱问句,这个能让我这边调用的插件,继承我入口文件的已经加载的库么... 主要没看到特别的说明和参数表示能做到这点... |
14
firejoke 2018-08-28 23:05:46 +08:00
之前我有一个需求是希望可以运行时指定映射的 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() |
15
firejoke 2018-08-28 23:08:48 +08:00
|
16
wwqgtxx 2018-08-28 23:48:32 +08:00 1
还有个比较暴力的办法
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 |
17
wwqgtxx 2018-08-28 23:50:13 +08:00
但是上述方法在出现 plugin 之间的相互引用的时候还是容易导致错误,这个需要用 import_hook 进一步优化了
|
18
akmonde OP |
19
skinny 2018-08-29 07:56:42 +08:00
@akmonde
主要的流程很简单: 一,是从特定模块路径递归导入模块,并将返回导入的模块列表。walk_modules 就使用了 importlib.import_module 来导入,和 pkgutil.iter_modules 来搜索子模块(自 Python 3.3 开始直接基于 importlib 实现的),没别的复杂设计。 二,就是从一导入的模块列表里寻找特定类,比如找到特定类的子类什么的,这个你可以自己决定怎么写(比如检测模块中有某个名字的方法)。 建议 plugin 的基类和 plugin 实现之类的放在不同目录,避免使用如上方法自动搜索和导入的时候出现重复操作。 这个和你已经加载的插件并不冲突,不过你可以在流程一或二阶段跳过已经加载的模块(注册下已加载的模块名字呗)。 |
20
akmonde OP @skinny emmmm,原谅我有点笨,我看过那两段代码,您也写的蛮清晰的。不过这个为啥能让我那边入口函数的库能够被子模块继承,这点我没太懂...
|
21
wwqgtxx 2018-08-29 10:59:44 +08:00
@skinny 说实话,我觉得你的实现方法并不能解决题主在 3#描述的问题,你的方案只是解决了批量导入模块的问题,却没办法在导入模块前动态给模块的开头加上 import
|
22
wwqgtxx 2018-08-29 11:04:30 +08:00
应该是 6#刚才打错了
|
23
skinny 2018-08-29 14:38:41 +08:00
@wwqgtxx 确实没办法。实际上我也没有找到 Python 模块注入 100%可行的办法,确切的说没有找到正常的在 import 前设置符号表的方法。如果他的插件模块在模块域就使用了未导入的模块名字,那目前我没有找到办法,如果是在类或函数时还好,批量导入时对那个模块设置下模块名字就好了。
伪代码: def walk_modules(path): .... .... .... mod = import_module(path) .... mod.sys = sys # or import_module('sys') .... .... 如果不愿意重构,就写个脚本批量处理下那些插件代码呗。 |
24
wwqgtxx 2018-08-29 16:23:31 +08:00 1
@skinny 基本上除了我在#16 提供的使用 exec 的方法以外,想要动态重构一个模块还真的很难。你提供的方法只要模块能正常的被 import 就能把其他模块比如 sys 注入,但是如果在 import 的时候就直接报错 ImportError 之类的就无能为力了。而我提供的方法其实是完全自己手动模拟的 import 的过程,但是无法解决出现一个插件内部 import 另一个插件的情况(当然也可以在 read 之后把文件内部的 import 再 hook 一下,不过这样就非常的臃肿了)。
看楼主的描述,其实是那些个插件本身写的就有语法问题 至于楼主说的“插件居然不能继承入口文件里面引入的库文件”,其实这是个很合理的行为,每个.py 文件都有一个自己独立的作用域,换句话说,除非手动 import 否则各个模块之间应该互相不干扰,贝莱就没有所谓的入口文件的概念,自然也就不存在继承的问题 |
25
skinny 2018-08-29 17:19:51 +08:00 1
@wwqgtxx 你都读入模块源代码拼接了 import 语句了,还不如写个脚本一次性处理下那些脚本。说真的,按描述看,楼主那些插件代码一塌糊涂,得重写才能避免那些问题。
|
26
baojiweicn2 2018-08-29 17:42:01 +08:00
|
27
akmonde OP @skinny
@wwqgtxx 讲真那些插件代码应该没问题,github 上搜罗的,不算是我的锅... 我想了想,可能是因为使用了 celery 的 subtask 调用的插件,才造成不能共享库的作用域,直接调用插件的话库本来可以共享作用域的。 @baojiweicn2 emmmm,查了下,好像这条是 celery 任务自动运行所有已注册的 app 吧。我那边呢,是需要动态导入一批未知名的模块,然后再将它们注册成 celery app 再运行,估计这法子可能不大合适。 |