Python 赢得众人喜爱的重要原因有很多:简洁的语法、强大的标准库以及极低的上手成本,你既可以用它来写一些快糙猛的小脚本,同时也能用它来做一些真正的“大项目”,解决一些更为复杂的问题,总结下Python的主要特点有:
-
易学易用:Python 的语法简洁明了,接近自然语言,非常适合初学者。它的代码可读性强,开发者能够更快地上手和实现复杂功能。
-
丰富的库和框架:Python 拥有庞大的标准库和第三方库,涵盖了从数据分析、机器学习、Web 开发到自动化脚本等各个领域。例如,NumPy 和 Pandas 是数据处理的利器,TensorFlow 和 PyTorch 是深度学习的主流框架,Django 和 Flask 是流行的 Web 开发框架。
-
广泛的应用领域:Python 被广泛应用于数据科学、人工智能、Web 开发、自动化测试、系统运维等多个领域。其通用性使得它在各个行业都能找到合适的应用场景。
-
社区活跃:Python 拥有一个庞大且活跃的社区,开发者可以通过论坛、博客、在线教程等途径获得大量的资源和帮助。这种强大的社区支持也是推动 Python 发展的重要因素。
-
跨平台性:Python 是跨平台的语言,可以运行在 Windows、MacOS、Linux 等操作系统上,这使得开发者可以更方便地在不同环境中部署和运行 Python 程序。
-
大公司的支持:许多大型科技公司,如谷歌、脸书、亚马逊等,都在使用 Python 并为其生态系统贡献力量,这无疑增强了 Python 的影响力和可信度。
-
面向对象和函数式编程:Python 支持多种编程范式,包括面向对象编程和函数式编程,这使得开发者可以选择最适合自己项目的编程风格。
写个几百行代码的 Python 脚本是一码事,参与一个有数万行代码的项目,用它来服务成千上万的用户则完全是另一码事。当项目规模变大,参与人数变多后,许多在写小脚本时完全不用考虑的问题会跳出来:
- 缩进用 Tab 还是空格?如何让所有人的代码格式保持统一?
- 为什么每次发布新版本都心惊胆战?如何在代码上线前发现错误?
- 如何在快速开发新功能的同时,对代码做安全重构?
一、常用工具介绍
在开发中大型项目时,往往需要多人参与,甚至跨团队合作。试想一下,如果每个人都按照自己的编码习惯和风格来,那简直是个灾难,最后的代码肯定破碎不堪、难以入目。因此在多人参与的大型项目里,最基本的一件事就是让所有人的代码风格保持一致,整洁得就像是出自同一人之手。
下面抛砖引玉介绍几个与代码风格有关的工具。如果能让所有开发者都使用这些工具,你就可以轻松统一项目的代码风格,内容简介:
- flake8、isort、black:常用的代码检查与格式化工具。
- pre-commit:一个专门用于预提交阶段的工具,根据设置好的检查规则(如:flake8、isort、black),对用户提交的修改代码进行格式化检查,如果没有通过会阻塞提交,屏蔽用户编码习惯的差异,比如不同的人一般会使用不同的IDE,甚至有的人用Vim编程。
1、flake8
Python 有一份官方代码风格指南:PEP 8。PEP 8 对代码风格提出了许多具体要求,比如每行代码不能超过 79 个字符、运算符两侧一定要添加空格,等等。
在开发项目时,光有一套纸面上的规范是不够的。纸面规范只适合阅读,无法用来快速检验真实代码是否符合规范。只有通过自动化代码检查工具(常被称为 Linter)才能最大地发挥 PEP 8 的作用。
flake8 就是这么一个工具。利用 flake8,你可以轻松检查代码是否遵循了 PEP 8 规范。
1.1 安装flake8
pip3 install flake8
1.2 基本用法
在终端中直接运行 flake8
即可检查单个文件或整个目录:
# 检查单个文件
flake8 example.py
# 检查整个目录
flake8 my_project/
1.3 输出示例
flake8
会输出代码中不符合 PEP 8 规范的地方,示例:
example.py:1:1: F401 'os' imported but unused
example.py:2:5: E225 missing whitespace around operator
example.py:5:1: E302 expected 2 blank lines, found 1
1.4 配置flake8
你可以通过在项目根目录下创建一个配置文件 .flake8
或 setup.cfg
来配置 flake8
的行为。下面是一个 .flake8
配置文件的示例:
[flake8]
max-line-length = 88
exclude = .git,__pycache__,old,build,dist
1.5 忽略特定规则
flake8 为每类错误定义了不同的错误代码,比如 F401、E111 等。这些代码的首字母代表了不同的错误来源,比如以 E 和 W 开头的都违反了 PEP 8 规范,以 F 开头的则来自于 pyflakes。
如果你想忽略某些特定的 PEP 8 规则,可以在命令行中使用 --ignore
选项:
flake8 example.py --ignore=E501
或者在配置文件中添加:
[flake8]
ignore = E501
1.6 在代码中忽略特定行
import os # noqa: F401
x = 1+2 # noqa: E225
1.7 示例项目
假设有一个简单的项目结构如下:
my_project/
example.py
.flake8
example.py 内容:
import os
def my_function():
x =1+2
return x
.flake8 内容:
[flake8]
max-line-length = 88
exclude = .git,__pycache__,old,build,dist
ignore = E225
运行 flake8 my_project/
,输出如下:
my_project/example.py:1:1: F401 'os' imported but unused
2、isort
PEP 8 认为,一个源码文件内的所有 import 语句,都应该依照以下规则分为三组,不同的 import 语句组之间应该用空格分开:
(1) 导入 Python 标准库包的 import 语句;
(2) 导入相关联的第三方包的 import 语句;
(3) 与当前应用(或当前库)相关的 import 语句(本地包)。
isort
是一个用于自动整理 Python 导入语句(import)的工具,它可以根据配置文件或默认规则将导入语句排序并分组。借助 isort,我们不用手动进行任何分组,它会帮我们自动做好这些事。
总之,有了 isort 以后,你在调整 import 语句时可以变得随心所欲,只需负责一些简单的编辑工作,isort 会帮你搞定剩下的所有事情——只要执行 isort,整段 import 代码就会自动变得整齐且漂亮。
2.1 安装
pip3 install isort
2.2 基本用法
在终端中运行 isort
来自动整理单个文件或整个目录的导入语句:
# 整理单个文件
isort example.py
# 整理整个目录
isort my_project/
2.3 示例代码
假设你有一个未整理的 Python 文件 example.py
:
import sys
import os
from datetime import datetime
import numpy as np
from my_local_module import my_function
import requests
from django.conf import settings
运行 isort example.py
后,isort
会将导入语句整理为:
import os
import sys
from datetime import datetime
import numpy as np
import requests
from django.conf import settings
from my_local_module import my_function
2.4 配置 isort
你可以通过在项目根目录下创建一个配置文件 .isort.cfg
或 pyproject.toml
来配置 isort
的行为。下面是 .isort.cfg
的一个示例:
[settings]
profile = black
line_length = 88
known_third_party = numpy,requests,django
known_first_party = my_local_module
如果使用 pyproject.toml
,可以这样配置:
[tool.isort]
profile = "black"
line_length = 88
known_third_party = ["numpy", "requests", "django"]
known_first_party = ["my_local_module"]
2.5 常用命令和选项
- 检查模式:只检查导入语句是否已整理,而不直接修改文件。
isort --check-only example.py
- 显示 diff:显示文件整理前后的差异。
isort --diff example.py
- 使用特定配置文件:
isort --settings-path .isort.cfg example.py
3、black
PEP 8 规范为许多代码风格问题提供了标准答案,但这份答案其实非常宏观,在许多细节要求上并不严格。在许多场景中,同一段代码在符合 PEP 8 规范的前提下,可以写成好几种风格。
以下面的代码为例,同一个方法调用语句可以写成三种风格,假如你用 flake8 来扫描上面这三段代码,会发现它们虽然风格迥异,但全都符合 PEP 8 规范。
第一种风格:在不超过单行长度限制时,把所有方法参数写在同一行。
User.objects.create(name='piglei', gender='M', lang='Python', status='active')
第二种风格:在第二个参数时折行,并让后面的参数与之对齐。
User.objects.create(name='piglei',
gender='M',
language='Python',
status='active')
第三种风格:统一使用一层缩进,每个参数单独占用一行。
User.objects.create(
name='piglei',
gender='M',
language='Python',
status='active'
)
具体哪种风格其实没有好坏之分,一切都只与个人喜爱有关,但是这种差异可能会给项目带来不必要的沟通成本,影响开发效率。
假设你喜欢第一种编码风格:只要函数参数没超过长度限制,就坚决都放在一行里。某天你在开发新功能时,给函数调用增加了一些新参数,修改后发现新代码的长度超过了最大长度限制,于是手动对所有参数进行折行、对齐,整个过程即机械又麻烦。
因此,在多人参与的项目中,除了用 flake8 来扫描代码是否符合 PEP 8 规范外,推荐一个更为激进的代码格式化工具:black。black 是个非常强势的代码格式化工具,基本没有任何可定制性。
black 用起来很简单,只要执行 black 命令即可。
3.1 安装 black
pip3 install black
3.2 基本用法
在终端中运行 black
来格式化单个文件或整个目录:
# 格式化单个文件
black example.py
# 格式化整个目录
black my_project/
3.3 示例代码
假设你有一个未格式化的 Python 文件 example.py
:
def example_function(param1,param2,param3):
if param1:print("Hello, world!")
else:
print("Goodbye, world!")
return {'key1':param1,'key2':param2,'key3':param3}
运行 black example.py
后,black
会将代码格式化为:
def example_function(param1, param2, param3):
if param1:
print("Hello, world!")
else:
print("Goodbye, world!")
return {"key1": param1, "key2": param2, "key3": param3}
3.4 配置 black
black
默认情况下不需要配置即可使用,但你可以通过 pyproject.toml
文件进行配置。在项目根目录下创建一个 pyproject.toml
文件,并添加以下内容:
[tool.black]
line-length = 88
target-version = ['py37']
exclude = '''
/(
\.git
| \.venv
| build
| dist
)/
'''
3.5 常用选项
- 检查模式:只检查代码是否符合
black
的格式,而不修改文件。
black --check example.py
- 显示差异:显示文件格式化前后的差异。
black --diff example.py
- 只格式化特定版本的代码:指定目标 Python 版本。
black --target-version py37 example.py
作为一个代码格式化工具,black 最大的特点在于它的不可配置性。正如官方介绍所言,black 是一个“毫不妥协的代码格式化工具”(The Uncompromising Code Formatter)。
和许多其他格式化工具相比,black 的配置项可以用“贫瘠”两个字来形容。除了单行长度以外,你基本无法对 black 的行为做任何调整。
配置项 | 说明 |
-l / --line-length | 允许的最大单行宽度,默认为 88 |
-S / --skip-string-normalization | 是否关闭调整字符串引号 |
在某些人看来,这种设计理念免去了配置上的许多麻烦,非常省心。而对于另一部分人来说,这种不支持任何个性化设置的设计,令他们完全无法接受。
从另一个角度看,虽然 black 格式化过的代码并非十全十美,肯定不能在所有细节上让大家都满意,但它确实能让我们不用在各种编码风格间纠结,能有效解决许多问题。整体来看,在大型项目中引入 black,利远大于弊。
4、pre-commit
前面介绍了几个常用的代码检查与格式化工具。偶尔手动执行那么一两次是远远不够的。要最大地发挥工具的能力,你必须让它们融入所有人的开发流程里。这意味着,对于项目的每位开发者来说,无论是谁改动了代码,都必须保证新代码一定被 black 格式化过,并且能通过 flake8 的检查。
虽然我们没法统一每个人的 IDE,但至少大部分项目使用的版本控制软件是一样的——Git。而 Git 有个特殊的钩子功能,它允许你给每个仓库配置一些钩子程序(hook),之后每当你进行特定的 Git 操作时——比如 git commit、git push,这些钩子程序就会执行。
pre-commit 就是一个基于钩子功能开发的工具。从名字就能看出来,pre-commit 是一个专门用于预提交阶段的工具。要使用它,你需要先创建一个配置文件 .pre-commit-config.yaml。
fail_fast: true
repos:
- repo: https://github.com/timothycrosley/isort
rev: 5.7.0
hooks:
- id: isort
additional_dependencies: [toml]
- repo: https://github.com/psf/black
rev: 20.8b1
hooks:
- id: black
args: [--config=./pyproject.toml]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: flake8
在上面的配置文件里,我定义了 isort、black、flake8 三个插件。基于这份配置,每当我修改完代码,执行 git commit 时,这些插件就会由 pre-commit 依次触发执行:
$ git commit -m 'Update'
isort...................................Passed
black...................................Passed
Flake8..................................Passed
[dev fac43421] Update
1 file changed, 1 insertion(+), 1 deletion(-)
假如某次改动后的代码无法通过 pre-commit 检查,这次提交流程就会中断。此时作者必须修正代码使其符合规范,之后再尝试提交。由于 pre-commit 的配置文件与项目源码存放在一起,都在代码仓库中,因此项目的所有开发者天然共享 pre-commit 的插件配置,每个人不用单独维护各自的配置,只要安装 pre-commit 工具就行。
使用 pre-commit,你可以让代码检查工具融入每位项目成员的开发流程里。所有代码改动在被提交到 Git 仓库前,都会经工具的规范化处理,从而真正实现项目内代码风格的统一。
二、思考说明
上面介绍了一些比较常见的编码编码格式规范检查工具,事实上远远不止这些,还有许多可以关注的点,比如:mypy工具、单元测试等。
不管怎么样,在日常的工作中按实际需求灵活运用,避免教条主义。
参考资料:
1、《Python 工匠:案例、技巧与工程实践》,朱雷