Slide 1

Slide 1 text

コーディング規約⾃動化 Pylintのカスタムルールを作ろう Takayuki Hirayama PyCon JP 2022

Slide 2

Slide 2 text

ⓒ 2022 atama plus Inc. 2 サンプルコード https://github.com/yukihira1992/pyconjp2022

Slide 3

Slide 3 text

⾃⼰紹介 • atama plus株式会社でエンジニアをしてます • よく使う技術 ◦ Python / Django ◦ TypeScript / Angular • 興味・関⼼ ◦ ソフトウェアテスト ◦ モデリング ◦ リファクタリング 3 Takayuki Hirayama @yukihira1992

Slide 4

Slide 4 text

AIで、⼀⼈ひとりに、最短で「わかる!」を。

Slide 5

Slide 5 text

ⓒ 2022 atama plus Inc. 5 全国の塾・予備校に AI教材「atama+」を提供。 ⼀⼈ひとりの 「得意」「苦⼿」を分析し、 学習をパーソナライズします。

Slide 6

Slide 6 text

ⓒ 2022 atama plus Inc. この発表の概要 • こんな⼈におすすめ ◦ Linterの仕組みが知りたい ◦ Linterのルールを作りたいけど具体的な⽅法が分からない • 前提知識 ◦ Pythonの基本的な⽂法 • 今⽇のゴール ◦ Pylintがコードをチェックする動きがイメージできる ◦ 調べながら簡単なルールを⾃作できる 6 コーディング規約⾃動化: Pylintのカスタムルールを作ろう

Slide 7

Slide 7 text

ⓒ 2022 atama plus Inc. アジェンダ 1. Linterの基本 2. Pylintの仕組みとルールの作り⽅ 3. プラグイン化 4. ルール作りの実践テクニック 5. まとめ 7 コーディング規約⾃動化: Pylintのカスタムルールを作ろう

Slide 8

Slide 8 text

1. Linterの基本

Slide 9

Slide 9 text

ⓒ 2022 atama plus Inc. Linterの概要 • Linterは静的コード解析ツールの1つ ◦ プログラムのソースコードを解析 ◦ 特定のパターンにマッチするコードを検出する • Linterが検出するもの ◦ バグの疑いがあるコード ◦ コーディングスタイルの違反 9 1. Linterの基本

Slide 10

Slide 10 text

ⓒ 2022 atama plus Inc. Pythonの著名なLinter • flake8 ◦ F811: redefinition of unused name from line N • 同名の再定義 • black ◦ Strings: Black prefers double quotes (" and """) over single quotes (' and '’’). • ⽂字列リテラルのスタイル 10 1. Linterの基本

Slide 11

Slide 11 text

ⓒ 2022 atama plus Inc. Linterを導⼊するメリット • コーディング規約に違反するコードの追加を防げる • スタイルが統⼀されて変更時の差分が減る ◦ スタイルの違いで喧嘩しない 11 1. Linterの基本 注 : CIに組み込む前提

Slide 12

Slide 12 text

ⓒ 2022 atama plus Inc. 余談 ルールを⾃作したきっかけ • ソフトウェアプロジェクト独⾃の規約を徹底したかったから ◦ モジュール間の依存関係をコントロールしたい ◦ コアモジュールから周辺モジュールへは依存しない 12 1. Linterの基本 Module Module Module Module Module Module 逆⽅向への依存を禁⽌

Slide 13

Slide 13 text

2. Pylintの仕組みとルールの作り⽅

Slide 14

Slide 14 text

ⓒ 2022 atama plus Inc. Pylintの基本的な仕組み ① ソースコードからAST (Abstract Syntax Tree) への変換 ② ASTの探索 ③ ルール違反の判定 14 2. Pylintの仕組みとルールの作り⽅

Slide 15

Slide 15 text

