理解造轮子的程度

造轮子造轮子,造法也有很多种,我们可以从零件厂采购轮毂、轮胎,自己组装,也可以从冶金、找橡胶树资源开始。

1.1 整体引用

这一种方法省事,相当于只把内部的组件打乱重组,包装成一个新的样子出来。Pipenv 即属此类,它其实是由 pip(安装器)、virtualenv(虚拟环境)、pip-tools(依赖解析)几大部分组合而成,连接调度的方式居然是通过 subprocess call。

所以这里面子进程启动、输出结果解析,都是耗时的。其余的次要组件,包括依赖树显示、依赖安全性检查等,无一例外都是通过内嵌别的库实现的。这种方法,很懒,引用作者 Kenneth Reitz 本人的话,叫做

I (re)design beautiful APIs

这种有一大缺点,即无论要做什么,例如“缺陷修复”、feature 引入都非常依赖上游库的更新,非常不自由。

比如要安装一个包,用这种方法实现出来是这个样子:

1
2
3
def install_requirement(requirement):
# requirement是符合PEP508规范的依赖格式
subprocess.check_call([pip_path, "install", "--no-deps", requirement])

1.2 使用内部API

当对于上游库的修改多到了一定程度,一气之下,决定化整为零,把依赖的库拆散,只取它内部的结构和接口来做。还是同样的功能,用 pip 内部的 API 实现起来,是这个样子的:

1
2
3
4
5
def install_requirement(requirement):
from pip._internal.req.constructors import install_req_from_line

ireq = install_req_from_line(requirement)
ireq.install(["--no-deps"])

看看那一串长长的 import string,人家都叫做 internal 还带下划线了,都挡不住你要从里面 import。

这就是这种方法的问题所在:非常不稳定。可能下个版本,pip 就升级了,API 会变得完全不同,那么就要做相应的改变。

1.3 自己动手,丰衣足食

改了几个版本以后,心里暗骂了一句 pip 的祖宗,责怪它为什么老改 API,一怒之下全部推倒重来,不求别人,全都自己实现,于是这一版代码变成了这样:

1
2
3
4
5
6
7
8
def install_requirement(requirement):
req = parse_install_requirement(requirement)
download_artifact(req)
if not req.is_wheel:
unpack_artifact(req)
build_wheel(req)
copy_modules(req)
install_scripts(req)

这里面省略了很多代码,这里的每一个函数,背后都是几十上百行的代码,因为 requirement 的类型是很多的,有本地的文件、有 Git 的地址,有的带 marker,有的带 extras……要覆盖到这所有的情况,难免出 bug。

这时就体现出用第三方库的优点来了:它们可能已经帮我们把所有的 bug 都踩过了,并受过生产环境的考验。

1.4 总结

所以造轮子要用哪种方法来造,是要经过仔细地考量的。

  • 用了第一种方法,结果发现需要定制很多

  • 用了第二种方法,发现被牵着走,依赖的 API 一改动,自己就要迭代

  • 用了第三种方法,结果发现天天都在修 bug