python ast 详解与用法

news2024/11/26 1:31:05

目录

  • 基本概念
  • 节点类型
    • ast.Assign
    • ast.Name
    • ast.Constant
    • ast.Call
    • ast.Attribute
  • 结点的遍历
    • ast源码
    • 示例
  • 结点的修改
    • 示例
  • 参考链接

基本概念

python 语言在编译前会解析为抽象语法树,整个文件可以看作一棵树,树上的每个节点就代表一条语句,中序遍历这棵树就可以得到整个文件代码。
在 python 中,我们可以通过自带的 ast 模块来对解析遍历语法树,通过ast.parse()可以将字符串代码解析为抽象语法树,然后通过ast.dump()可以打印这棵语法树。
除了ast模块外,还有 astor 模块,其中的 astor.to_sourse()函数可以将语法树Node转换为代码, astor.dump_tree() 可以很好地格式化整棵树。
除了这些操作外,我们还可以遍历和修改整棵语法树。
比如,对于a = 10 来说,我们可以先解析成抽象语法树,然后打印所有的结点,如下所示。根据输出,我们可以看到根节点是Module类型的,然后其body是Assign类型的。对于Assign类型的结点,可以继续划分为Name结点(表示变量名)和Constant结点(表示变量内容)。

node = ast.parse('a = 10')
print(astor.dump_tree(node))
# Module(body=[Assign(targets=[Name(id='a')], value=Constant(value=10, kind=None), type_comment=None)], type_ignores=[])

在这里插入图片描述

节点类型

上面的简单示例向我们展示了几种基本结点类型(Assign、Name、Constant),接下来我们将会展示其他几种常见的结点类型和示例,完整的节点类型可以查阅节点类型。大体上,我们可以把结点类型分为叶子结点类型和非叶子结点类型,比如Assign就是非叶子结点类型,NameConstant是叶子结点类型,因为他们不会有子结点了。

ast.Assign

Assign 类型用来表示赋值语句,比如a = 10b = a 这样的赋值语句都是Assign结点类型,他并不是一个叶子结点,因为它的下面一般还有 Name 结点。

ast.Name

Name类型用来表示一个变量的名称,是一个叶子结点。比如对于b = a 这样的赋值语句,子结点就是两个Name

node = ast.parse('a = b')
print(astor.dump_tree(node.body[0]))
# Assign(targets=[Name(id='a')], value=Name(id='b'), type_comment=None)

ast.Constant

表示一个不可变内容,它可以是Numberstring,只要其内容是不可变的,都是ast.Constant类型的结点,它是一个叶子结点

node = ast.parse('a = 100')
print(astor.dump_tree(node.body[0]))
# Assign(targets=[Name(id='a')], value=Constant(value=100, kind=None), type_comment=None)

node = ast.parse('a = "paddle"')
print(astor.dump_tree(node.body[0]))
# Assign(targets=[Name(id='a')], value=Constant(value='paddle', kind=None), type_comment=None)

ast.Call

表示函数的调用,比如paddle.to_tensor()。非叶子节点类型,一般包含三个属性:func、args、 keywords。

  • func:代表调用函数的名称,一般是一个ast.Nameast.Constant类型的结点。
  • args:代表函数传入的位置参数和可变参数。
  • keywords:代表函数传入的关键字参数。
node = ast.parse('paddle.to_tensor(1, a = 10)')
print(astor.dump_tree(node.body[0]))

# Expr(
    value=Call(func=Attribute(value=Name(id='paddle'), attr='to_tensor'),
        args=[Constant(value=1, kind=None)],
        keywords=[keyword(arg='a', value=Constant(value=10, kind=None))]))

对于上面的例子,我们通过可视化可以看到,顶层是一个ast.Expr类型的结点,表示一个表达式。下面是ast.Call 结点Call 结点包含 一个ast.Attribute结点,表示调用的方法名;一个ast.Constant类型的args,表示函数的位置参数;一个ast.keyword,表示函数的关键字参数。
在这里插入图片描述
下面我们看一个比较复杂的示例,多个函数的连续调用。根据输出结果可以看到,最后的调用reshape在最外层,然后一直向内递归,子结点又是一个ast.Call类型的结点。

node = ast.parse('a.to_tensor(1, a = 10).reshape(1)')
print(astor.dump_tree(node.body[0]))

