前几天在一个开源项目里遇到好多用户反馈,不会安装依赖,或者执行 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 帮你自动下载。
|  |      1louisyoungx OP | 
|  |      2pursuer      2022-02-22 20:43:32 +08:00 动态引入依赖还见过更 tricky 的方式是加入到 sys.meta_path ,在 import 到的时候发现没有再去下,比如 http_import 啥的 | 
|  |      3louisyoungx OP @pursuer 很妙的点子 | 
|  |      4ClericPy      2022-02-22 21:09:39 +08:00 想象不出什么场景会这样... 也用过 pipreqs 感觉不是我的习惯, 不是我的硬需求, 不过爱动手还是好事 之前我就把依赖打到 zip 里, 对方有 python 就能执行, 跨版本跨平台的话, 就惰性安装避免依赖有问题, 反正从头到尾就运行一个 python xxx.zip 甚至可以写上 shebang 直接 ./xxx.pyz... 这两天反而在琢磨一句 curl 部署代码干干净净地 现在给 Windows 上分享直接 nuitka 也用不了半分钟 | 
|  |      5louisyoungx OP @ClericPy 是我有一个 repo 的用户有大半都是没接触过 python 的,给我发了很多依赖报错的 issue (笑~ | 
|  |      6ClericPy      2022-02-22 21:50:46 +08:00 @louisyoungx 哈哈... Windows 的话直接丢 exe 他们不放心么. 用户挺多啊, 怎么省事怎么来吧, 一般发布还是环境隔离比较好, 上次被一个不向后兼容的 aiohttp 升级坑了我仨服务器 如果不嫌麻烦, 可以拿我的 Zipapps 把依赖和远吗打包成一个 .py 文件(实际是 zip), 然后当地用户双击就运行了 | 
|  |      7louisyoungx OP @ClericPy 是啊,后来给 windows 用户打包了个 GUI ,结果 bug 超级多,我现在准备拿 React Native 重写这个 GUI🤣 | 
|  |      8louisyoungx OP @ClericPy 我研究下 Zipapps ,很巧妙的点子 | 
|  |      9ClericPy      2022-02-22 21:57:01 +08:00 | 
|  |      10louisyoungx OP @ClericPy 哈哈我用的是 web ui 套壳的 GUI ,不是 pyqt 这些,所以超多 bug (包括打开空白,关窗口进程没结束之类) | 
|  |      11huntzhan      2022-02-22 22:02:54 +08:00 最佳实践的 Python 项目已经淘汰了 `requirements.txt`,目前建议的方式是将依赖声明放在 `setup.cfg` | 
|  |      12ClericPy      2022-02-22 22:03:35 +08:00 | 
|  |      13huntzhan      2022-02-22 22:04:10 +08:00 另外 infra 工具的一个原则是尽可能没有代码侵入,如果要求别人 import 才能用,那么不会有人用的 | 
|  |      14huntzhan      2022-02-22 22:07:14 +08:00 最后,自动下载依赖这个事情,个人觉得是伪需求,个人觉得所有开发环境的状态必须完全掌控在开发者的手上,如果我执行了一个程序,结果环境变了,这个就非常反直觉 | 
|  |      15louisyoungx OP @huntzhan 这个确实没有使用场景,现在唯一用处是让别人不要再给我的 repo 发依赖报错的 issue🤣当然那些人甚至不会 Python 。不过想问下在 setup.cfg 应该是库 library 的依赖申明吧,现在各种主流 Python 仓库用的还是 requirements.txt 多(除了上传到 PyPI 的库)包括 serverless 都会默认查找仓库里 requirements.txt 安装依赖。 | 
|      16g00001      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 环境,复制到哪台电脑都可以直接运行。 | 
|  |      17frostming      2022-02-23 14:25:54 +08:00 你这个只能做玩具满足非常狭窄的使用场景 因为 import name 和 package name 有可能是不一样的啊。 | 
|  |      18frostming      2022-02-23 14:27:41 +08:00 不该省的不要总想着省,运行个脚本,往环境里塞了一堆不知道哪来的包,这个副作用太可怕了。 Explicit is better than implicit 我知道小白搞不懂这个东西不知道怎么做,但他总是要搞懂的啊 | 
|  |      19opengo      2022-05-01 09:18:20 +08:00 @louisyoungx 提供一个 docker 镜像会不会也方便很多 |