$30 off During Our Annual Pro Sale. View Details »

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  8. 1. Linterの基本

    View Slide

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

    View Slide

  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の基本

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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に対して
    パターンマッチを⾏う

    View Slide

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

    View Slide

  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に変換

    View Slide

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

    View Slide

  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

    View Slide

  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_ + ⼩⽂字のノード名の
    コールバックを実装する
    対応するコールバックが
    無いと素通り

    View Slide

  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の処理
    データ構造

    View Slide

  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
    構造の探索

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  29. 3. プラグイン化

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  55. 5. まとめ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide