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