嘗試兜出一個在撰寫 Python 程式碼的時也能像 gofmt 這樣方便的 code formatter。

前言

去年開始學 Golang,但早在這之前就已經聽過很多 Gopher 說過 gofmt 有多麼方便,當 Pythonista 還在靠 syntax checker 和自制力來遵守 PEP8 的時候,Gopher 根本都不用管什麼 coding style,不管你是怎麼寫的,只要寫好之後用 gofmt 執行一下,就可以自動幫你把程式碼排版排好,還可以順便幫你檢查錯誤。

故事起源於 2016 底,和平常不是寫 Python 的朋友一起弄了一個用 Python 寫的 side project,因為朋友平常不是寫 Python 的,然後那陣子他又剛好在寫 Golang,覺得要遵守 PEP8 很麻煩,所以問我 Python 有沒有類似 go fmt 的工具。

當下想了一下好像還真的沒有,頂多就是像 pep8, flake8, pyflakes 這類的 syntax checker 而已,好像沒聽聞過什麼好用的 code formatter,也因為這樣,所以開始想辦法做到這件事。

當然真的去查了之後發現還是有的,但使用起來不盡理想。於是找了些現成的程式兜一兜,再加上 git pre-commit hook 後,最後算是勉強做到了,當下有做個凌亂的紀錄,但一直沒有整理成一篇文章,利用最近離職後比較閒的時間,把它整理紀錄一下。


紀錄

當時一開始是直接找到 GitHub 上的這個 repo: GitHub - Psycojoker/pyfmt: automatic code formatter for python following pep8 using baron FST, like gofmt

看起來好像不錯,但實際上使用起來有滿多問題的,而且作者又用了另外一個自己寫的 Python Full Syntax Tree library: GitHub - PyCQA/baron: IDE allow you to refactor code, Baron allows you to write refactoring code.,當時因為急著找現成的工具來用,所以就沒有多花時間研究。但後來才發現 PyCQA 裡頭的工具都滿不錯的: Python Code Quality Authority · GitHub,基本上都是用來提升 Python 程式碼品質的工具,滿推薦寫 Python 的人看一下的。


之後試了幾個工具以後,最後變成 autoflake + isort + autopep8 + git pre-commit hook 來做到這件事,老實說真的有點繁瑣,但我找不到更好的方法,如果有人知道的話還請不吝告知。

總之,接下來稍微介紹一下這幾個工具分別做了哪些事:


後來發現 Google 也出了一個 Python formatter: GitHub - google/yapf: A formatter for Python files,用了以後覺得比 autopep8 好用,所以就把 autopep8 換成 yapf 了:Use yapf instead of autopep8 as python code formatter. · pellaeon/fengyuan@abc9fc9 · GitHub


結果

最後的結果就是整合到一個 git pre-commit hook 裡頭,麻煩的是 clone 下來以後,得用這個指令初始化 git pre-commit hook:

ln -sf ../../pre-commit.sh .git/hooks/pre-commit  

其實還是很麻煩,之後應該會再繼續尋找有沒有更方便的方法,不排除自己寫一個就是。

最後的結果就是一個 git pre-commit hook:fengyuan/pre-commit.sh at master · pellaeon/fengyuan · GitHub

以下直接複製貼上原程式碼留個紀錄,以防哪天 GitHub 掛了。

#!/bin/sh  
#  
# An example hook script to verify what is about to be committed.  
# Called by "git commit" with no arguments.  The hook should  
# exit with non-zero status after issuing an appropriate message if  
# it wants to stop the commit.  
#  
# To enable this hook, rename this file to "pre-commit".  

if git rev-parse --verify HEAD >/dev/null 2>&1  
then  
    against=HEAD  
else  
    # Initial commit: diff against an empty tree object  
    against=4b825dc642cb6eb9a060e54bf8d69288fbee4904  
fi  

# If you want to allow non-ASCII filenames set this variable to true.  
allownonascii=$(git config --bool hooks.allownonascii)  

# Redirect output to stderr.  
exec 1>&2  

# Cross platform projects tend to avoid non-ASCII filenames; prevent  
# them from being added to the repository. We exploit the fact that the  
# printable range starts at the space character and ends with tilde.  
if [ "$allownonascii" != "true" ] &&  
    # Note that the use of brackets around a tr range is ok here, (it's  
    # even required, for portability to Solaris 10's /usr/bin/tr), since  
    # the square bracket bytes happen to fall in the designated range.  
    test $(git diff --cached --name-only --diff-filter=A -z $against |  
      LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0  
then  
    cat <<\EOF  
Error: Attempt to add a non-ASCII file name.  

This can cause problems if you want to work with people on other platforms.  

To be portable it is advisable to rename the file.  

If you know what you are doing you can disable this check using:  

  git config hooks.allownonascii true  
EOF  
    exit 1  
fi  

# Run syntax checker and formatter for Python files.  
STAGED_PYTHON_FILES=$(git diff --cached --name-only HEAD "*.py")  

if [ "$STAGED_PYTHON_FILES" != "" ]  
then  
    autoflake -i --remove-all-unused-imports --remove-unused-variables $STAGED_PYTHON_FILES  
    isort -y $STAGED_PYTHON_FILES  
    yapf -i $STAGED_PYTHON_FILES  
    git add $STAGED_PYTHON_FILES  
fi  

參考來源


Share


Donation

如果覺得這篇文章對你有幫助, 除了留言讓我知道外, 或許也可以考慮請我喝杯咖啡, 不論金額多寡我都會非常感激且能鼓勵我繼續寫出對你有幫助的文章。

If this blog post happens to be helpful to you, besides of leaving a reply, you may consider buy me a cup of coffee to support me. It would help me write more articles helpful to you in the future and I would really appreciate it.


Related Posts