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