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

2022-02-22 19:58:26 +08:00
 louisyoungx

前几天在一个开源项目里遇到好多用户反馈,不会安装依赖,或者执行 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 帮你自动下载。

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

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

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

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

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

从 shiv 抄的点子... 就是自带的 zipimport 那套 PEP. 只不过加了不默认解压以及自动解压和避免重复解压啥的, 文档写太乱有问题直接 issue 里骂
huntzhan
2022-02-22 22:04:10 +08:00
另外 infra 工具的一个原则是尽可能没有代码侵入,如果要求别人 import 才能用,那么不会有人用的
huntzhan
2022-02-22 22:07:14 +08:00
最后,自动下载依赖这个事情,个人觉得是伪需求,个人觉得所有开发环境的状态必须完全掌控在开发者的手上,如果我执行了一个程序,结果环境变了,这个就非常反直觉
louisyoungx
2022-02-22 22:18:40 +08:00
@huntzhan 这个确实没有使用场景,现在唯一用处是让别人不要再给我的 repo 发依赖报错的 issue🤣当然那些人甚至不会 Python 。不过想问下在 setup.cfg 应该是库 library 的依赖申明吧,现在各种主流 Python 仓库用的还是 requirements.txt 多(除了上传到 PyPI 的库)包括 serverless 都会默认查找仓库里 requirements.txt 安装依赖。
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
2022-02-23 14:25:54 +08:00
你这个只能做玩具满足非常狭窄的使用场景

因为 import name 和 package name 有可能是不一样的啊。
frostming
2022-02-23 14:27:41 +08:00
不该省的不要总想着省,运行个脚本,往环境里塞了一堆不知道哪来的包,这个副作用太可怕了。
Explicit is better than implicit
我知道小白搞不懂这个东西不知道怎么做,但他总是要搞懂的啊
opengo
2022-05-01 09:18:20 +08:00
@louisyoungx 提供一个 docker 镜像会不会也方便很多

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/835749

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX