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

PyCon JP 2022/ASTに入門する

atama plus
October 14, 2022

PyCon JP 2022/ASTに入門する

2022年10月14日のPyCon JP 2022にて、atama plus・アルゴリズム開発の安本(@myasumoto)が登壇しました。

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

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

atama plus

October 14, 2022
Tweet

More Decks by atama plus

Other Decks in Programming

Transcript

  1. A S T に ⼊ ⾨ す る
    安 本 雅 啓
    2022.10.14

    View Slide

  2. ⓒ 2022 atama plus Inc.
    アジェンダ
    1. ⾃⼰紹介
    2. 本セッションの概要説明
    3. Pythonコードはどのようにして実⾏されるか
    4. ASTの構造
    5. astモジュールについて
    6. astモジュールの活⽤事例
    7. まとめ
    2

    View Slide

  3. ⓒ 2022 atama plus Inc.
    アジェンダ
    1. ⾃⼰紹介
    2. 本セッションの概要説明
    3. Pythonコードはどのようにして実⾏されるか
    4. ASTの構造
    5. astモジュールについて
    6. astモジュールの活⽤事例
    7. まとめ
    3

    View Slide

  4. ⾃⼰紹介
    Algorithm Engineer @ atama plus Inc.
    • SIerでのR&Dエンジニア、AI StartupでのCTOを経て、
    2021年4⽉よりatama plusにジョイン。以降、⼀貫し
    てatama+プロダクトのアルゴリズム開発に携わる。
    • Like:機械学習/Deep Learning/強化学習/DDD(最近
    勉強中)
    • Hobby:料理/LEGO
    4
    安 本 雅 啓
    @ m y a s u m o t o

    View Slide

  5. ⓒ 2022 atama plus Inc.
    アジェンダ
    1. ⾃⼰紹介
    2. 本セッションの概要説明
    3. Pythonコードはどのようにして実⾏されるか
    4. ASTの構造
    5. astモジュールについて
    6. astモジュールの活⽤事例
    7. まとめ
    5

    View Slide

  6. ⓒ 2022 atama plus Inc.
    本セッションの概要
    • 本セッションを通して得られるもの
    • ASTとは何か
    • Pythonの実⾏プロセスの中でのASTの位置付け
    • astモジュールの概要と使い⽅
    • astモジュールを使うことで、どのようなことができるようになるか
    • 本セッションの想定する対象者
    • ASTという⾔葉を初めて聞く/聞いたことはあるが詳しく知らない⽅
    • Linter等の静的解析ツールを普段使っており背景技術に興味がある⼈
    • など
    6

    View Slide

  7. ⓒ 2022 atama plus Inc.
    アジェンダ詳細
    7
    Pythonコードがどの
    ようにして実⾏されるか
    ASTの構造
    astモジュールの使い⽅
    astモジュールの活⽤事例
    Pythonを実⾏したとき、ソースコードがどのように変換されて
    最終的に実⾏されるかを簡単に概観します。またその中での
    ASTの位置付けについても説明します。
    ASTの基本的な構造や、ASTを構成するノードの種類、ASTを可
    視化できるツールについて紹介します。
    astモジュールの想定する、「検査する」と「変更を加える」の
    2つのユースケースについて、astモジュールをどのようにして
    使えばよいか、紹介します。
    astモジュールを使って、⾃作linterやtestingフレームワークの
    プロトタイプを書いてみます。また実際のOSSなどでの活⽤事
    例についても紹介します。

    View Slide

  8. ⓒ 2022 atama plus Inc.
    アジェンダ
    1. ⾃⼰紹介
    2. 本セッションの概要説明
    3. Pythonコードはどのようにして実⾏されるか
    4. ASTの構造
    5. astモジュールについて
    6. astモジュールの活⽤事例
    7. まとめ
    8

    View Slide

  9. ⓒ 2022 atama plus Inc.
    実⾏までのプロセス
    • pythonコードを実⾏した時、内部では以下のようなプロセスが⾛ります。
    9
    Python
    コード
    CST
    (Concrete
    Syntax Tree)
    AST
    (Abstract
    Syntax Tree)
    バイト
    コード
    Lexer Parser
    Runtime
    Compiler
    字句解析器 構⽂解析器

    View Slide

  10. ⓒ 2022 atama plus Inc.
    実⾏までのプロセス
    • pythonコードを実⾏した時、内部では以下のようなプロセスが⾛ります。
    10
    Python
    コード
    CST
    (Concrete
    Syntax Tree)
    AST
    (Abstract
    Syntax Tree)
    バイト
    コード
    Lexer Parser
    Runtime
    Compiler
    字句解析器 構⽂解析器

    View Slide

  11. ⓒ 2022 atama plus Inc.
    CSTとAST
    • CST(Concrete Syntax Tree)とAST(Abstract Syntax Tree)は⼀般⽤語であり、
    ソースコードをParse(構⽂解析)して、その結果を、機械が解釈・処理しやすい
    ように、⽊構造として表現したものです。
    11
    CST
    • ⽂脈を考慮せず、⽂法に基づいてソー
    スコードを⽊構造に変換したもの。
    • 例えば、CSTでは、a / 2 といった⽂の
    「/」を「SLASH」というノードで保
    持する。
    AST
    • ソースコードを、⽂脈を考慮した上で、
    ⽊構造に変換したもの。
    • 例えば、ASTでは、a / 2 といった⽂の
    「/」は、「ast.Div」というノードで
    保持する。
    参考 https://eli.thegreenplace.net/2009/02/16/abstract-vs-concrete-syntax-trees/

    View Slide

  12. ⓒ 2022 atama plus Inc.
    CSTとAST
    12
    CST AST
    https://instagram-engineering.com/static-analysis-at-scale-an-instagram-story-8f498ab71a0c

    View Slide

  13. ⓒ 2022 atama plus Inc.
    CSTはどんなもの?
    • Pythonでは、parserモジュールを使うことによって、構⽂解析後の結果を確認
    することができます。
    13
    • 例えば、「a/1」という式は、構⽂
    解析によって右のような形の⽊構造
    に変換されます。
    • ここで数字は、tokenとsymbolのID
    を表しているのですが、このままだ
    と分かりにくいので、IDから名前に
    変換してみます。

    View Slide

  14. ⓒ 2022 atama plus Inc.
    CSTはどんなもの?
    • tokenとsymbolを名前に変換して
    みました。少しは分かりやすく
    なったでしょうか、、?
    • ⼤⽂字で表現されたものが
    token(SLASH, NUMBERなど)、⼩
    ⽂字で表現されたもの(atom, term
    など)がsymbolとなります。
    • これだけだと、扱いづらいですね。
    14
    「/」は「SLASH」と
    して表現されている

    View Slide

  15. ⓒ 2022 atama plus Inc.
    ASTはどんなもの?
    • Pythonでは、astモジュールを使うこと
    によって、作成されたASTを確認するこ
    とができます。
    • 例えば、a=1; b=a+2というコードは、
    右のような形の⽊構造に変換されます。
    • 各ノードには、「Assign」「BinOp」
    「Constant」というように、⼈が⾒ても
    想像しやすい名前がついているのが分か
    ります。
    15
    「/」は「Div()」と
    して表現されている

    View Slide

  16. ⓒ 2022 atama plus Inc.
    実⾏までのプロセス
    • pythonコードを実⾏した時、内部では以下のようなプロセスが⾛ります。
    16
    Python
    コード
    CST
    (Concrete
    Syntax Tree)
    AST
    (Abstract
    Syntax Tree)
    バイト
    コード
    Lexer Parser
    Runtime
    Compiler
    字句解析器 構⽂解析器

    View Slide

  17. ⓒ 2022 atama plus Inc.
    バイトコードはどんなもの?
    • Pythonでは、disモジュールを使うことに
    よって、バイトコードを確認することが
    できます。
    • dis.disは、バイトコードを逆アセンブル
    した結果を表⽰するものです。
    • LOAD_CONST、BINARY_ADDというの
    が、バイトコード命令を表しています。
    • Pythonのランタイムは、このバイトコー
    ドを解釈して実⾏します。
    17
    命令(オペコード) オペランド

    View Slide

  18. ⓒ 2022 atama plus Inc.
    実⾏までのプロセス(振り返り)
    • これで⼀通りのプロセスと、その中でのASTの位置付けについて、⾒てきまし
    た。次は本題の、ASTの詳細に⼊っていきたいと思います。
    18
    Python
    コード
    CST
    (Concrete
    Syntax Tree)
    AST
    (Abstract
    Syntax Tree)
    バイト
    コード
    Lexer Parser
    Runtime
    Compiler
    字句解析器 構⽂解析器

    View Slide

  19. ⓒ 2022 atama plus Inc.
    アジェンダ
    1. ⾃⼰紹介
    2. 本セッションの概要説明
    3. Pythonコードはどのようにして実⾏されるか
    4. ASTの構造
    5. astモジュールについて
    6. astモジュールの活⽤事例
    7. まとめ
    19

    View Slide

  20. ⓒ 2022 atama plus Inc.
    (改めて)ASTとは?
    • ASTは、ソースコードを、⽂脈を考慮した上で、⽊構造に変換したもの。
    • ⽊構造のノードには、式や制御⽂、リテラル(⽂字列、数字)、変数などが含
    まれます(完全なリストについては、公式ドキュメントを参照)
    • ノードの種類によって、保持するプロパティが異なる。
    • 例えば、「+」のようなものはBinOpに分類されますが、left, op, rightの3
    つの属性が存在しており、leftが左辺、opがオペレーションの種別(Add,
    Subなど)、rightが右辺を表す属性となります。
    • 具体的に⾒ていきましょう!
    20

    View Slide

  21. ⓒ 2022 atama plus Inc.
    ASTの例(1/3)
    • まずトップのノードはModuleとなり、そ
    の属性のbodyの中に、コード中に記述さ
    れた全statementが配列として⼊ります。
    • 「b = a+1」のstatementで何が起きるか
    は、⽊構造のLeaf側から⾒ていくと分か
    りやすいです。
    21
    1. aの変数をロードする(Load)
    2. 定数「1」を準備する(Constant)
    3. 1, 2の値の加算演算(Add/BinOp)を⾏う
    4. 3の結果を変数bに代⼊する(Assign)
    5. 変数bの値を保存する(Store)
    (1)
    (2)
    (3)
    (4)
    (5)

    View Slide

  22. ⓒ 2022 atama plus Inc.
    ASTの例(2/3)
    • 次は、制御構造が⼊るケースを⾒てみ
    ましょう。
    • Ifのノードには、test、body、orelseの
    属性が存在します。
    • testにはIf⽂の条件が⼊り、bodyにはIf
    ⽂の中のブロックの内容が⼊ります。
    • orelseはelse句を書いた際、そのブロッ
    クの内容が⼊ります。この例ではelse
    句を書いてないため、空配列となって
    います。
    22

    View Slide

  23. ⓒ 2022 atama plus Inc.
    ASTの例(3/3)
    • 今度は、関数を呼び出すケースを⾒てみま
    しょう。
    • 関数定義は、FunctionDefというノードで
    記述されます。引数がargsに、関数の中⾝
    がbody以下に格納されているのが分かるか
    と思います。
    • 次に関数を呼び出すのは、Callノードです。
    args, keywordsに引数を格納します。
    ちょっと興味深いのは、関数もまたLoad()
    される対象である、ということです。
    23

    View Slide

  24. ⓒ 2022 atama plus Inc.
    ASTを確認するためのGUIツール
    • Instaviz
    24
    ASTをグラフィカルに表⽰
    できるほか、逆アセンブル
    の結果なども併せて⾒るこ
    とができる。
    https://github.com/tonybaloney/instaviz

    View Slide

  25. ⓒ 2022 atama plus Inc.
    アジェンダ
    1. ⾃⼰紹介
    2. 本セッションの概要説明
    3. Pythonコードはどのようにして実⾏されるか
    4. ASTの構造
    5. astモジュールについて
    6. astモジュールの活⽤事例
    7. まとめ
    25

    View Slide

  26. ⓒ 2022 atama plus Inc.
    ASTモジュール
    • astモジュールには、主に、前章で説明したノード・クラスの他に、ASTノード
    を検査する、または、操作する(変更を加える)ためのユーティリティ関数や
    クラスが定義されています。
    • ここでは、以下の2つのユースケース毎に、使い⽅を簡単に説明していきます。
    • ASTを検査する
    • ASTの構造に変更を加える
    26

    View Slide

  27. ⓒ 2022 atama plus Inc.
    ASTの検査: (1)要素アクセス
    • parse後のASTに対して、属性
    アクセスをチェーンさせるこ
    とで、⽬的となる属性を取得
    することができます。
    27
    この値に
    アクセスしたい

    View Slide

  28. ⓒ 2022 atama plus Inc.
    ASTの検査: (2)ast.walkを使う
    • ast.walk関数は、ASTのノードを取り込んでジェネレータを返します。
    • ただし、順序は不定です。(あまり使い道がなさそう?😖)
    28

    View Slide

  29. ⓒ 2022 atama plus Inc.
    ASTの検査: (3)ast.NodeVisitor
    • ast.NodeVisitorクラスを継承したクラスの中で、visit_XXXという関数を
    overrideします。
    • すると、XXXのノードを訪問した際に、その関数の中⾝が実⾏されます。
    29
    visit_Constant関数を定義すると、
    Constantノードを訪問したとき
    に呼ばれるようになります
    generic_visitは、visit_XXX関数が定
    義されていないときに呼ばれる関数
    です。
    overrideした場合は、最後に明⽰的
    に呼んでおかないと、その⼦ノード
    が⾛査されなくなってしまいます。

    View Slide

  30. ⓒ 2022 atama plus Inc.
    ASTの検査: (3)ast.NodeVisitor
    • NodeVisitorを使った⾛査がどのような
    順番に⾏われるかをみてみましょう。
    30
    Module
    body
    Assign Assign
    Name Constant
    Store
    Name BinOp
    Store Name
    Load
    Add Constant

    View Slide

  31. ⓒ 2022 atama plus Inc.
    ASTの検査: (3)ast.NodeVisitor
    • NodeVisitorを使った⾛査がどのような
    順番に⾏われるかをみてみましょう。
    31
    Module
    body
    Assign Assign
    Name Constant
    Store
    Name BinOp
    Store Name
    Load
    Add Constant
    あらゆるNodeに対して呼ばれる
    generic_visit関数をフック
    NodeVisitorが辿る
    順番を確認できる

    View Slide

  32. ⓒ 2022 atama plus Inc.
    ASTの検査: (3)ast.NodeVisitor
    • Moduleノードからスタートして、深さ
    優先探索によって⾛査されるのが分か
    ります。
    32
    Module
    body
    Assign Assign
    Name Constant
    Store
    Name BinOp
    Store Name
    Load
    Add Constant

    View Slide

  33. ⓒ 2022 atama plus Inc.
    ASTの検査: (4)その他
    • これまでに紹介したもの以外に、以下のようなhelper関数が⽤意されています
    (詳細は公式doc参照)
    33
    関数名 概要
    ast.iter_fields() ノードの属性に対するイテレーターを返します。
    ast.iter_child_nodes() ノードの直接の⼦ノードに対するイテレーターを
    返します。
    ast.get_docstring() FunctionDef, ClassDef, Moduleノードに対して
    呼ぶことができ、docstringの⽂字列を返します。

    View Slide

  34. ⓒ 2022 atama plus Inc.
    (余談)get_docstringを使ったOSSの例
    • Darglint
    • docstringの説明と実際の関数の実装が合っているかどうかをチェックする
    linter。
    34
    ┌──────────────────────────────┬──────────────────┬────────────────┬──────────────────┐
    │ Docstring │ short │ long │ full │
    ├──────────────────────────────┼──────────────────┼────────────────┼──────────────────┤
    │ """Doubles the argument.""" │ None │ None │ Missing argument │
    │ │ │ │ Missing return │
    │ │ │ │ │
    │ │ │ │ │
    ├──────────────────────────────┼──────────────────┼────────────────┼──────────────────┤
    │ """Doubles the argument. │ Missing argument │ None │ Missing argument │
    │ │ Missing return │ │ Missing return │
    │ Not very pythonic. │ │ │ │
    │ │ │ │ │
    │ """ │ │ │ │
    │ │ │ │ │
    ├──────────────────────────────┼──────────────────┼────────────────┼──────────────────┤
    │ """Doubles the argument. │ Missing return │ Missing return │ Missing return │
    │ │ │ │ │
    │ Args: │ │ │ │
    │ x: The number to double. │ │ │ │
    │ │ │ │ │
    │ """ │ │ │ │
    └──────────────────────────────┴──────────────────┴────────────────┴──────────────────┘
    def double(x):
    #
    return x * 2
    https://github.com/terre
    ncepreilly/darglint

    View Slide

  35. ⓒ 2022 atama plus Inc.
    ASTに変更を加える
    • ASTに変更を加える際は、ast.NodeTransformerクラスを⽤います。
    • 前に説明したNodeVisitorと同じように、NodeTransformerをサブクラス化し
    た上で、visit_XXX関数をoverrideして使⽤します。
    • この関数はノードを返す必要があり、何を返すかによって、以下のように振る
    舞います。
    • オリジナルのノードを返す→何もしない
    • 変更を加えたノードを返す→ASTに変更を加えることができる
    • Noneを返す→そのノードがAST上から削除される
    35

    View Slide

  36. ⓒ 2022 atama plus Inc.
    NodeTransformerの使⽤例
    36
    a = 1
    b = a + 2
    print(b)
    ソースコード AST 実⾏結果
    【ユースケース】
    ソースコード中に現れる
    +(プラス)の演算をすべて
    -(マイナス)に置き換える

    View Slide

  37. ⓒ 2022 atama plus Inc.
    カスタムTransformerの定義と変換
    37
    カスタムTransformerの定義
    カスタムTransformerを使ったASTの変換
    NodeTransformerを継承
    visit_{変換対象node名}をoverride
    Add()ノードの代わりにSub()ノードを返す
    • ⼿動で加えたノードはlineno, col_offsetが⼊っておらず、そのままだと
    エラーになります。
    • よって、何らか埋める必要があり、それを補助するのが
    fix_missing_locations関数です(基本的には親ノードと同じ値で埋める)。

    View Slide

  38. ⓒ 2022 atama plus Inc.
    変更を加えたASTの実⾏結果
    38
    a = 1
    b = a + 2
    print(b)
    a = 1
    b = a - 2
    print(b)
    正しく変換されたことが確認できました🎉
    ast.unparseを⽤いると、コードに逆変換することもできます
    *) ただし、コメントや空⽩、空⾏などの情報はASTに変換した
    段階でドロップしてしまうので、完全な逆変換ではありません。

    View Slide

  39. ⓒ 2022 atama plus Inc.
    (余談)libCST
    • CSTとASTの両⽅の情報に加え、空⽩
    やコメントなどの情報も付与した構⽂
    ⽊を構築するためのライブラリ。
    • Instagramの開発チームによって開発
    された。
    • これを使えば、可逆変化が担保され、
    かつASTに変更を加えることができる
    (凄い、、!)。
    39
    https://github.com/Instagram/LibCST

    View Slide

  40. ⓒ 2022 atama plus Inc.
    (余談)libCST
    40
    https://libcst.readthedocs.io/en/latest/why_libcst.html より抜粋して要約
    元のコード
    AST
    CST
    libCST
    意味的理解はしやすいがdetailが抜け落ちる
    detailは保持されるが、意味的理解はしづらい
    ASTのレベルで
    の抽象性を持ち
    つつ、CSTのレ
    ベルでのdetail
    も保持する

    View Slide

  41. ⓒ 2022 atama plus Inc.
    アジェンダ
    1. ⾃⼰紹介
    2. 本セッションの概要説明
    3. Pythonコードはどのようにして実⾏されるか
    4. ASTの構造
    5. astモジュールについて
    6. astモジュールの活⽤事例
    7. まとめ
    41
    (1) ⾃作linterを書いてみる
    (2) ⾃作testingフレームワークを書いてみる

    View Slide

  42. ⓒ 2022 atama plus Inc.
    ⾃作linterを書いてみる
    • 今回は例として、importを多重に定義してしまった場合に、警告を出すような
    linterを書いてみようと思います。
    42
    import io as osio
    import os, csv
    from random import random as rand
    import pandas as pd
    from numpy.random import rand
    print(rand())
    • randが2重に定義されてしまっている。
    • このような場合、最後に定義された
    ものが有効となり、実⾏時に特にエ
    ラーは出ない。
    実⾏時エラーにならないためバグを⽣み
    やすく、linterでチェックできるように
    したい!

    View Slide

  43. それ、ASTで解決しましょう!

    View Slide

  44. ⓒ 2022 atama plus Inc.
    ⾃作linterのコード
    44
    class CustomVisitor(ast.NodeVisitor):
    def __init__(self):
    self.imported_module_names = set() # import済みのmodule名を格納する
    # import xxx 形式でのモジュールインポートをサポート
    def visit_Import(self, node):
    self._detect_duplicated_import(node)
    self.generic_visit(node)
    # from xxx import yyy 形式でのモジュールインポートをサポート
    def visit_ImportFrom(self, node):
    self._detect_duplicated_import(node)
    self.generic_visit(node)
    def _detect_duplicated_import(self, node):
    for alias in node.names:
    # as句で別名をつけた場合は、その名前を格納し、そうでない場合はmodule名をそのまま⽤いる
    name = getattr(alias, "asname", None)
    if name is None:
    name = alias.name
    # 既にimport済みのmodule名と同名のmoduleをimportしようとした場合、警告を出す
    if name in self.imported_module_names:
    print(f"Duplicated import detected. name:{name}, module:{node.module} @ L{node.lineno}")
    self.imported_module_names.add(name)
    tree = ast.parse(code)
    visitor = CustomVisitor()
    visitor.visit(tree)

    View Slide

  45. ⓒ 2022 atama plus Inc.
    実⾏結果
    45
    ~/ ❯ python sample_linter.py
    Duplicated import detected. name:rand,
    module:numpy.random @ L7
    code = """
    import io as osio
    import os, csv
    from random import random as rand
    import pandas as pd
    from numpy.random import rand
    print(rand())
    """
    7⾏⽬でrandが再定義されていることを
    検知できている
    検知対象コード 検知結果(実⾏結果)

    View Slide

  46. ⓒ 2022 atama plus Inc.
    コード説明
    46
    class CustomVisitor(ast.NodeVisitor):
    def __init__(self):
    self.imported_module_names = set() # import済みのmodule名を格納する
    # import xxx 形式でのモジュールインポートをサポート
    def visit_Import(self, node):
    self._detect_duplicated_import(node)
    self.generic_visit(node)
    # from xxx import yyy 形式でのモジュールインポートをサポート
    def visit_ImportFrom(self, node):
    self._detect_duplicated_import(node)
    self.generic_visit(node)
    def _detect_duplicated_import(self, node):
    for alias in node.names:
    # as句で別名をつけた場合は、その名前を格納し、そうでない場合はmodule名をそのまま⽤いる
    name = getattr(alias, "asname", None)
    if name is None:
    name = alias.name
    # 既にimport済みのmodule名と同名のmoduleをimportしようとした場合、警告を出す
    if name in self.imported_module_names:
    print(f"Duplicated import detected. name:{name}, module:{node.module}
    @ L{node.lineno}")
    self.imported_module_names.add(name)
    実装⽅針: ASTを辿る際に、
    import済みのモジュール名を記
    憶しておくことで、重複を検知
    ①:NodeVisitorを継承したクラ
    スを作成
    ②:import xxxと、from xxx
    import yyyは、別のノードタイ
    プとなるため、両⽅に対する
    visti関数の定義が必要
    ③:as句で別名をつけた場合は、
    asnameというフィードが追加
    されるので、そちらを参照する
    必要がある




    View Slide

  47. ⓒ 2022 atama plus Inc.
    アジェンダ
    1. ⾃⼰紹介
    2. 本セッションの概要説明
    3. Pythonコードはどのようにして実⾏されるか
    4. ASTの構造
    5. astモジュールについて
    6. astモジュールの活⽤事例
    7. まとめ
    47
    (1) ⾃作linterを書いてみる
    (2) ⾃作testingフレームワークを書いてみる

    View Slide

  48. ⓒ 2022 atama plus Inc.
    ⾃作testingフレームワークを書いてみる
    • 今回は、pytestのように、assert⽂での評価が失敗した場合に、通常よりも多
    くの情報を出すようなフレークワークを書いてみたいと思います。
    48
    assert [1, 4, 3] == [1, 2, 3]
    Traceback (most recent call last):
    File "/.../assert_sample.py", line 4, in
    assert [1, 4, 3] == [1, 2, 3]
    AssertionError
    assert [1, 2] == [1, 2, 3]
    Traceback (most recent call last):
    File "/.../assert_sample.py", line 4, in
    assert [1, 2] == [1, 2, 3]
    AssertionError
    2番⽬のインデックスの値だけが異なる
    そもそも配列の⻑さが異なる
    いずれのケースも同じエラーが返ってきてしまう。
    →もう少し細かい原因(差分の詳細)を出したい!

    View Slide

  49. それ、ASTで解決しましょう!

    View Slide

  50. ⓒ 2022 atama plus Inc.
    ⾃作testingフレームワークのコード
    50
    class AssertCmpTransformer(ast.NodeTransformer):
    def visit_Assert(self, node):
    if isinstance(node.test, ast.Compare) and len(node.test.ops) == 1 and isinstance(node.test.ops[0], ast.Eq):
    call = ast.Call(
    func=ast.Name(
    id='customized_assert', ctx=ast.Load()),
    args=[node.test.left, node.test.comparators[0], ast.Constant(value=node.lineno)],
    keywords=[]
    )
    expr = ast.Expr(value=call)
    return expr
    return node
    def customized_assert(left, right, lineno):
    assertion_msg = None
    if isinstance(left, list) and isinstance(right, list):
    if len(left) != len(right):
    assertion_msg = f"length of the list is different. left:{len(left)} right:{len(right)}"
    else:
    if left != right:
    indexs_with_diff = []
    for i, (l, r) in enumerate(zip(left, right)):
    if l != r:
    indexs_with_diff.append(i)
    assertion_msg = f"left:{left}, right:{right}, index {indexs_with_diff} is different"
    else:
    if left != right:
    assertion_msg = f"left: {left}, right: {right}"
    if assertion_msg is not None:
    print(f"Assertion Error: {assertion_msg} @ L{lineno}")

    View Slide

  51. ⓒ 2022 atama plus Inc.
    実⾏結果
    51
    検知対象コード 検知結果(実⾏結果)
    ~/work/pycon ❯ python sample_testframework.py
    Assertion Error: left: 1, right: 2 @ L3
    Assertion Error: length of the list is different. left:1 right:2 @ L4
    Assertion Error: left:[1, 4, 3], right:[1, 2, 3], index [1] is
    different @ L5
    code = """
    assert 1 == 1
    assert 1 == 2
    assert [1] == [1, 2]
    assert [1, 4, 5] == [1, 2, 3]
    “””
    配列については、より詳細な差分情報を表⽰できている

    View Slide

  52. ⓒ 2022 atama plus Inc.
    コード説明
    52
    class AssertCmpTransformer(ast.NodeTransformer):
    def visit_Assert(self, node):
    if isinstance(node.test, ast.Compare) and len(node.test.ops) == 1 and isinstance(node.test.ops[0],
    ast.Eq):
    call = ast.Call(
    func=ast.Name(
    id='customized_assert', ctx=ast.Load()),
    args=[node.test.left, node.test.comparators[0], ast.Constant(value=node.lineno)],
    keywords=[]
    )
    expr = ast.Expr(value=call)
    return expr
    return node
    def customized_assert(left, right, lineno):
    assertion_msg = None
    if isinstance(left, list) and isinstance(right, list):
    if len(left) != len(right):
    assertion_msg = f"length of the list is different. left:{len(left)} right:{len(right)}"
    else:
    if left != right:
    indexs_with_diff = []
    for i, (l, r) in enumerate(zip(left, right)):
    if l != r:
    indexs_with_diff.append(i)
    assertion_msg = f"left:{left}, right:{right}, index {indexs_with_diff} is different"
    else:
    if left != right:
    assertion_msg = f"left: {left}, right: {right}"
    if assertion_msg is not None:
    print(f"Assertion Error: {assertion_msg} @ L{lineno}")
    実装⽅針: assert関数を、より
    詳細な情報を出⼒できる別の関数
    に差し替える。
    ①:NodeTransformerを継承した
    クラスを作成。
    ②:差し替え対象が、2値間の同
    値⽐較であることをチェックする
    (今回はそれ以外はサポートせず)。
    ③:assert差し替え先のノードを
    作成し、これを返す。
    ④:型をチェックしてlist型なら、
    詳細な情報を表⽰する。
    ① ②


    View Slide

  53. ⓒ 2022 atama plus Inc.
    (余談)pytestにおけるASTの書き換え
    • pytestでも、ASTの書き換えを
    ⾏うことで、詳細情報を出⼒で
    きるようにしているようです。
    53
    https://github.com/pytest-dev/pytest/blob/main/src/_pytest/assertion/rewrite.py#L585

    View Slide

  54. ⓒ 2022 atama plus Inc.
    (余談)pytestにおけるASTの書き換え
    • pytest-ast-back-to-pythonと呼ばれるライブラリを使うと、pytestがどのよう
    な変更を加えているかを⾒ることができるようです。
    • pytest内で変換した後のastをunparseしてそう?
    54
    https://github.com/tomviner/pytest-ast-back-to-python

    View Slide

  55. ⓒ 2022 atama plus Inc.
    アジェンダ
    1. ⾃⼰紹介
    2. 本セッションの概要説明
    3. Pythonコードはどのようにして実⾏されるか
    4. ASTの構造
    5. astモジュールについて
    6. astモジュールの活⽤事例
    7. まとめ
    55

    View Slide

  56. ⓒ 2022 atama plus Inc.
    まとめ
    • Pythonのソースコードは、途中、CST、ASTに変換され、最終的にはバイトコードと
    なって、実⾏されます。
    • その中でもASTは、抽象度の⾼い⽂脈に変換された構⽂⽊であり、Moduleノードをルー
    トとして、さまざまなタイプのノードが再帰的に接続された構造を持っています。
    • ASTを検査する⽅法としてNodeVisitorが、ASTに変更を加える⽅法として
    NodeTransformerが⽤意されています。これらを継承し、振る舞いを変更したいノード
    タイプのvisitメソッドをoverrideすることで特定のノードに簡単にアクセスできます。
    • NodeVisitorやNodeTransformerを使うことで、linterやtest frameworkを簡単に書くこ
    とができるようになります。また、実際に世の中に存在するpytestなどのライブラリも、
    この仕組みを利⽤していることが知られています。
    56

    View Slide

  57. ⓒ 2022 atama plus Inc.
    参考にした情報
    • Pythonの動作全般について
    • CPYTHON INTERNALS (Amazon URL)
    • ASTに興味を持ったきっかけの本。最初の⽅の説明は、ほぼこの本の受け売りです。
    • CPythonの内部動作について、詳しく、かつ分かりやすく書かれているので是⾮。
    • Static Analysis at Scale: An Instagram Story (URL)
    • LibCSTの開発の経緯をまとめた記事。CST/ASTの違いが分かりやすく書かれています。
    • ASTについて
    • 公式ドキュメント (URL)
    • Green Tree Snakes - the missing Python AST docs (URL)
    • ASTを詳しく解説しており、活⽤例もいくつか掲載されている。ASTを使ったtesting
    frameworkの作り⽅はこのサイトの例を参考にしました。
    • Learn Python ASTs by building your own linter (URL)
    • こちらも同じく、ASTの解説記事。linterの作り⽅の参考にさせて頂きました。
    57

    View Slide

  58. ご清聴ありがとうございました!

    View Slide

  59. Q&A

    View Slide

  60. Wow students.
    ⽣徒が熱狂する学びを。
    勉強をワクワクするもの、⾃分からやりたいものに変え、
    ⽣徒⼀⼈ひとりの可能性を広げる。
    私たちのあらゆる⾏動は、ただ、そのためにあります。

    View Slide

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

    View Slide