Python进阶(4) | 创建Python库的模板工程 Python-lib-starter
文章目录
- Python进阶(4) | 创建Python库的模板工程 Python-lib-starter
- 1. 目的
- 2. Python-lib-starter 目录结构浅析
- 2.1 关键目录和文件
- 2.2 非关键目录和文件
- 3. moelib 目录分析
- 3.1 `__init__.py`
- 延迟评估类型注解的意义
- 使用 from __future__ import annotations
- 设置 `__version__` 等
- 3.2 `__main__.py`
- 4. tests 目录
- 4.1 `__init__.py`
- 4.2 `test_moelib.py`
- This can be replaced with tomllib if you are using Python 3.11+
- 5. 简化的代码
1. 目的
当我们使用 Python 写一些复用性较高的代码时候, 我们可以制作一个 Python 库, 用 pip install 的方式提供下载。
像 PyTorch, OpenCV 或 Pillow 这些工程, 都过于复杂庞大。 是否有一个“空壳”的 Python 工程, 它已经是一个 Python 包、 只需要我们填充核心功能代码呢? python-lib-starter (https://github.com/ShigureLab/python-lib-starter) 就是这样一个项目。
2. Python-lib-starter 目录结构浅析
git clone https://github.com/ShigureLab/python-lib-starter
cd python-lib-starter
code .
2.1 关键目录和文件
可以看到关键目录有两个:
moelib
: 库的核心代码tests
: 库的单元测试代码
关键文件只有一个:
setup.py
: 配置了安装功能
2.2 非关键目录和文件
非关键目录:
.github
: CI/CD 脚本.vscode
: VSCode 配置和扩展
非关键文件:
.editorconfig
: 代码风格配置文件.gitignore
: git 忽略文件.prettierignore
: 代码格式化工具 Pretty 的配置文件。js文件使用的。.prettierrc
: 配置 Prettier 代码格式化工具的选项和规则的。个人觉得比较冗余,有了 .editorconfig 基本够用justfile
: justfile 是 Just 命令运行器的配置文件,用于定义一组命令和指令,这些命令和指令可以通过 Just 工具来执行。Just 是一个命令行工具,旨在使运行项目中的特定任务(如构建、测试、部署等)变得简单快捷。它类似于 Makefile 用于 make 命令,但 Just 提供了更现代和更易于使用的语法。poetry.lock
: poetry.lock 文件是由 Poetry 依赖管理工具生成的一个锁文件,用于确保项目依赖的一致性。Poetry 是一个现代的 Python 包管理工具,它旨在简化包的安装、版本管理和打包过程。当你使用 Poetry 添加、更新或安装项目依赖时,Poetry 会创建或更新 poetry.lock 文件。poetry.toml
: 不知道。pyproject.toml
: pyproject.toml 文件是一个用于配置和管理 Python 项目的文件,它遵循 TOML(Tom’s Obvious, Minimal Language)格式。这个文件的引入旨在提供一个统一的配置文件来替代之前分散在多个文件(如 setup.py、requirements.txt、setup.cfg 等)中的配置信息。pyproject.toml 文件的规范由 PEP 518 首次引入,并在后续的提案中得到扩展,比如 PEP 621 规定了项目元数据的标准。
总的来说, 这些非关键目录和文件是锦上添花, 关键目录和文件中, 其实有了单元测试, 已经超过60分了, 在项目开发初期足够了。
3. moelib 目录分析
目录结构:
- moelib
- `__init__.py`
- `__main__.py`
3.1 __init__.py
# Meta information for the project.
from __future__ import annotations
__version__ = "0.1.3"
__author__ = "Nyakku Shigure"
__year__ = "2023"
__project_info__ = {
"name": __name__,
"version": __version__,
"copyright": f"{__year__}, {__author__}",
"author": __author__,
}
annotations 是“延迟评估类型注解”, 是 Python 3.10 中的默认行为, 3.10 版本之前只能在 __future__
里获取。
延迟评估类型注解的意义
在 Python 3.7 之前,类型注解(Type Hints)在定义时就会被评估。这意味着,如果你在类型注解中引用了尚未定义的类或自身类(用于表示类方法的返回值是其实例),就会导致 NameError 或者其他错误。
例如,考虑下面的代码:
class Node:
def add_child(self) -> Node: # 这里使用 Node 作为返回类型
pass
在 Python 3.7 之前,这段代码会引发错误,因为在 add_child 方法的注解中使用了 Node 类型,而在解释这行代码时 Node 类还没有完全定义。
使用 from future import annotations
通过使用 from future import annotations,所有的类型注解都会被自动视为字符串字面量。这样,它们就不会在定义时立即被评估,而是延迟到实际需要进行类型检查的时候(比如使用类型检查工具如 MyPy 进行静态类型检查时)。这解决了上面提到的问题,允许你自由地在类型注解中引用尚未定义的类或自身类。
from __future__ import annotations
class Node:
def add_child(self) -> Node: # 现在这样写不会引发错误
pass
总的来说,from __future__ import annotations
让你能够在 Python 代码中更灵活地使用类型注解,尤其是在处理复杂的类型定义和类的相互引用时。这个特性在 Python 3.10 及以后的版本中成为了默认行为,所以如果你使用的是 Python 3.10 或更高版本,就不需要再显式地导入这个特性了。
设置 __version__
等
语句 __version__ = "0.1.3"
的含义是将字符串 “0.1.3” 赋值给名为 version 的全局变量。这个字符串通常遵循语义化版本控制(Semantic Versioning),其中:
- 0 表示主版本号(Major version),当你做了不兼容的 API 修改时,需要增加这个数字。
- 1 表示次版本号(Minor version),当你以向下兼容的方式添加功能时,增加这个数字。
- 3 表示修订号(Patch version),当你做向下兼容的问题修正时,增加这个数字。
__project_info__ = {
"name": __name__,
"version": __version__,
"copyright": f"{__year__}, {__author__}",
"author": __author__,
}
__project_info__
是一个自定义的字典,用于存储关于项目的信息。这种做法并不常见,但它展示了如何在一个地方集中管理项目的元数据,包括项目名、版本、版权信息和作者信息。这些信息可以在项目的多个地方被引用或展示,比如在文档、项目首页或者版权声明中
3.2 __main__.py
from __future__ import annotations
import argparse
from moelib import __version__
def main() -> None:
parser = argparse.ArgumentParser(prog="moelib", description="A moe moe project")
parser.add_argument("-v", "--version", action="version", version=__version__)
args = parser.parse_args() # type: ignore
if __name__ == "__main__":
main()
from future import annotations
同前一节, 导入 “延迟评估类型注解”。
import argparse
导入参数解析库。
from moelib import version
导入自己写的 moelib 库里面的 __version__
变量。
def main() -> None:
parser = argparse.ArgumentParser(prog="moelib", description="A moe moe project")
parser.add_argument("-v", "--version", action="version", version=__version__)
args = parser.parse_args() # type: ignore
设置命令行参数, 可以用 python -m moelib -v
来执行,获取到版本好:
PS D:\github\python-lib-starter> pip install -e .
PS D:\github\python-lib-starter\moelib> python -m moelib -v
0.1.3
4. tests 目录
目录结构:
- tests
- `__init__.py`
- `test_moelib.py`
4.1 __init__.py
这个文件是空的。
4.2 test_moelib.py
通常单元测试文件的命名, 要么是 test
开头, 要么是 _test.py
结尾。
from __future__ import annotations
from pathlib import Path
# This can be replaced with tomllib if you are using Python 3.11+
import tomli as tomllib
from moelib import __version__
with Path("pyproject.toml").open("rb") as f:
project_info = tomllib.load(f)
def test_version():
assert __version__ == project_info["tool"]["poetry"]["version"]
我们主句分析:
from future import annotations
同前一节, 导入 “延迟评估类型注解”。
from pathlib import Path
导入 pathlib 库, 用于路径相关的操作。
This can be replaced with tomllib if you are using Python 3.11+
import tomli as tomllib
导入 tomlib, 用于解析 pyproject.toml
文件。
from moelib import version
从我们自己写的 moelib 库导入 __version__
变量。
with Path(“pyproject.toml”).open(“rb”) as f:
project_info = tomllib.load(f)
用 tomllib 解析 pyproject.toml
的内容。
def test_version():
assert version == project_info[“tool”][“poetry”][“version”]
创建了一个单元测试的测试用例, 检查 __version__
是否和 pyproject.toml 里的版本号一样。
个人觉得这个测试能体现一部分水平(了解单元测试),但是把版本号分散在两个地方(moelib/__init__.py
和 pyproject.toml
)无疑增加了维护的复杂性,其实没必要。实际上只要检查 __version__
包含三个字段,每个字段都是数字,就可以了:
def test_version():
#assert __version__ == project_info["tool"]["poetry"]["version"]
# check if __version__ is semver
assert __version__.count(".") == 2
# each part of semver must be integer
assert all(map(lambda x: x.isdigit(), __version__.split(".")))
5. 简化的代码
https://github.com/zchrissirhcz/python-lib-starter
去掉了 “不重要的代码”, 修改了单元测试, 适合项目最初开发时的本地开发。