Expr(
    value=Call(
        func=Attribute(
            value=Call(func=Attribute(value=Name(id='a'), attr='to_tensor'),      
                args=[Constant(value=1, kind=None)],
                keywords=[keyword(arg='a', value=Constant(value=10, kind=None))]),
            attr='reshape'),
        args=[Constant(value=1, kind=None)],
        keywords=[]))

ast.Attribute

上面的例子中出现了ast.Attribute结点,Attribute结点可以理解为属性,是一个非叶子结点。它包含两个字段,value字段和attr字段。对于a.shape来说value指明调用的前半部分,即aattr指明调用的后半部分,即shape

node = ast.parse('a.shape')
print(astor.dump_tree(node.body[0]))

Expr(value=Attribute(value=Name(id='a'), attr='shape'))

结点的遍历

ast模块中,可以借助继承ast.NodeVisitor类来完成结点的遍历,该类具有两种访问结点的方法,一种是针对所有结点类型通用的访问方法generic_visit(),另一种是针对某个类型结点的访问方法 visit_xxx,其中xxx代表具体的结点类型。generic_visit()函数是遍历每个结点的入口函数,随后会调用visitor()函数,获取该结点的类型,然后判断是否有遍历该类型结点的函数,如果有则调用 visit_xxx类型的方法,如果没有则调用generic_visit()方法。

ast源码

class NodeVisitor(object):
    def visit(self, node):
        """Visit a node."""
        method = 'visit_' + node.__class__.__name__
        visitor = getattr(self, method, self.generic_visit)
        return visitor(node)

    def generic_visit(self, node):
    	# 可以看到 generic_visit函数会调用visit函数,然后寻找并调用特定类型的visit函数。 
        """Called if no explicit visitor function exists for a node."""
        for field, value in iter_fields(node):
            if isinstance(value, list):
                for item in value:
                    if isinstance(item, AST):
                        self.visit(item)
            elif isinstance(value, AST):
                self.visit(value)

    def visit_Constant(self, node):
        value = node.value
        type_name = _const_node_type_names.get(type(value))
        if type_name is None:
            for cls, name in _const_node_type_names.items():
                if isinstance(value, cls):
                    type_name = name
                    break
        if type_name is not None:
            method = 'visit_' + type_name
            try:
                visitor = getattr(self, method)
            except AttributeError:
                pass
            else:
                import warnings
                warnings.warn(f"{method} is deprecated; add visit_Constant",
                              PendingDeprecationWarning, 2)
                return visitor(node)
        return self.generic_visit(node)

示例

下面是一个例子,我们定义了一个继承ast.NodeVisitor的类,并且重写了visit_attribute方法,这样在遍历到ast.Attribute结点时,会输出当前调用的属性名或方法名,对于其他类型的结点则会输出结点类型。

class CustomVisitor(ast.NodeVisitor):
    def visit_Attribute(self, node):
        print('----' + node.attr)
        ast.NodeVisitor.generic_visit(self, node)

    def generic_visit(self, node):
        print(node.__class__.__name__)
        ast.NodeVisitor.generic_visit(self, node)

code = textwrap.dedent(
    '''
    import paddle
    x = paddle.to_tensor([1, 2, 3])
    axis = 0
    y = paddle.max(x, axis=axis)
    '''
)
node = ast.parse(code)
visitor = CustomVisitor()
visitor.generic_visit(node)

需要注意的是,当我们重写visit_xxx函数后,一定要记得再次调用ast.NodeVisitor.generic_visit(self, node),这样才会继续遍历整棵语法树。

结点的修改

对于结点的修改可以借助ast.NodeTransformer 类来完成,ast.NodeTransformer继承自ast.NodeVisitor类,重写了generic_visit方法,该方法可以传入一个结点,并且返回修改后的结点,从而完成语法树的修改。

示例

在该示例中,我们定义了CustomVisitor类来修改ast.Call 结点。具体来说,当遍历到Call类型的结点后,流程如下:

  • 首先会调用get_full_attr方法获取整个api名称,如果是普通方法调用,则会返回完整的调用名称,比如torch.tensor()会返回torch.tensor;如果是连续的方法调用,比如x.exp().floor(),则会返回ClassMethod.floor
  • 然后调用 ast.NodeVisitor.generic_visit(self, node) ,进行深度优先的修改,这样就可以一层层递归,先修改后内层,再修改外层。
  • 接着判断如果是普通的方法调用,则修改结点后返回;
  • 如果是连续的方法调用,需要先通过astor.to_source(node)获取前缀方法,保留前缀方法名称的同时,修改目前的方法名后返回。具体是通过'{}.{}()'实现的。