ⓒ 2022 atama plus Inc. Abstract Syntax Tree • ソースコードを構⽂解析して⽊構造で表したデータ • 様々な種類のノードから構成される • 実⾏に不要なトークンは取り除かれる ◦ 数式のカッコや要素や引数を区切るカンマなど 15 2. Pylintの仕組みとルールの作り⽅ - ソースコードからASTへの変換 BinOp * BinOp + Name x Const 1 Const 2 ( x + 1 ) * 2 PylintはASTに対して パターンマッチを⾏う

Slide 16

Slide 16 text

ⓒ 2022 atama plus Inc. 今⽇登場する主なASTのノード 16 2. Pylintの仕組みとルールの作り⽅ - ソースコードからASTへの変換 ノード名 説明 NodeNG 全ノードの基底クラス Module モジュール FunctionDef 関数・メソッド定義 Call 関数・メソッド呼び出し If if⽂ BinOp ⼆項演算 Const 定数 Name 変数名

Slide 17

Slide 17 text

ⓒ 2022 atama plus Inc. ソースコードからASTへの変換 17 2. Pylintの仕組みとルールの作り⽅ - ソースコードからASTへの変換 注 : Pylintに合わせて標準ライブラリのastではなくastroidを使っています。 https://github.com/PyCQA/astroid BinOp * BinOp + Name x Const 1 Const 2 ソースコードの⽂字列 ASTに変換

Slide 18

Slide 18 text

ⓒ 2022 atama plus Inc. Pylintの基本的な仕組み ① ソースコードからAST (Abstract Syntax Tree) への変換 ② ASTの探索 ③ ルール違反の判定 18 2. Pylintの仕組みとルールの作り⽅

Slide 19

Slide 19 text

ⓒ 2022 atama plus Inc. ASTの探索 • ASTWalkerがASTを深さ優先探索する • ASTWalkerはLinter本体であるPyLinterから使われるクラス 19 2. Pylintの仕組みとルールの作り⽅ - ASTの探索 BinOp * BinOp + Name x Const 1 Const 2 ASTWalker 1 2 3 4 5

Slide 20

Slide 20 text

ⓒ 2022 atama plus Inc. 探索で発⾒したノードの判定 • ASTWalkerはノードを発⾒するとCheckerのコールバックを実⾏する • Checkerはノードがルールに違反していないか判定する 20 2. Pylintの仕組みとルールの作り⽅ - ASTの探索 BinOp * BinOp + Name x Const 1 Const 2 ASTWalker Checker visit_binop visit_name visit_ + ⼩⽂字のノード名の コールバックを実装する 対応するコールバックが 無いと素通り

Slide 21

Slide 21 text

ⓒ 2022 atama plus Inc. Visitorパターン 21 2. Pylintの仕組みとルールの作り⽅ - ASTの探索 • データ構造と処理を分離するためのデザインパターン 参考: Java⾔語で学ぶデザインパターン⼊⾨ 第3版 https://www.sbcr.jp/product/4815609801/ File - accept(Visitor) Element - accept(Visitor) Directory - accept(Visitor) FooVisitor - visit(File) - visit(Directory) Visitor - visit(File) - visit(Directory) ダブルディスパッチ 構造の探索 + Elementの処理 データ構造

Slide 22

Slide 22 text

ⓒ 2022 atama plus Inc. PylintのVisitorパターン 22 2. Pylintの仕組みとルールの作り⽅ - ASTの探索 • ASTWalkerが動的にChecker(Visitor)とNode(Element)を接続する 参考:エキスパートPythonプログラミング 改訂3版 https://tatsu-zine.com/books/expert-python-programming-3ed BinOp NodeNG Name FooChecker - visit_binop(BinOp) - visit_name(Name) BaseChecker Nodeの処理 データ構造 ASTWalker 構造の探索

Slide 23

Slide 23 text

