Python 进阶
Python 协程实现原理
dict 和 set 实现原理
Python 线程安全
Python 抽象语法树(AST)
Python 日志输出
Python 扩展入门(一)
Python 程序执行原理
Python 垃圾回收
Python 动态创建类
检查工具
PyFrameObject
yield 生成器工作原理
dict 设计与实现
Python 性能分析原理
PyCodeObject
Python 弱引用
Python 性能分析原理(二)
Python 源码分析(一)
Python Annotated
Python 依赖注入
python freelist
python代码编译成pyc
Python mmap 内存映射文件
Python值得学习的内容
async Future 对象
asyncio loop的实现
asyncio.sleep 的实现
asyncio 原理
Python 代码加密
Python Token类型
Python 扩展入门(二)
Python 性能优化
本文档使用 MrDoc 发布
-
+
首页
Python 抽象语法树(AST)
### CPython编译过程 >s 源代码 -> 解析树(Parse Tree) -> 抽象语法树(AST) -> 控制流程图 -> 字节码 ### 如何查看抽象语法树 示例代码 test_code.py: ```python class User(object): def __init__(self, name: str): self.name = name def work(self): print(self.name + "is working...") def hello(): print("Hello") user = User(name="olivetree") if __name__ == "__main__": hello() user.work() ``` 通过ast模块来解析出它的抽象语法树: ```Python import ast with open("test_code.py", "r") as f: ast_obj = ast.parse(f.read(), "test_code.py") print(ast_obj) print(ast.dump(ast_obj, include_attributes=False)) ``` 得到的结果如下: ``` Module( body=[ ClassDef( name='User', bases=[Name(id='object', ctx=Load())], keywords=[], body=[ FunctionDef( name='__init__', args=arguments( posonlyargs=[], args=[ arg(arg='self', annotation=None, type_comment=None), arg(arg='name', annotation=Name(id='str', ctx=Load()), type_comment=None) ], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[] ), body=[ Assign( targets=[ Attribute( value=Name(id='self', ctx=Load()), attr='name', ctx=Store() ) ], value=Name(id='name', ctx=Load()), type_comment=None ) ], decorator_list=[], returns=None, type_comment=None ), FunctionDef( name='work', args=arguments( posonlyargs=[], args=[ arg(arg='self', annotation=None, type_comment=None) ], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[] ), body=[ Expr( value=Call( func=Name(id='print', ctx=Load()), args=[ BinOp( left=Attribute( value=Name(id='self', ctx=Load()), attr='name', ctx=Load() ), op=Add(), right=Constant(value='is working...', kind=None) ) ], keywords=[] ) ) ], decorator_list=[], returns=None, type_comment=None ) ], decorator_list=[] ), FunctionDef( name='hello', args=arguments( posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[] ), body=[ Expr( value=Call( func=Name(id='print', ctx=Load()), args=[ Constant(value='Hello', kind=None) ], keywords=[] ) ) ], decorator_list=[], returns=None, type_comment=None ), Assign( targets=[Name(id='user', ctx=Store())], value=Call( func=Name(id='User', ctx=Load()), args=[], keywords=[ keyword( arg='name', value=Constant(value='olivetree', kind=None) ) ] ), type_comment=None ), If( test=Compare( left=Name(id='__name__', ctx=Load()), ops=[Eq()], comparators=[ Constant(value='__main__', kind=None) ] ), body=[ Expr( value=Call( func=Name(id='hello', ctx=Load()), args=[], keywords=[] ) ), Expr( value=Call( func=Attribute( value=Name(id='user', ctx=Load()), attr='work', ctx=Load() ), args=[], keywords=[] ) ) ], orelse=[] ) ], type_ignores=[] ) ``` ### 语法树节点的遍历 `ast.NodeVisitor` 是一个专门用来遍历语法树的类,我们可以通过继承这个类来实现对语法树各个节点的遍历,以及在遍历过程中对节点进行修改。 ``` class CodeVisitor(ast.NodeVisitor): def generic_visit(self, node): print type(node).__name__ ast.NodeVisitor.generic_visit(self, node) def visit_FunctionDef(self, node): print type(node).__name__ ast.NodeVisitor.generic_visit(self, node) def visit_Assign(self, node): print type(node).__name__ ast.NodeVisitor.generic_visit(self, node) ``` 从上面代码可以看出,遍历的类主要包含两类函数: - 一类是 `generic_visit`,这是通用访问函数; - 另一类是 `visit_节点类型`,这是专用访问函数; 遍历的时候,首先从根节点开始遍历,当访问到一个`Assign类型`的节点,有以下两种情况: - 如果存在`visit_Assign`函数,则调用`visit_Assign`; - 如果不存在该函数,则调用`generic_visit`函数; **总结来说**:`每个节点类型都可以有专用的访问函数`,如果没有,则调用通用的访问函数`generic_visit`。 > 注意,每个访问函数都需要加上`ast.NodeVisitor.generic_visit(self, node)`这段代码,否则程序不会继续访问当前节点的子节点。 ### 语法树节点的修改 `ast模块`提供了`NodeTransformer类`来支持对节点的修改。 - `NodeTransformer`继承于`NodeVisitor`,并重写了`generic_visit`方法; - `NodeTransformer`的`generci_visit`方法和`visit_节点类型`方法都需要返回一个`Node对象`; - 如果`节点转换器`的`visit_节点类型`方法返回`None`,就可以删除该节点; 假设我们有如下代码: ``` a = 10 print(a) ``` 我们定义一个`NodeTransformer`的子类来修改代码: ``` class ReWriteName(ast.NodeTransformer): def generic_visit(self, node): has_lineno = getattr(node, "lineno", "None") col_offset = getattr(node, "col_offset", "None") print(type(node).__name__, has_lineno, col_offset) ast.NodeTransformer.generic_visit(self, node) return node def visit_Name(self, node): new_node = node if node.id == "a": new_node = ast.Name(id = "a_rep", ctx = node.ctx) return new_node def visit_Num(self, node): if node.n == 10: node.n = 100 return node ``` 这个`节点转换器`主要做了两件事: - 在`visit_Name`中,将变量"a"替换成了变量"a_rep",执行到`a = 10`以及`print a`的时候,都会将a替换成a_rep,并返回一个新节点; - 在`visit_Num`中,简单粗暴的将10替换成了100,返回修改后的原节点; 我们通过如下方式运用这个NodeTransfomer visitor: ``` file = open("code.py", "r") source = file.read() visitor = ReWriteName() root = ast.parse(source) root = visitor.visit(root) ast.fix_missing_locations(root) code_object = compile(root, "<string>", "exec") exec code_object ``` 可以看到,我们同时将`a = 10`和`print a`两处将a名字换成了a_rep,并将10替换成了100,最后打印的结果是100,成功修改了语法树的节点。 ### 执行过程 >s `抽象语法树ast`作用在`Python解析语法`之后,编译成`pyCodeObject字节码`之前,通过`NodeTransformer`修改后,返回修改后的语法树,再通过内置模块compile编译成`pyCodeObject对象`,交给Python虚拟机执行。 ### 参考 >i [Python AST 语法分析](https://blog.csdn.net/ma89481508/article/details/56017697 "python ast 语法分析") [Python抽象语法树](https://www.escapelife.site/posts/40a2fc93.html "Python抽象语法树") [Python Ast介绍及应用](https://www.cnblogs.com/yssjun/p/10069199.html "Python Ast介绍及应用") [AST 模块:用 Python 修改 Python 代码](https://pycoders-weekly-chinese.readthedocs.io/en/latest/issue3/static-modification-of-python-with-python-the-ast-module.html "AST 模块:用 Python 修改 Python 代码")
gaojian
2022年1月20日 16:48
分享文档
收藏文档
上一篇
下一篇
微信扫一扫
复制链接
手机扫一扫进行分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码