def get_full_attr(node):
        # torch.nn.fucntional.relu
        if isinstance(node, ast.Attribute):
            return get_full_attr(node.value) + '.' + node.attr
        # x.abs() -> 'x'
        elif isinstance(node, ast.Name):
            return node.id
        # for example ast.Call
        else:
            return 'ClassMethod'
            
class CustomVisitor(ast.NodeTransformer):
    
    def visit_Call(self, node):
        # 获取api的全称
        full_func = get_full_attr(node.func)

        # post order
        ast.NodeVisitor.generic_visit(self, node)
        
        # 如果是普通方法调用,直接改写整个结点即可
        if full_func == 'torch.tensor':
            # 将 torch.tensor() 改写为 paddle.to_tensor()
            code = 'paddle.to_tensor()'
            new_node = ast.parse(code).body[0]
            return new_node.value
        
        # 如果是类方法调用,需要取前面改写后的方法作为 func.value 
        if full_func == 'ClassMethod.floor':
            # 获取前缀方法作为 func.value
            new_func = astor.to_source(node).strip('\n')
            new_func = new_func[0: new_func.rfind('.')]
            # 将 floor() 改写为 floor2()
            code = '{}.{}()'.format(new_func, 'floor2')
            new_node = ast.parse(code).body[0]
            return new_node.value

        # 其余结点不修改
        return node

code = textwrap.dedent(
    '''
    import torch
    x = torch.tensor([1, 2, 3])
    x = x.exp().floor()
    '''
)
node = ast.parse(code)
visitor = CustomVisitor()
node = visitor.generic_visit(node)
result_code = astor.to_source(node)
print(result_code)

参考链接

https://blog.csdn.net/ThinkTimes/article/details/110831176?ydreferer=aHR0cHM6Ly9jbi5iaW5nLmNvbS8%3D
https://greentreesnakes.readthedocs.io/en/latest/
https://github.com/PaddlePaddle/PaConvert

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/477974.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机中找不到msvcp140.dll无法继续执行代码,解决方法

DLL是Dynamic Link Library的缩写,意为动态链接库。dll文件是电脑系统及软件运行的重要文件,电脑如果丢失dll文件,那么很多软件跟游戏都是无法运行的,msvcp140.dll丢失这个问题就有很多小伙伴遇到,小编今天就分享找不到…

山东专升本计算机第一章-计算机信息技术与计算机文化

计算机信息技术与计算机文化 计算机中的信息表示 数制及其转换 数制:用进位的原则进行计数数码:数制中表示基本数值大小的不同数字符号基数:一种数制所使用的数码个数位权:数码在不同位置的权值 数制的转换 • R进制转化为十进…

【操作系统和强化学习】1.内存管理策略

文章目录 前言1.内存是什么1.1 基本硬件保护措施 碎片1.2 分段机制1.2.1 分段硬件1.3 分页 前言 CPU的调度可以提高CPU的利用率和计算机的响应用户的速度。为了改进性能,应该将多个进程保存在内存中。也就是说必须共享内存。 本文讨论的是如管理内存。 1.内存是什么…

知识变现海哥:我是如何通过知识变现年赚100万的

我叫王海青,大家都称我海哥,是一名知识变现导师,海青教育创始人。从2011年开始,已经从事知识变现培训服务10多年,帮助3000人实现了个人知识技能变现,一直致力于帮助普通人,把自己的一项知识技能…

TiDB实战篇-操作系统参数TiDB Server