ⓒ 2022 atama plus Inc. Pylintの基本的な仕組み ① ソースコードからAST (Abstract Syntax Tree) への変換 ② ASTの探索 ③ ルール違反の判定 23 2. Pylintの仕組みとルールの作り⽅

Slide 24

Slide 24 text

ⓒ 2022 atama plus Inc. 24 print関数の利⽤を検知するルール

Slide 25

Slide 25 text

ⓒ 2022 atama plus Inc. print関数を利⽤するコードの構造 25 2. Pylintの仕組みとルールの作り⽅ - ルール違反の判定 Name ʻprintʼ Const ʻHello Worldʼ func Call args keywords 検知したいコードを ASTに変換してみる 関数・メソッド呼び出し 対象の名前 位置引数と キーワード引数

Slide 26

Slide 26 text

ⓒ 2022 atama plus Inc. print関数利⽤の判定ロジック 26 2. Pylintの仕組みとルールの作り⽅ - ルール違反の判定 注 : instance.method() 形式のメソッド呼び出しはfuncがNameノードではなくAttributeノードになります 関数・メソッド呼び出し判定 対象が関数か判定 関数名の判定

Slide 27

Slide 27 text

ⓒ 2022 atama plus Inc. print関数利⽤を検知するCheckerクラス 27 2. Pylintの仕組みとルールの作り⽅ - ルール違反の判定 メッセージ定義 メッセージ ≒ ルール visitメソッドの中で 違反を判定する add_messageで違反した ルールとノードを記録する メッセージ定義の詳細 : https://pylint.pycqa.org/en/latest/development_guide/how_tos/custom_checkers.html#defining-a-message

Slide 28

Slide 28 text

ⓒ 2022 atama plus Inc. 28 ASTへの変換とASTの探索はPylint本体が⾏う ルールの⾃作で必要なのはCheckerの実装

Slide 29

Slide 29 text

3. プラグイン化

Slide 30

Slide 30 text

ⓒ 2022 atama plus Inc. ディレクトリ構成 30 3. プラグイン化 Pylintの設定ファイル プラグインモジュール ⾃作のCheckerクラス 検査対象のコード

Slide 31

Slide 31 text

ⓒ 2022 atama plus Inc. プラグインとして動かすには ① プラグイン形式のモジュールにする ② モジュール検索パスを設定する ③ プラグインを指定して実⾏する 31 3. プラグイン化

Slide 32

Slide 32 text

ⓒ 2022 atama plus Inc. Checkerの登録 32 3. プラグイン化 - プラグイン形式のモジュールにする • プラグインモジュールのトップにregister関数を作る ◦ 1ファイルでない場合は__init__.py • register関数でPyLinterに⾃作Checkerを登録する ◦ PyLinterは内部でASTWalkerにCheckerを登録する

Slide 33

Slide 33 text

ⓒ 2022 atama plus Inc. プラグインとして動かすには ① プラグイン形式のモジュールにする ② モジュール検索パスを設定する ③ プラグインを指定して実⾏する 33 3. プラグイン化

Slide 34

Slide 34 text

ⓒ 2022 atama plus Inc. モジュール検索パスとは • インポート対象の名前を検索する対象のディレクトリのリスト • 基本的に次の3種類 ◦ ⼊⼒されたスクリプトのディレクトリ • ファイル指定が無い場合はカレントディレクトリ ◦ 環境変数 PYTHONPATH で指定されたディレクトリ ◦ インストール時に設定されるデフォルトのディレクトリ • 標準ライブラリ • site-packages • sys.pathに格納される 34 3. プラグイン化 - モジュール検索パスを設定する

Slide 35

Slide 35 text

ⓒ 2022 atama plus Inc. pylintコマンドから⾒たカレントディレクトリ 35 3. プラグイン化 - モジュール検索パスを設定する pylintのカレントディレクトリ pylintからインポートしたい プラグイン そのままではインポートできない モジュール検索パスの設定が必要

Slide 36

Slide 36 text

