Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

atama plus
October 14, 2022

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

2022年10月15日のPyCon JP 2022にて、atama plus・WEBエンジニアの平山(@yukihira1992)が登壇しました。

▼PyCon JP 2022 登壇情報
https://2022.pycon.jp/timetable?id=CNFPPT

▼開発チームの情報発信リンク集
https://linktr.ee/atama_plus_dev

atama plus

October 14, 2022
Tweet

More Decks by atama plus

Other Decks in Programming

Transcript

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

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

  3. ⾃⼰紹介 • atama plus株式会社でエンジニアをしてます • よく使う技術 ◦ Python / Django

    ◦ TypeScript / Angular • 興味・関⼼ ◦ ソフトウェアテスト ◦ モデリング ◦ リファクタリング 3 Takayuki Hirayama @yukihira1992
  4. AIで、⼀⼈ひとりに、最短で「わかる!」を。

  5. ⓒ 2022 atama plus Inc. 5 全国の塾・予備校に AI教材「atama+」を提供。 ⼀⼈ひとりの 「得意」「苦⼿」を分析し、

    学習をパーソナライズします。
  6. ⓒ 2022 atama plus Inc. この発表の概要 • こんな⼈におすすめ ◦ Linterの仕組みが知りたい

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

    3. プラグイン化 4. ルール作りの実践テクニック 5. まとめ 7 コーディング規約⾃動化: Pylintのカスタムルールを作ろう
  8. 1. Linterの基本

  9. ⓒ 2022 atama plus Inc. Linterの概要 • Linterは静的コード解析ツールの1つ ◦ プログラムのソースコードを解析

    ◦ 特定のパターンにマッチするコードを検出する • Linterが検出するもの ◦ バグの疑いがあるコード ◦ コーディングスタイルの違反 9 1. Linterの基本
  10. ⓒ 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の基本
  11. ⓒ 2022 atama plus Inc. Linterを導⼊するメリット • コーディング規約に違反するコードの追加を防げる • スタイルが統⼀されて変更時の差分が減る

    ◦ スタイルの違いで喧嘩しない 11 1. Linterの基本 注 : CIに組み込む前提
  12. ⓒ 2022 atama plus Inc. 余談 ルールを⾃作したきっかけ • ソフトウェアプロジェクト独⾃の規約を徹底したかったから ◦

    モジュール間の依存関係をコントロールしたい ◦ コアモジュールから周辺モジュールへは依存しない 12 1. Linterの基本 Module Module Module Module Module Module 逆⽅向への依存を禁⽌
  13. 2. Pylintの仕組みとルールの作り⽅

  14. ⓒ 2022 atama plus Inc. Pylintの基本的な仕組み ① ソースコードからAST (Abstract Syntax

    Tree) への変換 ② ASTの探索 ③ ルール違反の判定 14 2. Pylintの仕組みとルールの作り⽅
  15. ⓒ 2022 atama plus Inc. Abstract Syntax Tree • ソースコードを構⽂解析して⽊構造で表したデータ

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

    ソースコードからASTへの変換 ノード名 説明 NodeNG 全ノードの基底クラス Module モジュール FunctionDef 関数・メソッド定義 Call 関数・メソッド呼び出し If if⽂ BinOp ⼆項演算 Const 定数 Name 変数名
  17. ⓒ 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に変換
  18. ⓒ 2022 atama plus Inc. Pylintの基本的な仕組み ① ソースコードからAST (Abstract Syntax

    Tree) への変換 ② ASTの探索 ③ ルール違反の判定 18 2. Pylintの仕組みとルールの作り⽅
  19. ⓒ 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
  20. ⓒ 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_ + ⼩⽂字のノード名の コールバックを実装する 対応するコールバックが 無いと素通り
  21. ⓒ 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の処理 データ構造
  22. ⓒ 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 構造の探索
  23. ⓒ 2022 atama plus Inc. Pylintの基本的な仕組み ① ソースコードからAST (Abstract Syntax

    Tree) への変換 ② ASTの探索 ③ ルール違反の判定 23 2. Pylintの仕組みとルールの作り⽅
  24. ⓒ 2022 atama plus Inc. 24 print関数の利⽤を検知するルール

  25. ⓒ 2022 atama plus Inc. print関数を利⽤するコードの構造 25 2. Pylintの仕組みとルールの作り⽅ -

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

    ルール違反の判定 注 : instance.method() 形式のメソッド呼び出しはfuncがNameノードではなくAttributeノードになります 関数・メソッド呼び出し判定 対象が関数か判定 関数名の判定
  27. ⓒ 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
  28. ⓒ 2022 atama plus Inc. 28 ASTへの変換とASTの探索はPylint本体が⾏う ルールの⾃作で必要なのはCheckerの実装

  29. 3. プラグイン化

  30. ⓒ 2022 atama plus Inc. ディレクトリ構成 30 3. プラグイン化 Pylintの設定ファイル

    プラグインモジュール ⾃作のCheckerクラス 検査対象のコード
  31. ⓒ 2022 atama plus Inc. プラグインとして動かすには ① プラグイン形式のモジュールにする ② モジュール検索パスを設定する

    ③ プラグインを指定して実⾏する 31 3. プラグイン化
  32. ⓒ 2022 atama plus Inc. Checkerの登録 32 3. プラグイン化 -

    プラグイン形式のモジュールにする • プラグインモジュールのトップにregister関数を作る ◦ 1ファイルでない場合は__init__.py • register関数でPyLinterに⾃作Checkerを登録する ◦ PyLinterは内部でASTWalkerにCheckerを登録する
  33. ⓒ 2022 atama plus Inc. プラグインとして動かすには ① プラグイン形式のモジュールにする ② モジュール検索パスを設定する

    ③ プラグインを指定して実⾏する 33 3. プラグイン化
  34. ⓒ 2022 atama plus Inc. モジュール検索パスとは • インポート対象の名前を検索する対象のディレクトリのリスト • 基本的に次の3種類

    ◦ ⼊⼒されたスクリプトのディレクトリ • ファイル指定が無い場合はカレントディレクトリ ◦ 環境変数 PYTHONPATH で指定されたディレクトリ ◦ インストール時に設定されるデフォルトのディレクトリ • 標準ライブラリ • site-packages • sys.pathに格納される 34 3. プラグイン化 - モジュール検索パスを設定する
  35. ⓒ 2022 atama plus Inc. pylintコマンドから⾒たカレントディレクトリ 35 3. プラグイン化 -

    モジュール検索パスを設定する pylintのカレントディレクトリ pylintからインポートしたい プラグイン そのままではインポートできない モジュール検索パスの設定が必要
  36. ⓒ 2022 atama plus Inc. 環境変数 PYTHONPATH を設定する⽅法 • Pylintのドキュメントに書かれている⽅法

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

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

    ③ プラグインを指定して実⾏する 38 3. プラグイン化 - モジュール検索パスを設定する
  39. ⓒ 2022 atama plus Inc. プラグインを指定してpylintを実⾏ 39 3. プラグイン化 -

    プラグインを指定して実⾏する 常にプラグインを有効化したい場合は .pylintrc でload-pluginsを設定すると良い load-pluginsオプションで プラグインのモジュール名を指定して実⾏
  40. 4. ルール作りの実践テクニック

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

  42. ⓒ 2022 atama plus Inc. 別名が付けられたprint関数の利⽤を検知できるのか? 42 4. ルール作りの実践テクニック -

    名前解決 print2にprint関数を代⼊ print関数は直接呼び出されない 関数名がprint2に変わっている Callノードの関数名を⾒るだけでは不⼗分
  43. ⓒ 2022 atama plus Inc. 名前解決の課題 43 4. ルール作りの実践テクニック -

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

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

    名前解決 オリジナルのFunctionDef が得られている 関数名もprint 関数の定義場所がbuiltins モジュールであることまで 確認するとより正確
  46. ⓒ 2022 atama plus Inc. inferenceの活⽤例 - スーパークラスの把握 46 4.

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

  48. ⓒ 2022 atama plus Inc. ネストしたif⽂をどうやって検知するか? 48 4. ルール作りの実践テクニック -

    スコープの管理 3重にネストしたif⽂ 途中に別のノードが存在する場合もある
  49. ⓒ 2022 atama plus Inc. スコープを考慮した判定 49 4. ルール作りの実践テクニック -

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

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

    スコープの管理 Node1 If Node2 Node3 Node4 If If スタック ① スコープの開始時 ② スコープの終了時 ① スタックにPush ② スタックからPop
  52. ⓒ 2022 atama plus Inc. スタックのトップが 直属のスコープ スタックの状態とスコープ 52 4.

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

    スコープの管理 初期化のフック if開始のコールバック if終了のコールバック 判定 判定ロジックの⾒通しが良くなった
  54. ⓒ 2022 atama plus Inc. テクニックは他にもいろいろ 54 4. ルール作りの実践テクニック •

    Pylint本体で提供されているルールのコードを読んでみよう ◦ https://github.com/PyCQA/pylint/tree/main/pylint/checkers ◦ 同じようなルールは参考にできる
  55. 5. まとめ

  56. ⓒ 2022 atama plus Inc. 今⽇話しきれなかったこと • ⾃作のCheckerのテスト • inference(推論)の拡張

    ◦ astroidが対応していない推論のパターンを⾃分で追加できる 56 5. まとめ
  57. ⓒ 2022 atama plus Inc. 今⽇のまとめ • Pylintの仕組みとルールの作り⽅ ◦ VisitorパターンでASTを処理している

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

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