Python 项目工程化最佳实践指南

Python 工程化这件事都没有一致的规范和项目办理的计划,或许是由于 Python 忽然鼓起的时间较短、用于开发大型项意图公司较少。所认为了协助内部的同学更好的处理 Python 工程化问题和同享下个人的开发习气和代码办理思路写下这篇文章。

依靠办理

在 PEP 518 和 pyproject.toml 引进之前。一个项目无法告知一个像 pip 这样的东西它需求什么样的构建东西来构建。现在 setuptools 有一个 setup_require 参数来指定构建项目所需的东西,可是除非你装置了 setuptools,否则你无法读取该设置,这意味着你不能声明你需求 setuptools 来读取 setuptools 中的设置。这个鸡和蛋的问题便是为什么像 virtualenv 这样的东西会默许装置 setuptools,以及为什么 pip 在运转一个 setup.py 时总是会注入 setuptools 和wheel,不论你是否显式地装置了它。你甚至不要尝试依靠于 setuptools 的一个特定版原本构建你的项目,由于你没有办法来指定版别; 不论用户可巧装置了什么,你都得将就运用。

在 PEP 518 之后你能够声明你的构建东西以及要求的版别。

在过去咱们或许会常常运用 requirements.txt 之类的文件来保存咱们项目所需的依靠,可是它并没有很好的办法去区分咱们在出产环境、开发环境、测验环境所需求的依靠有必要要分红多份文件独自声明,经过一些新的构建东西咱们就能够处理咱们的问题。并且独自用 requirements.txt 也不能声明咱们需求的 python 版别、体系环境等等。

在这次的内部项目开发中,我挑选的是 PDM,PDM 旨在成为下一代 Python 软件包办理东西。它开端是为个人兴趣而诞生的。假如你觉得 pipenv 或许 poetry 用着非常好,并不想引进一个新的包办理器,那么继续运用它们吧;但假如你发现有些东西这些东西不支撑,那么你很或许能够在 pdm 中找到。

Poetry 看起来也是个不错的挑选,Poetry 和 Pipenv 、PDM 相似,是一个 Python 虚拟环境和依靠办理东西,另外它还供给了包办理功用,比方打包和发布。你能够把它看做是 Pipenv 和 Flit 这些东西的超集。它能够让你用 Poetry 来一起办理 Python 库和 Python 程序。

假如你用的是 PDM 或许 Poetry 请先在项目目录中,经过 virtualenv 创立一个叫 .venv 或许相似的文件夹。为什么我引荐 virtualenv 而不是 PEP582 呢?在许多体系下都依靠了一个默许的 python 版别,假如用 PEP582 的话,默许会运用体系自带的 python 版别,假如不必的话咱们又有必要额定的要生成一个需求的 python 版别对应的虚拟环境;其次是目前 vscode 并不支撑。

咱们能够运用这些构建东西,将咱们的开发环境、测验环境、出产环境的依靠都区分开来。

咱们相同能够在 pyproject.toml 中增加构建东西的额定指令,比方能够增加用于发动服务的 start 指令、测验用的 test 指令等等。相似 PDM Scripts 所描绘的相同。经过构建东西发动服务能很有效处理包地点位置的问题,强制让一切的包的运转目录都为项目根目录。

项目结构

引荐的项目结构如下:

Dockerfile

这个文件首要用于给 Docker 构建镜像运用,主张在出产环境布置时经过 Docker 进行布置。

docs

专门用于保存文档的文件夹。

LICENSE

假如这个是一个开源项目,那么这个文件一般用于放置运用的开源协议

pyproject.toml

基于 PEP518 规范的装备文件,保存了项目介绍、作者联系办法、所依靠的包、运用的构建东西等等。

README.md

首要用于放项目介绍、运用说明的 MarkDown 文档。

{project_name}

放实践项目代码的文件夹,你能够取恣意的姓名,但需求保证不跟你所依靠的其它第三方的包姓名重复。之所以不必 src 是由于你在编写测验用例、或许把它作为 python 的包给其它项目用时会更便利。

{project_name}-stubs

{project_name}-stubs中的 project_name需求跟上面那个文件夹的姓名一致,然后拼接 -stubs,比方项目叫 andy,那么这个文件夹叫 andy-stubs。假如你的项目是一个 python 的库的话,你能够在这个文件夹中寄存 mypy 生成的类型描绘文件。mypy 会主动读取这个项目中的类型描绘,便利做类型判断。详情见 mypy 文档。

