本文主要介绍如何利用 Python 的 pre-commit 包定义 Git pre-commit 钩子,从而自动化代码质量检查的过程,并介绍了这项技术对数据科学家的作用。原文:Pre-Commit & Git Hooks: Automate High Code Quality

Pankaj Patel @Unsplash

什么是预提交(Pre-Commit)?

Pre-commit 是一个 Python 软件包,能够帮助我们更容易创建预提交钩子(pre-commit hook)。钩子是 git 原生的东西,是在执行特定 git 命令前运行的脚本。

可以在仓库的 .git/hooks 目录中找到钩子,该目录由 git 自动创建。在这个目录中,可以找到类似下面这样的文件:

applypatch-msg.sample     pre-commit.sample         prepare-commit-msg.sample
commit-msg.sample         pre-merge-commit.sample   push-to-checkout.sample
fsmonitor-watchman.sample pre-push.sample           update.sample
post-update.sample        pre-rebase.sample
pre-applypatch.sample     pre-receive.sample

.sample 扩展名会阻止执行这些钩子。要启用钩子,请删除 .sample 扩展名并编辑文件。

不过这么做既繁琐又对用户不友好,而且很难通过版本控制进行管理,这就有 pre-commit 的用武之地了,它为 commit 命令创建钩子,可以自动检测代码中的任何问题,并使脚本的创建工作天衣无缝。

它会创建一个在调用 git commit 命令时自动执行的配置文件。配置文件中的任何检查失败,都会终止提交,从而始终确保代码库的质量和一致性。

有关 git 钩子的更多信息,请参阅 Git Hooks 官方文档

数据科学为什么需要 pre-commit 钩子?

作为数据科学家,为什么也需要学习 git 钩子?现在社区中出现了一种趋势,认为精通软件工程越来越重要,而能够将自己的模型部署到生产中是非常有价值的事情。

而利用 git 钩子和 pre-commit 是确保稳健部署机器学习模型,同时保证代码质量的一种技术。机器学习模型比较繁琐,因此任何能让工作流程自动化并在模型投入生产前捕捉潜在错误的方法都是有价值的。

要了解有关全栈数据科学的更多信息,请查看:The predictable rise of full stack data scienceThe 4 hats of a full stack data scientist

使用方法

安装

安装 pre-commit 非常简单,和通过 pip 安装其他 Python 库一样。我个人是用 Poetry,但都能正常工作。

pip install pre-commit # pip 
poetry add pre-commit # I personally use poetry

运行该程序即可确认已安装。

pre-commit --version
配置文件

进入代码仓库根目录,在项目中创建 .pre-commit-config.yaml。运行以下命令即可完成创建:

touch .pre-commit-config.yaml 

该文件将定义在提交代码前要运行的内容。示例如下:

repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v2.3.0
    hooks:
    -   id: check-yaml
    -   id: end-of-file-fixer
    -   id: trailing-whitespace

-   repo: https://github.com/psf/black
    rev: 22.10.0
    hooks:
    -   id: black

- repo: https://github.com/PyCQA/flake8
  rev: v6.0.0 
  hooks:
    - id: flake8
      additional_dependencies: [flake8-docstrings, flake8-import-order]
      args: ['--max-line-length=88']

这里 repos 表示包含要运行的钩子的仓库列表。第一个是包含钩子的 pre-commit-hooks 仓库:

  • id: check-yaml
  • id: end-of-file-fixer
  • id: trailing-whitespace

在这个例子里,id 是执行的特定预提交钩子的唯一标识符。

然后,我们对 blackflake8 代码包采用相同的流程。

执行

定义配置文件后,需要执行 pre-commit install,让 pre-commit 知道你想使用指定的脚本在该仓库上执行预提交检查。

然后,修改代码并尝试提交,应该会看到预提交钩子开始工作。

也可以执行命令 pre-commit run --all-files 来查看 pre-commit 正在做什么。

如果想在不运行钩子的情况下提交代码,可以执行 git commit --no-verify 绕过钩子。

好了,我们举个例子!

示例

我在 Medium-Articles 仓库中安装了 pre-commit,用它来展示一个例子。

在仓库中,有以下 Makefile 文件

SHELL=/bin/bash

PHONY: install
install:
    poetry install

PHONY: lint
lint:
    poetry run black .
    poetry run isort .

其中定义了两个命令 installlint 来设置环境,确保代码没有问题。

该仓库的 .pre-commit-config.yaml 如下所示,与之前展示的模板略有不同。

repos:
  - repo: local
    hooks:
      - id: lint
        name: lint
        entry: make lint
        language: python
        types: [python]
        stages: [commit]

在本例中,钩子所在的仓库是本地的,即在我的项目中。该钩子将执行 entry 里定义的 make lint 命令,作为钩子的一部分,该命令已在 makefile 文件中定义。

仓库里有一个代码文件是这样的(relu.py):

# Import packages
import numpy as np
import os
import plotly.express as px


# relu function
def relu(x):
    return np.maximum(0, x)


# Generate data
x = np.linspace(-5, 5, 100)
y = relu(x)

# Graph
fig = px.line(x=x, y=y)
fig.update_layout(template="simple_white", font=dict(size=18), title_text="ReLU", width=650, title_x=0.5, height=400,)

if not os.path.exists("../images"):
    os.mkdir("../images")
fig.write_image("../images/relu.png")

fig.show()

我们尝试提交这段代码,看看会发生什么。

(medium-articles-py3.11) egorhowell@Egors-MBP Medium-Articles % git add .
(medium-articles-py3.11) egorhowell@Egors-MBP Medium-Articles % git commit -m "testing pre-commit"
[INFO] Initializing environment for local.
[INFO] Installing environment for local.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
lint.....................................................................Failed
- hook id: lint
- files were modified by this hook

poetry run black .
Skipping .ipynb files as Jupyter dependencies are not installed.
You can fix this by running ``pip install "black[jupyter]"``
reformatted /Users/egorhowell/Repos/Medium-Articles/Data Science Basics/relu.py
reformatted /Users/egorhowell/Repos/Medium-Articles/Time Series/Exponential Smoothing/holt_winters.py

All done! ✨ 🍰 ✨
2 files reformatted, 67 files left unchanged.
poetry run isort .
Fixing /Users/egorhowell/Repos/Medium-Articles/Time Series/Exponential Smoothing/holt_winters.py
Skipped 2 files
make: Nothing to be done for `Data Science Basics/relu.py'.

钩子失败了,它重新格式化了 relu.py。现在这个文件是这样的:

# Import packages
import os

import numpy as np
import plotly.express as px


# relu function
def relu(x):
    return np.maximum(0, x)


# Generate data
x = np.linspace(-5, 5, 100)
y = relu(x)

# Graph
fig = px.line(x=x, y=y)
fig.update_layout(
    template="simple_white",
    font=dict(size=18),
    title_text="ReLU",
    width=650,
    title_x=0.5,
    height=400,
)

if not os.path.exists("../images"):
    os.mkdir("../images")
fig.write_image("../images/relu.png")

fig.show()

可以看到,预提交钩子成功运行了!

总结和进一步思考

Pre-commit 是一个实用的 Python 软件包,能改善 git 钩子的工作流程并简化脚本。它旨在通过自动化代码检查流程来保持软件的高质量并消除错误风险。作为数据科学家,越来越多参与到模型部署中,可以通过 git 钩子和 pre-commit 等技术来确保模型安全部署到生产环境。


你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

本文由mdnice多平台发布


俞凡
24 声望18 粉丝

你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起...