ⓒ 2022 atama plus Inc. 環境変数 PYTHONPATH を設定する⽅法 • Pylintのドキュメントに書かれている⽅法 • 実⾏前に環境変数を設定する • 設定を徹底するのが難しい (特にチームに浸透させたい場合) ◦ 毎回⼿で設定する ◦ pylintをラップしたシェルスクリプトを作る ◦ direnvで環境変数を管理する 36 3. プラグイン化 - モジュール検索パスを設定する

Slide 37

Slide 37 text

ⓒ 2022 atama plus Inc. init-hook で sys.path を書き換える⽅法 • .pylintrcでinit-hookを設定する ◦ sys.pathにプロジェクトディレクトリを追加する • pylintコマンドを実⾏時に⾃動的に読まれる 37 3. プラグイン化 -モジュール検索パスを設定する Importエラーが出た場合は init-hook で sys.path の内容を出⼒してみると良い

Slide 38

Slide 38 text

ⓒ 2022 atama plus Inc. プラグインとして動かすには ① プラグイン形式のモジュールにする ② モジュール検索パスを設定する ③ プラグインを指定して実⾏する 38 3. プラグイン化 - モジュール検索パスを設定する

Slide 39

Slide 39 text

ⓒ 2022 atama plus Inc. プラグインを指定してpylintを実⾏ 39 3. プラグイン化 - プラグインを指定して実⾏する 常にプラグインを有効化したい場合は .pylintrc でload-pluginsを設定すると良い load-pluginsオプションで プラグインのモジュール名を指定して実⾏

Slide 40

Slide 40 text

4. ルール作りの実践テクニック

Slide 41

Slide 41 text

ⓒ 2022 atama plus Inc. 41 実践テクニック① 名前解決

Slide 42

Slide 42 text

ⓒ 2022 atama plus Inc. 別名が付けられたprint関数の利⽤を検知できるのか? 42 4. ルール作りの実践テクニック - 名前解決 print2にprint関数を代⼊ print関数は直接呼び出されない 関数名がprint2に変わっている Callノードの関数名を⾒るだけでは不⼗分

Slide 43

Slide 43 text

ⓒ 2022 atama plus Inc. 名前解決の課題 43 4. ルール作りの実践テクニック - 名前解決 • Pythonは何にでも別名を付けられる ◦ モジュール名 ◦ クラス名 ◦ 関数名 ◦ 変数名 • 判定ルールでは名前が指すオリジナルの定義を使いたい • 真剣に考えると代⼊を追ってシンボルテーブルを作らないといけない ◦ かなり⼤変そう シンボルテーブル : 名前と中⾝を対応付けたテーブル

Slide 44

Slide 44 text

ⓒ 2022 atama plus Inc. astroidのinference(推論)機能の紹介 44 4. ルール作りの実践テクニック - 名前解決 注 : 推論に失敗する場合や推論の結果が2つ以上になる場合もある。実際に使う場合はエラー処理が必須 Inferenceは 名前が指す対象を推論する astroidの強⼒な機能 Nameノードは変数名以上の 情報を持っていない inferメソッドで推論すると cが数値の3を指していると分かる

Slide 45

Slide 45 text

ⓒ 2022 atama plus Inc. inferenceを使った関数呼び出しの名前解決 45 4. ルール作りの実践テクニック - 名前解決 オリジナルのFunctionDef が得られている 関数名もprint 関数の定義場所がbuiltins モジュールであることまで 確認するとより正確

Slide 46

Slide 46 text

ⓒ 2022 atama plus Inc. inferenceの活⽤例 - スーパークラスの把握 46 4. ルール作りの実践テクニック - 名前解決 • Djangoのモデルを判定するのも結構⼤変 ◦ 基底のModelクラスの指定⽅法が無数にある ◦ 抽象基底クラスやプロキシモデルを利⽤してたら?

Slide 47

Slide 47 text

