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

2018-08-28 15:51:38 +08:00
 akmonde

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

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

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

解决尝试:

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

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

2582 次点击
所在节点    Python
28 条回复
wwqgtxx
2018-08-28 16:29:47 +08:00
有个不是很优雅的实现方法,假设你的插件在 /plugin 目录下,你在程序启动时创建(如果已经存在则删除后重建)一个 /_plugin 目录,然后把源 plugin 目录下的文件一个个的拷贝过去,在拷贝的时候,只要是.py 文件就在开头追加一段你需要的"import xxx;import xxx",最后再用__import__("._plugin/xxxxx")
owenliang
2018-08-28 16:30:07 +08:00
某些原因到底是啥原因
akmonde
2018-08-28 16:39:28 +08:00
@wwqgtxx 以前这么做过,感觉有点傻..这边有几千个文件,每个都加,就算是批量也感觉有点难看。
owenliang
2018-08-28 17:12:01 +08:00
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 的全局变量里?
xuboying
2018-08-28 17:15:56 +08:00
akmonde
2018-08-28 17:30:45 +08:00
@owenliang 感谢回复,另外简单表述下,比如存在 plugins/util.py 文件,本来这文件可能本身没有 import sys 或者其他第三方库,我想办法需要在 main.py 里面,把 import sys,赋给 plugins/util.py [可能有很多 plugins,都需要赋给] ,然后保证在调用 plugins/util.py 时候不会出错。
owenliang
2018-08-28 17:42:00 +08:00
yufpga
2018-08-28 18:13:16 +08:00
引用六楼的回复,依据![python 包导入机制]( https://blog.csdn.net/tz_zs/article/details/77018298) 你这样子应该是行不通的。如果可以这样做,python 的包导入将毫无安全性可言。我觉得应该从代码层面去彻底避免这个问题。
baojiweicn2
2018-08-28 18:27:17 +08:00
importlib 了解下
akmonde
2018-08-28 19:44:40 +08:00
@owenliang 还是非常感谢,不过我这边 plugin 太多了,都去注册一下某个库文件不太现实。
akmonde
2018-08-28 19:56:19 +08:00
@baojiweicn2
@xuboying
两位的意思是 importlib 有个设置 globals 和 locals 的选项么?如果是的话可能有点尴尬,这特性好像是 3.x 引入的,我这边是 2.7,似乎没有这两个扩展选项。
见:
```
https://docs.python.org/2.7/library/importlib.html
```
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
akmonde
2018-08-28 22:52:56 +08:00
@skinny 谢谢指教,不过看起来好像问题复杂化了,貌似是换了两种 import 方式么。
弱弱问句,这个能让我这边调用的插件,继承我入口文件的已经加载的库么...
主要没看到特别的说明和参数表示能做到这点...
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()
firejoke
2018-08-28 23:08:48 +08:00
wwqgtxx
2018-08-28 23:48:32 +08:00
还有个比较暴力的办法
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
wwqgtxx
2018-08-28 23:50:13 +08:00
但是上述方法在出现 plugin 之间的相互引用的时候还是容易导致错误,这个需要用 import_hook 进一步优化了
akmonde
2018-08-29 00:21:26 +08:00
@yufpga 明儿再看看,我白天就是没有找到问题根源,也就是关于 celery subtask 调用插件带来的库不继承问题。
@wwqgtxx 这法子估计能行,不过插件多了以后估计会比较慢,我明儿看看。实在不行只能批量加在插件头部,或者这样弄了。
@firejoke 兄 dei,讲真你这写的不错的。不过好像跟我需求不太一样啊。我不是只找动态映射啊,是库继承不了的问题。
skinny
2018-08-29 07:56:42 +08:00
@akmonde
主要的流程很简单:
一,是从特定模块路径递归导入模块,并将返回导入的模块列表。walk_modules 就使用了 importlib.import_module 来导入,和 pkgutil.iter_modules 来搜索子模块(自 Python 3.3 开始直接基于 importlib 实现的),没别的复杂设计。
二,就是从一导入的模块列表里寻找特定类,比如找到特定类的子类什么的,这个你可以自己决定怎么写(比如检测模块中有某个名字的方法)。

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

这个和你已经加载的插件并不冲突,不过你可以在流程一或二阶段跳过已经加载的模块(注册下已加载的模块名字呗)。
akmonde
2018-08-29 09:15:25 +08:00
@skinny emmmm,原谅我有点笨,我看过那两段代码,您也写的蛮清晰的。不过这个为啥能让我那边入口函数的库能够被子模块继承,这点我没太懂...

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/483983

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX