python AST 抽象语法树
2021-03-07 17:30
标签:tps ocs imageview var 遍历 fir 改变 this htm Abstract Sytax Tree Python官方提供的CPython解释器对python源码的处理过程如下: 即实际python代码的处理过程如下: 上述过程在python2.5之后被应用。python源码首先被解析成语法树,随后又转换成抽象语法树。在抽象语法树中我们可以看到源码文件中的python的语法结构。 下面是一个抽象语法的简单实例。 其中 三引号可以根据书写的方式智能换行,输出如下: 这是python自带的函数 上面func_def经过compile编译得到字节码,cm即code对象,True == isinstance(cm, types.CodeType)。 可以看到,这里对源代码进行了解析 python提供了两种方式来遍历整个语法树 比如 我们将 func_def 的 add 函数中的加法运算改为减法 输出: A 使用NodeVisitor主要是通过修改语法树上节点的方式改变AST结构,NodeTransformer主要是替换ast中的节点。 输出: 可以看加入了一个print语句,然后将变量名字由 x, y 改为了 a, b Keep in mind that if the node you’re operating on has child nodes you must either transform the child nodes yourself or call the generic_visit() method for the node first. Don’t use the python AST 抽象语法树 标签:tps ocs imageview var 遍历 fir 改变 this htm 原文地址:https://www.cnblogs.com/qiulinzhang/p/14258626.html
参考:
https://docs.python.org/3/library/ast.html#ast.NodeTransformer
https://www.cnblogs.com/yssjun/p/10069199.html
Abstract Syntax Trees即抽象语法树。Ast是python源码到字节码的一种中间产物,借助ast模块可以从语法树的角度分析源码结构。此外,我们不仅可以修改和执行语法树,还可以将Source生成的语法树unparse成python源码。因此ast给python源码检查、语法分析、修改代码以及代码调试等留下了足够的发挥空间。1. AST简介
大部分时间编程可能都不需要用到抽象语法树,但是在特定的条件和需求的情况下,AST又有其特殊的方便性。func_def = """
def add(x, y):
return x+y
print(add(3,5))
"""
print(func_def)
def add(x, y):
return x+y
print(add(3,5))
2. 创建AST
2.1
compile(source, filename, mode[, flags[, dont_inherit]])
>>> cm = compile(func_def, filename=‘
2.2 生成AST
>>> cm1 = ast.parse(func_def,filename=‘
(
body=[
FunctionDef(name=‘add‘,
args=arguments(
args=[arg(arg=‘x‘, annotation=None), arg(arg=‘y‘, annotation=None)],
vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]
),
body=[Return(
value=BinOp(left=Name(id=‘x‘, ctx=Load()), op=Add(), right=Name(id=‘y‘, ctx=Load()))
)
],
decorator_list=[],
returns=None),
Expr(value=Call(
func=Name(id=‘print‘, ctx=Load()),
args=[Call(func=Name(id=‘add‘, ctx=Load()), args=[Num(n=3), Num(n=5)], keywords=[])],
keywords=[])
)
]
)
body
,可以看到,一个是FunctionDef
,也就是我们定义的add函数,另外一个是下面使用的print
函数FunctionDef
,可以看到里面的 name
是 ‘add’,也就是函数的名字是 add, 再一个就是args
,参数,可以看到一个是 ‘x‘,annotation=None,另外一个参数是 y, annntation=None; 然后里面又有一个body
,里面可以看到是 return
返回值,其中BinOp
表示双目操作符,操作符的左值为x
,操作符 op
为 Add()
,也就是将我们源代码中的 +
转换成了 Add()
函数,最后就是右值 y
print
函数,可以看到,values
是 Call
调用了一个函数,其中 函数名func为 add
,参数有两个,一个是3,一个是53. 遍历语法树
visit_nodename
函数,在里面定义参数即可FunctionDef
中存在 BinOp
节点,若想 visit BinOp
这个节点,就需要在 FunctionDef
中增加一句 self.generic_visit()
来达到递归访问;如果不加,就只能访问当前节点generic_visit(node)
This visitor calls visit() on all children of the node. Note that child nodes of nodes that have a custom visitor method won’t be visited unless the visitor calls generic_visit() or visits them itself.3.1
ast.NodeVisitor
class CodeVisitor(ast.NodeVisitor):
def visit_BinOp(self, node):# 这个函数的访问是由于 Visit_FunctionDef的先访问再generic_visit才访问的
print(‘Bin‘) # 如果Visit_FunctionDef中没有generic_visit的话,则这个函数是不会访问的
if isinstance(node.op, ast.Add):
node.op = ast.Sub()
self.generic_visit(node)
def visit_FunctionDef(self, node):
print(‘Function Name: %s‘% node.name)
self.generic_visit(node) # FunctionDef中还包含有 BinOp,因此会进去visit BinOP
def visit_Call(self, node):
print("call")
self.generic_visit(node) # 因为AST的Call中还包含有一个Call,因此会重复再访问一次
r_node = ast.parse(func_def)
visitor = CodeVisitor()
visitor.visit(r_node) # 这里的visit函数会根据 node 的语法树去遍历里面的函数,
Function Name: add
Bin
call
call
3.2
ast.NodeTransformer
NodeVisitor
subclass that walks the abstract syntax tree and allows modification of nodesclass CodeTransformer(ast.NodeTransformer):
def visit_BinOp(self, node):
if isinstance(node.op, ast.Add):
node.op = ast.Sub()
self.generic_visit(node)
return node
def visit_FunctionDef(self, node):
self.generic_visit(node) # 这里表示先去访问里面的children node
if node.name == ‘add‘:
node.name = ‘sub‘
args_num = len(node.args.args)
args_num = len(node.args.args)
args = tuple([arg.arg for arg in node.args.args])
print(str(args))
func_log_stmt = ‘‘.join(["print(‘calling func: %s‘, " % node.name, "‘args:‘", ", %s" * args_num % args ,‘)‘])
node.body.insert(0, ast.parse(func_log_stmt))
#func_log_stmt = ‘‘.join(["print ‘calling func: %s‘, " % node.name, "‘args:‘", ", %s" * args_num % args])
#node.body.insert(0, ast.parse(func_log_stmt))
return node
def visit_Name(self, node):
replace = {‘add‘: ‘sub‘, ‘x‘: ‘a‘, ‘y‘: ‘b‘}
re_id = replace.get(node.id, None)
node.id = re_id or node.id
self.generic_visit(node)
return node
def visit_arg(self, node):
self.generic_visit(node)
replace = {‘x‘:‘a‘, ‘y‘:‘b‘}
node.arg = replace[node.arg]
return node
r_node = ast.parse(func_def)
transformer = CodeTransformer()
r_node = transformer.visit(r_node)
#print(astunparse.dump(r_node))
source = astunparse.unparse(r_node) # astunparse 一般python不自带,需要conda 或者 pip安装
print(source)
(‘a‘, ‘b‘)
def sub(a, b):
print(‘calling func: sub‘, ‘args:‘, a, b)
return (a - b)
print(sub(3, 5))
NodeVisitor
if you want to apply changes to nodes during traversal. For this a special visitor exists (NodeTransformer
) that allows modifications.