操作系统参数 CPU 绿色的是设置cpu用最高的频率去使用cpu。 专门有一个内存给指定的cpu使用。减少cpu的相应时间。 内存 大页的优化,操作系统关闭大页 。 磁盘 TiDB Server配置 cpu 同时多少个会话 如果负载很高那么就往下面调节。 控制优先级(…

计算卸载论文阅读01-理论梳理

标题:When Learning Joins Edge: Real-time Proportional Computation Offloading via Deep Reinforcement Learning 会议:ICPADS 2019 一、梳理 问题:在任务进行卸载时,往往忽略了任务的特定的卸载比例。 模型:针…

BUUCTF ciscn_2019_c_1

小白垃圾做题笔记而已,不建议阅读。 1前期: 其实刚开始拿到程序的时候我还以为是逆向题放错地方了。唉,做题太少了。啥也不会。我是大笨蛋。 题目中用的是ubuntu18,我的ubuntu没怎么用过,vmtools都不能用&#xff0c…

【源码解析】canal核心功能源码解析

1. 项目地址 https://github.com/alibaba/canal.git2. 模块介绍 canal核心模块的功能: deployer模块:独立部署模块,用于canal-server的独立启动,包括本地配置解析、拉取远程配置、启动canal-server。server模块:cana…

山东专升本计算机第六章-数据库技术

数据库技术 SQL数据库与NOSQL数据库的区别 数据库管理系统 考点 6 数据库管理系统的组成和功能 组成 • 模式翻译 • 应用程序的翻译 • 交互式查询 • 数据的组织和存取 • 事务运行管理 • 数据库的维护 功能 • 数据定义功能 • 数据存取功能 • 数据库运行管理…

MySQL备份和恢复

文章目录 一、库的备份和恢复1.库的备份2.库的恢复 二、表的备份和恢复1.表的备份2.表的恢复 备份数据,其实就是生成一个 sql 文件,把创建数据库、创建表、插入数据等各种 SQL 语句都装载到这个文件中。恢复数据,其实就是按顺序执行 sql 文件…

操作系统进程概述、通信

进程 进程就是程序的一次执行过程,同一个程序多次执行对应多个进程; 一、进程的组成 1、PCB (1)进程描述信息:主要是PID等关键信息; (2)进程控制和管理信息; &#xf…

( 数组和矩阵) 566. 重塑矩阵 ——【Leetcode每日一题】

❓566. 重塑矩阵 难度:简单 在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原始数据。 给你一个由二维数组 mat 表示的 m x n 矩阵…

unity-VRTK-simulator开发学习日记3(射线样式|忽略层|有无效名单)

目录 射线样式 组成 可用状态 材质替换 射线激活设置为常态 忽略层级(射线等) 自定义忽略层级 (射线等) 有效名单和无效名单 有效名单 无效名单 创建一个模拟手柄的按钮(键盘键入按钮) 输入系统…

最新Wordpress网站因重装宝塔而导致数据库崩溃无法访问的终极解决办法

在当今数字化时代,拥有自己的网站已成为越来越多人展示个人或企业信息的重要方式。虽然建立并维护自己网站看起来是一个简单的过程,但如果出现问题,比如数据丢失、网站无法正常运行等情况时,往往会令用户感到十分懊恼和无助。在这…

sql注入(二)盲注,二次注入,宽字节注入

目录 目录 一、布尔盲注 1.判断库名的长度 2.判断数据库名 2.1判断数据库名首字符 2.2 判断数据库名的其余字符 二、时间盲注: 1.判断库名的长度 2.判断库名: 3.判断表名payload: 4.爆出列名 5.爆数据 三、二次注入 1.原理&#…

【某软件网络协议分析】

由于网站无法上传附件,本帖子完整内容请点击此处 首先,从coco.apk提取dex文件,利用dex2jar将dex转化为jar,拖到jd-gui中,发现有如下几个可疑点: com.azus.android.tcplogin.CryptUtil.rsaEncrypt com.azus…

MySQL示例数据库(MySQL Sample Databases) 之 Employees 数据库

文章目录 MySQL示例数据库(MySQL Sample Databases) 之 Employees 数据库官方示例数据介绍Employees 数据库Employees 数据库安装Employees 数据库的结构参考 MySQL示例数据库(MySQL Sample Databases) 之 Employees 数据库 官方示例数据介绍 MySQL 官方提供了多个示例数据库…

二叉树及其遍历

文章目录 二叉树树的定义二叉树的定义遍历先序遍历中序遍历后序遍历层次遍历定义队列层次创建二叉树层次遍历 二叉树 树是一种非线性的数据结构,由若干个节点组成,节点之间存在一种父子关系,具有层次结构。二叉树是一种特殊的树结构&#xff…

火遍全网的ChatGPT究竟是什么?

ChatGPT是什么 ChatGPT是一个由OpenAI开发的大型语言模型,基于GPT-3.5架构。它被训练用于自然语言处理和生成任务,可以回答各种问题,包括一般知识、文化、科学、技术、商业、娱乐等方面的问题。ChatGPT可以进行对话,回答用户的问…

OJ刷题 第十四篇(递归较多)

23204 - 进制转换 时间限制 : 1 秒 内存限制 : 128 MB 将一个10进制数x(1 < x < 100,000,000)转换成m进制数(2< m < 16) 。分别用 ABCDEF表示10以上的数字。 输入 x m (1 < x < 100,000,000, 2< m < 16) 输出 m进制数 样例 输入 31 16 输出 1F 答…