はじめに
琴葉わん... nikkieです。
先日、琴葉ちゃんと一緒にいたい 世の中のPythonコードの関数の引数の型ヒントをよりPythonicにしたい一心で flake8-kotohaをリリースしました。
その中で分かったflake8 pluginの作りかた1をまとめます。
目次
第1の巨人:タイミー Product Team Blog
先人のアウトプットを参照して(=巨人の肩の上に乗りまくって)進めました。
flake8 pluginを初めて作るので、全体感を掴むのに役立ちました。
pluginには2種類ある
- 抽象構文木を利用するplugin
- 1行ずつ処理するplugin
flake8-kotohaは抽象構文木を利用するつもりだった2ので、前者について見ていきました。
- プラグインの本体(
TypeAPluginSampleクラス)__init__()メソッド- ast.ASTを受け取る
run()メソッド- ジェネレータ
- ast.NodeVisitorを継承したクラスをインスタンス化し、
visit()メソッドを呼び出す 本来ならvisitorに結果を溜め込んで結果に応じてエラーをレポート
- pyproject.toml
以上で、pluginのプロジェクトをインストールしてflake8と叩くと、pluginが動きます!(Entry Pointsの好例!)
flake8 -hでインストールされたpluginを確認できました。
第2の巨人:anthony explains
タイミーさんの記事では抽象構文木の扱いの詳細までは記載がなかったので、別の先人のアウトプットを参照しました。
flake8のドキュメント Writing Plugins for Flake8 — flake8 7.1.0 documentation にあるビデオチュートリアルです。
ビデオチュートリアルとanthonyさんが作った拡張 flake8-2020 の両方を参考に、抽象構文木を扱う実装をしていきました。
Pythonの関数の呼び出しでfunc(**辞書)と書けるのですが、辞書のキーがハイフンを含むときにはエラーになります。
これがanthonyさんのpluginの題材でした
俺の書いたpluginを見てくれ!
- Python 3.12.0
- flake8 7.0.0
pyproject.toml(抜粋)
https://github.com/ftnext/kotoha-python-linter/blob/v0.1.0/pyproject.toml
[project] dependencies = ["flake8"] [project.entry-points."flake8.extension"] KTH = "kotoha.plugins:Flake8KotohaPlugin"
src/kotoha/plugins.py(抜粋)
https://github.com/ftnext/kotoha-python-linter/blob/v0.1.0/src/kotoha/plugins.py
class Flake8KotohaPlugin: def run(self) -> Generator[tuple[int, int, str, Type[Any]], None, None]: checker = ArgumentConcreteTypeHintChecker() checker.visit(self._tree) for lineno, col_offset, message in checker.errors: yield (lineno, col_offset, message, type(self))
ArgumentConcreteTypeHintChecker6はsrc/kotoha/core.pyに
https://github.com/ftnext/kotoha-python-linter/blob/v0.1.0/src/kotoha/core.py
class ArgumentConcreteTypeHintChecker(ast.NodeVisitor): def __init__(self) -> None: self.errors: list[tuple[LineNumber, ColumnOffset, ErrorMessage]] = []
終わりに
巨人の肩に乗って、抽象構文木ベースのflake8 pluginの作りかたを完全に理解しました。
- pluginの実態はクラス
- pyproject.toml
[project.entry-points."flake8.extension"]にプラグインのクラスを指定
先人のアウトプットに感謝です。
ありがとうございます!
指定されたメソッドを用意したクラスを作るだけ!7
Entry Pointsってすごいなと思います(どう実装してるんだろう?)
- ワーディングは『冴えない彼女の育てかた』を意識しています。サエヒロならぬフレプラです。↩
- 抽象構文木を走査するスクリプト版がすでにありました。↩
- Entry Pointsの一例です! ↩
- 一例ですが https://github.com/asottile-archive/flake8-2020/blob/v1.8.1/flake8_2020.py#L67-L69↩
- https://github.com/asottile-archive/flake8-2020/blob/v1.8.1/flake8_2020.py#L161-L162↩
- 記事を書いていて気づいたのですが、Pythonの用語集によると、argumentは実引数。仮引数はparameterなんですよね。仮引数の型ヒントのpluginだからrename?(しかしtyping.Listのドキュメントはargumentを使っている)↩
-
Entry Pointsではないですが、Sphinxの拡張(
setup()関数を持ったモジュール)にも共通するような ↩