tests

用来寄存单元测验等的文件夹。

tox.ini

tox 的装备文件。

.gitignore

用于告知 git 应该忽略哪些文件。

模块引证

Python 模块是最首要的笼统层之一,并且很或许是最天然的一个。笼统层允许将代码分为 不同部分,每个部分包括相关的数据与功用。

例如在项目中,一层控制用户操作相关接口,另一层处理底层数据操作。最天然分开这两 层的办法是,在一份文件里重组一切功用接口,并将一切底层操作封装到另一个文件中。 这种情况下,接口文件需求导入封装底层操作的文件,可经过 importfrom ... import 句子完结。一旦你运用 import 句子,就能够运用这个模块。 既能够是内置的模块包括 os 和 sys,也能够是已经装置的第三方的模块,或许项目 内部的模块。

为遵守风格指南中的规则,模块称号要短、运用小写,并防止运用特别符号,比方点(.) 和问号(?)。如 my.spam.py 这样的姓名是有必要不能用的!该办法命名将阻碍 Python 的模块查找功用。就 my.spam.py 来说,Python 认为需求在 my 文件夹 中找到 spam.py 文件,实践并不是这样。假如愿意你能够将模块命名为 my_spam.py, 不过并不引荐在模块名中运用下划线。可是,在模块称号中运用其他字符(空格或连字号) 将阻挠导入(-是减法运算符),因此请尽量坚持模块称号简略,以无需分开单词。 最重要的是,不要运用下划线命名空间,而是运用子模块。

# OK
import library.plugin.foo
# not OK
import library.foo_plugin

除了以上的命名限制外,Python文件成为模块没有其他特别的要求,但为了合理地运用这 个观念并防止问题,你需求理解 import 的原理机制。具体来说,import modu 句子将 寻觅适宜的文件,即调用目录下的 modu.py 文件(假如该文件存在)。假如没有 找到这份文件,Python 解说器递归地在 “PYTHONPATH” 环境变量中查找该文件,假如仍没 有找到,将抛出 ImportError 反常。

一旦找到 modu.py,Python 解说器将在阻隔的作用域内履行这个模块。一切顶层 句子都会被履行,包括其他的引证。办法与类的界说将会存储到模块的字典中。然后,这个 模块的变量、办法和类经过命名空间露出给调用方,这是Python中特别有用和强壮的核心概念。

在许多其他语言中,include file 指令被预处理器用来获取文件里的一切代码并‘复制’ 到调用方的代码中。Python 则不相同:include 代码被独立放在模块命名空间里,这意味着您 一般不需求担心 include 的代码或许形成不好的影响,例如重载同名办法。

也能够运用import句子的特别形式 from modu import * 模拟更规范的行为。但 import * 一般 被认为是不好的做法。运用 from modu import * 的代码较难阅览并且依靠独立性不足。 运用 from modu import func 能精确认位您想导入的办法并将其放到全局命名空间中。 比 from modu import * 要好些,由于它明确地指明往全局命名空间中导入了什么办法,它和 import modu 比较唯一的优点是之后运用办法时能够少打点儿字。

import modu
[...]
x = modu.sqrt(4)

其次是假如引证自己项意图的模块时,参加你的项目叫 my,模块叫 modu,那么不主张运用 from my import modu来引证,强烈引荐运用 from . import modu

其次是主张假如需求给其它模块引证某些类的时候,请在这个模块的 __init__.py 中露出并且加上 as 替代一些语言中的 export。

from .config import Config as Config

并且完全不主张在 __init__.py 放置大量代码,主张只用于替代 export。

__init__.py 中加了过多代码,跟着项意图复杂度增长, 目录结构越来越深,子包和更深嵌套的子包或许会呈现。在这种情况下,导入多层嵌套 的子包中的某个部件需求履行一切经过路径里碰到的 __init__.py 文件。假如 包内的模块和子包没有代码同享的需求,运用空白的 __init__.py 文件是正常甚至好的做法。

类型查看

Python是一门动态语言,许多时候咱们或许不清楚函数参数类型或许回来值类型,很有或许导致一些类型没有指定办法,在写完代码一段时间后回过头看代码,很或许忘记了自己写的函数需求传什么参数,回来什么类型的成果,就不得不去阅览代码的具体内容,降低了阅览的速度,typing 模块能够很好的处理这个问题。