ⓒ 2022 atama plus Inc. 47 実践テクニック② スコープの管理

Slide 48

Slide 48 text

ⓒ 2022 atama plus Inc. ネストしたif⽂をどうやって検知するか? 48 4. ルール作りの実践テクニック - スコープの管理 3重にネストしたif⽂ 途中に別のノードが存在する場合もある

Slide 49

Slide 49 text

ⓒ 2022 atama plus Inc. スコープを考慮した判定 49 4. ルール作りの実践テクニック - スコープの管理 • ネストしたif⽂ ◦ ifノード⾃体は普通に使って良い ◦ ネストが深い場合だけ違反にしたい • ノードは出現する⽂脈によって意味が変わる ◦ ⽂脈≒スコープを考慮した判定をしたい

Slide 50

Slide 50 text

ⓒ 2022 atama plus Inc. ノードを辿る実装の課題 50 4. ルール作りの実践テクニック - スコープの管理 • 先祖ノードや⼦孫ノードを辿るとコードが複雑になる ◦ ループ ◦ 再帰処理 • スコープ以外の条件が必要な場合もある ◦ 各条件はできるだけシンプルにしたい 直接の親や⼦だけを調べれば良い場合は愚直な実装もOK

Slide 51

Slide 51 text

ⓒ 2022 atama plus Inc. スタックを利⽤したスコープの管理 51 4. ルール作りの実践テクニック - スコープの管理 Node1 If Node2 Node3 Node4 If If スタック ① スコープの開始時 ② スコープの終了時 ① スタックにPush ② スタックからPop

Slide 52

Slide 52 text

ⓒ 2022 atama plus Inc. スタックのトップが 直属のスコープ スタックの状態とスコープ 52 4. ルール作りの実践テクニック - スコープの管理 If スタック スタック If スタック If スコープを作るノード⾃体をスタックに保持すると、スコープの属性を参照したい時に便利 スタックのサイズは ネストの数 ノードが存在する ifブロックのスコープ内 スタックが空 ifブロックのスコープ外 ノードが複数存在する ネストしたifブロックの中

Slide 53

Slide 53 text

ⓒ 2022 atama plus Inc. スタックを利⽤したスコープ管理の実装 53 4. ルール作りの実践テクニック - スコープの管理 初期化のフック if開始のコールバック if終了のコールバック 判定 判定ロジックの⾒通しが良くなった

Slide 54

Slide 54 text

ⓒ 2022 atama plus Inc. テクニックは他にもいろいろ 54 4. ルール作りの実践テクニック • Pylint本体で提供されているルールのコードを読んでみよう ◦ https://github.com/PyCQA/pylint/tree/main/pylint/checkers ◦ 同じようなルールは参考にできる

Slide 55

Slide 55 text

5. まとめ

Slide 56

Slide 56 text

ⓒ 2022 atama plus Inc. 今⽇話しきれなかったこと • ⾃作のCheckerのテスト • inference(推論)の拡張 ◦ astroidが対応していない推論のパターンを⾃分で追加できる 56 5. まとめ

Slide 57

Slide 57 text

ⓒ 2022 atama plus Inc. 今⽇のまとめ • Pylintの仕組みとルールの作り⽅ ◦ VisitorパターンでASTを処理している ◦ CheckerクラスでASTの構造からルール違反を判定する • プラグイン化 ◦ モジュール検索パスへの追加が必要なので注意 • 実践テクニック ◦ 複雑な名前解決にはastroidのinferenceが便利 ◦ スタックを利⽤してスコープを管理するとコードの⾒通しが良い 57 5. まとめ

Slide 58

Slide 58 text

ⓒ 2022 atama plus Inc. 58 ご静聴ありがとうございました

Slide 59

Slide 59 text

もっと知りたい⽅はこちら @atamaplus_dev プロダクト開発に関する情報発信を⾒る atama plusのニュースレターに登録