快捷:通过包来加速代码的开发
除了计算机本身,子程序是计算机科学中最伟大的发明。(引自《代码大全》)
通过包来实现代码复用
问题的提出
问题的提出可以总结为如下的两种情形:情形1来源于之前看到的一个问答网站上的用户提问:程序员需要记忆代码吗?情形2来源于实际工作场景的归纳总结:在代码工程A中,存在一个utils模块,该模块里面存放供该工程中其他代码文件所调用的一些通用子函数。该项目完结,进入一个新的项目,相关的代码工程称之为代码工程B,在开发的过程中,会遇到B中的utils模块需要与A中类似甚至一致的通用函数方法。此时可以手动将老项目中的utils模块中的相关函数代码复制一份过来,修修改改用起来。但这样做除了额外的工作量之外(哈哈是的,复制粘贴也需要工作量的),还会带来一个经典的软件问题:“平行维护”。相同(似)的代码被放置多处,每一处的修正、改进无法同步至其他处。
本文是尝试对上述问题的解答:以python语言为例,可以将这些在多个项目中通用的方法收集起来,然后构建、管理自己的pypi包。
制作自己的pypi包
注册pypi账号
pypi官网的账号为https://pypi.org/,可能需要vpn才能访问。注册完之后,应该具有以下三个要素:
- 账号名
- 密码
- token
构建pypi包
举一个最简单的例子,该pypi的包的名字为toytool,如果想安装该包的做法是:pip install toytool
。它的作用是提供通用库函数ReadImage,该函数支持opencv和pillow两种图像处理库的方式打开图片。
import ttool
from ttool.read import ReadImage
imgOpencv = ReadImage("1.png", "opencv") # 以opencv的方式打开图片
imgPillow = ReadImage("1.png", "pillow") # 以pillow的方式打开图片
toytool包的代码树如下图所示:
setup.py文件中的内容为:
import setuptools
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read() # 显示在pypi官网页面上的项目介绍
setuptools.setup(
name="toytool", # 决定了pip install toytool的时候包的名称
version="0.0.1",
author="HandsomeBoy",
author_email="xxx@163.com",
description="toy-tool", # 显示在pypi官网页面上的项目介绍(简介)
long_description=long_description,
long_description_content_type="text/markdown",
packages=setuptools.find_packages(),
python_requires=">=3.6",
install_requires=["pillow", "opencv-python", "opencv-python-headless"] # ReadImage的依赖项,在安装pip install toytool的时候,会安装该依赖项
)
ttool文件夹名称决定了在包调用的时候,import的名称,例如这里为:
import ttool
from ttool.read import ReadImage
read.py中的内容如下所示:
import cv2
import typing
from PIL import Image
def ReadImage(ImagePath: str, Mode: str = 'cv2') -> typing.Any:
"""Read image in one manner of opencv or pillow.
Args:
ImagePath: img file path.
Mode: one of ['pillow', 'cv2'].
"""
if mode == 'pillow':
img = Image.open(ImagePath)
elif mode == "opencv":
img = cv2.imread(ImagePath)
return img
上传pypi包
上传之前先安装pip install twine
。然后正式上传:
cd toytool
python3 setup.py sdist
twine upload dist/*
rethink
该方案在实际的工作中使用起来还是很不错的,当然本文举的这个是最简单的读图片的例子,实际上自己的通用函数包更加丰富:写图片,读json,写json,在图片上绘制文字,绘制形状等等都可以不断的纳入、丰富自己的pypi包里面。
有了pypi包,一定程度上降低了代码的复杂性:“在编写包时,需要考虑细枝末节。但一旦包写好之后,程序员就可以忘记这些细节,在不了解其内部工作原理的情况下使用该包”。具体到本文的例子,opencv和pillow打开图片的方式,一个为read一个为open,这些繁杂的信息在包封装之后归为统一ReadImage。关于如何看待程序员需要记忆代码的问题?问答网站上,大家回答的大概意图基本上是:“我不需要知道一切。我只需要知道在哪里可以找到它。”这回答有一定的道理,但本文可以作为该回答的一定补充。因为毕竟翻阅文档也需要耗费一定的时间,而且如果旁边有同事看着的时候,一些常规的操作,如果需要查阅文档,也显得挺尴尬,但同时程序员的大脑是最宝贵的资源,通过包来主导、统一自己的模式是一个降低记忆的手段。
有了pypi包,一定程度上避免了重复代码的问题,进而避免了“平行维护”的问题。
再有就是C++也可以总结自己的通用函数库,对应pypi包的管理方式称之为conan。后续计划另开一篇介绍cona包管理。