首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python 学习手册
Python Cookbook
Python 基础教程
Python Sites
PyPI - Python Package Index
http://www.simple-is-better.com/
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
V2EX  ›  Python

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

  •  
  •   akmonde · 239 天前 · 1514 次点击
    这是一个创建于 239 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

    解决尝试:

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

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

    第 1 条附言  ·  239 天前
    @owenliang

    刚才讲的“某些原因”如下:

    celery 创建 subtask,然后再在 subtask 里面引入插件,插件居然不能继承入口文件里面引入的库文件,我也很无奈。

    本来起先以为是路径不对,后来入口文件直接引用原生库,再调用插件后插件里也没继承,引用不了该库。

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

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

    这个和你已经加载的插件并不冲突,不过你可以在流程一或二阶段跳过已经加载的模块(注册下已加载的模块名字呗)。
        20
    akmonde   239 天前
    @skinny emmmm,原谅我有点笨,我看过那两段代码,您也写的蛮清晰的。不过这个为啥能让我那边入口函数的库能够被子模块继承,这点我没太懂...
        21
    wwqgtxx   239 天前
    @skinny 说实话,我觉得你的实现方法并不能解决题主在 3#描述的问题,你的方案只是解决了批量导入模块的问题,却没办法在导入模块前动态给模块的开头加上 import
        22
    wwqgtxx   239 天前
    应该是 6#刚才打错了
        23
    skinny   239 天前
    @wwqgtxx 确实没办法。实际上我也没有找到 Python 模块注入 100%可行的办法,确切的说没有找到正常的在 import 前设置符号表的方法。如果他的插件模块在模块域就使用了未导入的模块名字,那目前我没有找到办法,如果是在类或函数时还好,批量导入时对那个模块设置下模块名字就好了。

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

    如果不愿意重构,就写个脚本批量处理下那些插件代码呗。
        24
    wwqgtxx   238 天前   ♥ 1
    @skinny 基本上除了我在#16 提供的使用 exec 的方法以外,想要动态重构一个模块还真的很难。你提供的方法只要模块能正常的被 import 就能把其他模块比如 sys 注入,但是如果在 import 的时候就直接报错 ImportError 之类的就无能为力了。而我提供的方法其实是完全自己手动模拟的 import 的过程,但是无法解决出现一个插件内部 import 另一个插件的情况(当然也可以在 read 之后把文件内部的 import 再 hook 一下,不过这样就非常的臃肿了)。
    看楼主的描述,其实是那些个插件本身写的就有语法问题
    至于楼主说的“插件居然不能继承入口文件里面引入的库文件”,其实这是个很合理的行为,每个.py 文件都有一个自己独立的作用域,换句话说,除非手动 import 否则各个模块之间应该互相不干扰,贝莱就没有所谓的入口文件的概念,自然也就不存在继承的问题
        25
    skinny   238 天前   ♥ 1
    @wwqgtxx 你都读入模块源代码拼接了 import 语句了,还不如写个脚本一次性处理下那些脚本。说真的,按描述看,楼主那些插件代码一塌糊涂,得重写才能避免那些问题。
        27
    akmonde   238 天前
    @skinny
    @wwqgtxx
    讲真那些插件代码应该没问题,github 上搜罗的,不算是我的锅...
    我想了想,可能是因为使用了 celery 的 subtask 调用的插件,才造成不能共享库的作用域,直接调用插件的话库本来可以共享作用域的。

    @baojiweicn2 emmmm,查了下,好像这条是 celery 任务自动运行所有已注册的 app 吧。我那边呢,是需要动态导入一批未知名的模块,然后再将它们注册成 celery app 再运行,估计这法子可能不大合适。
        28
    akmonde   237 天前
    @wwqgtxx
    @skinny
    最后還是選了把需要包含的庫,寫進一個單獨的文件,然後再直接在所有插件的頭部插入 import....真香,居然還是用了老辦法...
    最後還遇到個坑,workflow 的 chord 等等,win 下不支持的( celery v3.x ),排錯花了老半天。
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   3904 人在线   最高记录 5043   ·  
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 20ms · UTC 06:43 · PVG 14:43 · LAX 23:43 · JFK 02:43
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1