b = a + 2 print(b) a = 1 b = a - 2 print(b) 正しく変換されたことが確認できました🎉 ast.unparseを⽤いると、コードに逆変換することもできます *) ただし、コメントや空⽩、空⾏などの情報はASTに変換した 段階でドロップしてしまうので、完全な逆変換ではありません。
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でチェックできるように したい!
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}")
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型なら、 詳細な情報を表⽰する。 ① ② ③ ④
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