私が歌川です

@utgwkk が書いている

pycodestyleのコードをなんとなく読む

はじめに

これは Pythonその3 Advent Calendar 2020 - Qiita 16日目の記事です。昨日は 特徴量エンジニアリングのライブラリ xfeat を使ってみて便利だったこと - Taste of Tech Topics でした。

pycodestyleについて

pycodestyleは、Pythonのコードスタイルチェッカです。 コードスタイルのルールに違反している箇所を指摘してくれます。 CIに組み込むことで、コーディング規約に違反している箇所を機械的に指摘したり、テストを落としたりすることができます。

pycodestyleのコードを読む

pycodestyleがどのようにPythonのコードをパースし、スタイルチェックを行っているのか見ていきましょう。 全ての実装を理解することは諦め、実装の流れをなんとなくつかむのを目標にしていきます。

ソースコードのありか

pycodestyleのソースコードはGitHubで公開されています。 以下では、commit 9dd78b97dea0e943300c92c853d6869e7fe41299 時点のソースコードを読んでいきます。

github.com

この記事にはソースコードの該当箇所へのリンクを貼るのみにとどめています。 適宜GitHubを開きつつ記事を読むと理解が深められるかもしれません。

スタイルチェックが行われるまで

コマンドラインからpycodestyleを起動すると、まず最初に _main 関数が呼ばれます。続いて StyleGuide クラスのインスタンスを作っています

とくにコマンドラインオプションを指定しなかったら style_guide.check_files() が実行され、指定された各ファイルに対してスタイルチェックが行われます。

各ファイルに対してスタイルチェックを行う

check_files で、指定された各ファイルに対してスタイルチェックを行います。 ディレクトリが指定された場合は、再帰的にディレクトリツリーをたどってチェック対象のファイルを収集します。

runner つまり self.runner の既定値は input_file です。 input_file 内で fchecker.check_all() を呼び出しています。 fcheckerChecker クラスのインスタンスで、このクラスがスタイルチェックを行う本体のクラスのようです。

check_all でチェックを行う

fchecker.check_all() の実装を見ましょう。 いろいろありますが generate_tokens() で、与えられたソースコードをPythonのトークン列に分解したジェネレータを取得しています。 これは tokenize.generate_tokens() で得たトークンをちょっと加工して yield しています。

generate_tokens() で得られたトークン列をリストにappendしていきつつスタイルチェックを行っています。 得られたトークン列に対して check_logical()check_physical() でチェックを行っていっているようです。 それぞれPythonの論理行物理行に対するチェックを行う関数です。

ルールを登録するデコレータ register_check

ところで肝心のルールはどこにあるのでしょうか? check_logical メソッドなどの実装を見ると、具体的なルールが書いておらず、ただ self._physical_checks で取得できる各ルールに対して self.run_check(check, argument_names) と書いてあるだけです。

ここで使われるルールは _checks という辞書logical_line キーに入っているルールです。 この辞書は最初は空ですが、 register_check デコレータで関数をwrapすることでルールが追加できます。

このデコレータがちょっとおもしろいことをしています。 inspect モジュールを使って関数の引数名を取得して、 _checks のどのキーにルールを追加するかを判定しています。 pytestのfixtureに似たような雰囲気がありますね*1

ルールの例 imports_on_separate_lines

論理行に対する簡単なルールの例として imports_on_separate_lines を見ていきましょう。 これは、同一論理行に複数のモジュールの import を連ねてはいけない、というルールです。

# OK
import os
import sys

# NG
import os, sys

論理行の先頭が import で始まり、 , が行に含まれるときエラーを報告します*2

この関数を register_check デコレータでwrapすることで、論理行に対するルールとして登録しています。

おわりに

pycodestyleのコードをなんとなく読んできました。 どういう流れでコードスタイルがチェックされているかなんとなく分かったかもしれません。 また、pycodestyleに新しいルールを追加するのも難しくなさそう、と思ってもらえたかもしれません。

世の中にある有名なライブラリのソースコードを読んでみると勉強になるので、一度は読んでみるとよいでしょう。

明日は @Tyankazu さんで「PythonでLINEトークの頻出単語ランキング出してみた」です。

*1:時間がなくてここまでは調べていません。誰か知ってる人がいたら教えてください!

*2:もうちょっと条件は細かい