代碼質量保證的利器:Git 預提交鉤子
一、什么是預提交(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 命令時自動執行的配置文件。配置文件中的任何檢查失敗,都會終止提交,從而始終確保代碼庫的質量和一致性。
二、數據科學為什么需要 pre-commit 鉤子?
作為數據科學家,為什么也需要學習 git 鉤子?現在社區中出現了一種趨勢,認為精通軟件工程越來越重要,而能夠將自己的模型部署到生產中是非常有價值的事情。
而利用 git 鉤子和 pre-commit 是確保穩健部署機器學習模型,同時保證代碼質量的一種技術。機器學習模型比較繁瑣,因此任何能讓工作流程自動化并在模型投入生產前捕捉潛在錯誤的方法都是有價值的。
三、使用方法
1. 安裝
安裝 pre-commit 非常簡單,和通過 pip 安裝其他 Python 庫一樣。我個人是用 Poetry,但都能正常工作。
pip install pre-commit # pip
poetry add pre-commit # I personally use poetry
運行該程序即可確認已安裝。
pre-commit --version
2. 配置文件
進入代碼倉庫根目錄,在項目中創建 .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 是執行的特定預提交鉤子的唯一標識符。
然后,我們對 black 和 flake8 代碼包采用相同的流程。
3. 執行
定義配置文件后,需要執行 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 .
其中定義了兩個命令 install 和 lint 來設置環境,確保代碼沒有問題。
該倉庫的 .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 等技術來確保模型安全部署到生產環境。