V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
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
louisyoungx
V2EX  ›  Python

一行代码动态加载 Python 库依赖

  •  
  •   louisyoungx ·
    louisyoungx · 2022-02-22 19:58:26 +08:00 · 3602 次点击
    这是一个创建于 1039 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前几天在一个开源项目里遇到好多用户反馈,不会安装依赖,或者执行 pip install -r requirements.txt 没有反应。

    可能造成的原因有很多种,一一排查起来也很麻烦。

    想一劳永逸解决这个问题,一般大家都是到 site-packages 里面把所需要的包导出来,放到项目根目录。

    但这样终究太过粗糙,不符合 Python 优雅的个性。

    所以我就想,能不能动态引入包,如果没有的话,再调用 pip 下载。最后也差不多实现了我的设想。

    我大概查了一下,现在好像没有人用过这个方案,我自己使用感觉还是很方便的,分享给大家。

    虽然想打成 library 给大家下载的,后来想到这又要依赖 pip ,违背了做动态依赖的本意

    所以我推荐是使用 快速开始 - 注入代码运行 中的方式

    快速开始

    通过 pip 安装运行

    PyPI 下载 dypend依赖包

    pip install dypend
    

    在本地生成 requirements.txt 依赖文件

    pip freeze >  requirements.txt
    

    在项目的入口文件的最上层引入 dypend ,不用更改任何其他代码

    import dypend
    

    这时 dypend会检查你的 Python 环境中是否都有 requirements.txt 中的包,如果没有, dypend会调用 pip下载。

    注入代码运行

    在本地生成 requirements.txt 依赖文件

    pip freeze >  requirements.txt
    

    在项目的入口文件的最上层添加如下代码,不用更改任何其他代码

    import os
    import re
    REQUIREMENTS = os.getcwd() + '/requirements.txt'
    def getDepends():
        requirements = open(REQUIREMENTS, 'r')
        libs = requirements.readlines()
        libList = []
        for lib in libs:
            try:
                name = re.search("^.+(?===)", lib).group(0)
                version = re.search("(?<===).+$", lib).group(0)
                libDict = {
                    "name": name,
                    "version": version
                }
                libList.append(libDict)
            except:
                continue
        return libList
    def importLib():
        """Load python dependent libraries dynamically"""
    
        libList = getDepends()
    
        from pip._internal import main as pip_main
        import importlib
    
        def install(package):
            pip_main(['install', package])
    
        createVar = locals()
    
        for lib in libList:
            print(lib)
            try:
                createVar[lib["name"]] = importlib.import_module(lib["name"])
            except Exception as e:
                try:
                    install(f'{lib["name"]}=={lib["version"]}')
                    createVar[lib["name"]] = importlib.import_module(lib["name"])
                except Exception as e:
                    print(e)
    importLib
    

    这时 dypend 会检查你的 Python 环境中是否都有 requirements.txt 中的包,如果没有,你会看到 depend 帮你自动下载。

    19 条回复    2022-05-01 09:18:20 +08:00
    louisyoungx
        1
    louisyoungx  
    OP
       2022-02-22 20:16:36 +08:00
    https://github.com/louisyoungx/dypend
    项目地址在这~
    话说朋友们觉得这个有用吗=_=
    pursuer
        2
    pursuer  
       2022-02-22 20:43:32 +08:00
    动态引入依赖还见过更 tricky 的方式是加入到 sys.meta_path ,在 import 到的时候发现没有再去下,比如 http_import 啥的
    louisyoungx
        3
    louisyoungx  
    OP
       2022-02-22 21:06:49 +08:00
    @pursuer 很妙的点子
    ClericPy
        4
    ClericPy  
       2022-02-22 21:09:39 +08:00
    想象不出什么场景会这样... 也用过 pipreqs 感觉不是我的习惯, 不是我的硬需求, 不过爱动手还是好事

    之前我就把依赖打到 zip 里, 对方有 python 就能执行, 跨版本跨平台的话, 就惰性安装避免依赖有问题, 反正从头到尾就运行一个 python xxx.zip 甚至可以写上 shebang 直接 ./xxx.pyz... 这两天反而在琢磨一句 curl 部署代码干干净净地

    现在给 Windows 上分享直接 nuitka 也用不了半分钟
    louisyoungx
        5
    louisyoungx  
    OP
       2022-02-22 21:14:19 +08:00
    @ClericPy 是我有一个 repo 的用户有大半都是没接触过 python 的,给我发了很多依赖报错的 issue (笑~
    ClericPy
        6
    ClericPy  
       2022-02-22 21:50:46 +08:00
    @louisyoungx 哈哈... Windows 的话直接丢 exe 他们不放心么. 用户挺多啊, 怎么省事怎么来吧, 一般发布还是环境隔离比较好, 上次被一个不向后兼容的 aiohttp 升级坑了我仨服务器

    如果不嫌麻烦, 可以拿我的 Zipapps 把依赖和远吗打包成一个 .py 文件(实际是 zip), 然后当地用户双击就运行了
    louisyoungx
        7
    louisyoungx  
    OP
       2022-02-22 21:53:40 +08:00
    @ClericPy 是啊,后来给 windows 用户打包了个 GUI ,结果 bug 超级多,我现在准备拿 React Native 重写这个 GUI🤣
    louisyoungx
        8
    louisyoungx  
    OP
       2022-02-22 21:54:47 +08:00
    @ClericPy 我研究下 Zipapps ,很巧妙的点子
    ClericPy
        9
    ClericPy  
       2022-02-22 21:57:01 +08:00
    @louisyoungx

    GUI 打包 exe 的 bug 没感觉到啥, 兼容性是真头疼, 后来改 web ui 了... 只能祝顺利了
    louisyoungx
        10
    louisyoungx  
    OP
       2022-02-22 22:02:37 +08:00
    @ClericPy 哈哈我用的是 web ui 套壳的 GUI ,不是 pyqt 这些,所以超多 bug (包括打开空白,关窗口进程没结束之类)
    huntzhan
        11
    huntzhan  
       2022-02-22 22:02:54 +08:00
    最佳实践的 Python 项目已经淘汰了 `requirements.txt`,目前建议的方式是将依赖声明放在 `setup.cfg`
    ClericPy
        12
    ClericPy  
       2022-02-22 22:03:35 +08:00
    @louisyoungx

    从 shiv 抄的点子... 就是自带的 zipimport 那套 PEP. 只不过加了不默认解压以及自动解压和避免重复解压啥的, 文档写太乱有问题直接 issue 里骂
    huntzhan
        13
    huntzhan  
       2022-02-22 22:04:10 +08:00
    另外 infra 工具的一个原则是尽可能没有代码侵入,如果要求别人 import 才能用,那么不会有人用的
    huntzhan
        14
    huntzhan  
       2022-02-22 22:07:14 +08:00
    最后,自动下载依赖这个事情,个人觉得是伪需求,个人觉得所有开发环境的状态必须完全掌控在开发者的手上,如果我执行了一个程序,结果环境变了,这个就非常反直觉
    louisyoungx
        15
    louisyoungx  
    OP
       2022-02-22 22:18:40 +08:00
    @huntzhan 这个确实没有使用场景,现在唯一用处是让别人不要再给我的 repo 发依赖报错的 issue🤣当然那些人甚至不会 Python 。不过想问下在 setup.cfg 应该是库 library 的依赖申明吧,现在各种主流 Python 仓库用的还是 requirements.txt 多(除了上传到 PyPI 的库)包括 serverless 都会默认查找仓库里 requirements.txt 安装依赖。
    g00001
        16
    g00001  
       2022-02-22 22:50:04 +08:00
    Python 做界面、生成 EXE 其实只要用 aardio + Python 可以省很多事。
    https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzA3Njc1MDU0OQ==&action=getalbum&album_id=2270340412479438855&scene=173&subscene=10000&sessionid=0&enterid=1645540841&from_msgid=2650932062&from_itemidx=1&count=3&nolastread=1#wechat_redirect

    aardio 里的库一直是按需动态加载,生成 EXE 也是按需发布。
    也可以用这种方式引用 Python 模块,例如:

    import py3;
    import py3.lib.numpy;
    import py3.lib.matplotlib;
    import py3.lib.tkinter;

    而且这种是绿色 Python 运行时,不影响系统安装的 Python 环境,复制到哪台电脑都可以直接运行。
    frostming
        17
    frostming  
       2022-02-23 14:25:54 +08:00
    你这个只能做玩具满足非常狭窄的使用场景

    因为 import name 和 package name 有可能是不一样的啊。
    frostming
        18
    frostming  
       2022-02-23 14:27:41 +08:00
    不该省的不要总想着省,运行个脚本,往环境里塞了一堆不知道哪来的包,这个副作用太可怕了。
    Explicit is better than implicit
    我知道小白搞不懂这个东西不知道怎么做,但他总是要搞懂的啊
    opengo
        19
    opengo  
       2022-05-01 09:18:20 +08:00
    @louisyoungx 提供一个 docker 镜像会不会也方便很多
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2695 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 04:03 · PVG 12:03 · LAX 20:03 · JFK 23:03
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.