自 python3.5 开端,PEP484 为 python 引进了类型注解 (type hints)。

Mypy 是 Python 中的静态类型查看器。Mypy 具有强壮且易于运用的类型体系,具有许多优秀的特性,例如类型推断、泛型、可调用类型、元组类型、联合类型和结构子类型。引荐运用 mypy 作为类型查看东西并且每个办法有必要声明清楚参数、参数的类型、回来值类型。

def register(
    self, factory: Optional[PooledObjectFactory] = None, name: Optional[str] = None
) -> None:
	pass

假如这个参数或许回来值能够为空,应当标注 Optional 或许运用 3.11 的语法 类型 | None。如 PooledObjectFactory | None。

在 vscode 中你能够装置 mypy 的插件,这样能够直接在 vscode 中完结类型查看。

代码格局化和风格查看

为了协助开发者一致代码风格,Python 社区提出了 PEP8 代码编码风格,它并没有强制要求大家有必要遵循,Python 官方一起推出了一个查看代码风格是否契合 PEP8 的东西,姓名也叫 pep8。

Black 自称“零退让代码格局化东西(The uncompromising code formatter)”。

Black 号称是不退让的 Python 代码格局化东西。之所以成为“不退让”是由于它检测到不契合规范的代码风格直接就帮你悉数格局化好,底子不需求你确认,直接替你做好决议。而作为回报,Black 供给了快速的速度。 Black 经过产生最小的差异来更快地进行代码审查。 Black 的运用非常简略,装置成功后,和其他体系指令相同运用,只需在 black 指令后面指定需求格局化的文件或许目录即可。

某种意义上来说一个可装备很低的代码格局化和查看东西在团队中比一个能够大量自界说装备的更好。现代的 IDE 一般都供给了对 Black 的支撑。

装备办理

主张将装备放在 {project_name}/{project_name} 文件夹中,运用 yaml 格局进行保存。之所以不必 toml 之类的格局是由于假如用 k8s 之类的装备映射功用的话就无法运用了,yaml 则能够很好的与其它体系坚持兼容。

你能够将装备地点的 yaml 文件读取出来并且反序列化成一个装备对象。这个装备对象能够是 python 中的 dataclass 也能够便是一个普通的类,并且上面声明装备的每个字段。

装备是一种或许常常会增删字段的东西,咱们不应该经过相似 dict 的办法进行操作。

反常办理

几乎一切编程语言中都有反常。反常能够快速指出程序呈现的问题,便于排查。开发人员也能够依据情况抛出自界说反常, 以指示希望的内容和实践不相符。杰出的反常设计和运用习气,能够进步程序的质量。

在逻辑中,或许呈现不契合预期的逻辑,会抛出相关反常。此刻在编码时,为了逻辑的正常运转,需求对逻辑进行处理,捕获反常。

捕获反常是,运用 try...except 代码块包裹需求处理反常的代码。 expect 捕获指定的反常类型,假如呈现,进入 对应的代码逻辑。关于一些不想处理的,经过 raise 抛出反常。

在捕获时尽量不要捕获宽泛的反常基类如 Exception,而是捕获具体的反常,如 ValueError。

处理反常时,假如没有继续抛出反常,需求输入日志信息。除非你知道不输出任何信息不会形成拍错困难。项目反常要以 ERROR 结尾。和规范反常命名相似。

测验

在 Python 中除了有语言内置的测验结构之外,还有许多第三方测验结构,一些非测验结构内部也会内置测验结构。其意图都是在内置测验结构的基础上 增加了一些特性,让编写测验愈加便利,测验进程愈加顺利。

为了便利测验结构查找测验用例,在编写测验时应遵循一定的规范:

  • 测验模块要以 test_ 最初
  • 测验办法要以 test_ 最初
  • 测验类名要以 Test 最初

测验都放到 tests 文件夹下面。

Pytest 是在 unittest 的基础上 增加了大量语法糖,让测验愈加简便和灵敏。并且带有插件功用,便利集成其他功用。

由于 Pytest 能兼容其他大多数测验结构,并且它也具有强壮的功用,所以引荐运用 Pytest 作为首要测验结构运用。

tox 是通用的虚拟环境办理和测验指令行东西。tox 能够让咱们在同一个 Host 上自界说出多套相互独立且阻隔的 python 环境,假如你的项目需求兼容多个 python 版别的话强烈引荐运用它。