我的编程空间,编程开发者的网络收藏夹
学习永远不晚

python ast 详解与用法

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

python ast 详解与用法

基本概念

在 python 中,我们可以通过自带的 ast 模块来对解析遍历语法树,通过ast.parse()可以将字符串代码解析为抽象语法树,然后通过ast.dump()可以打印这棵语法树。
除了ast模块外,还有 astor 模块,其中的 astor.to_sourse() 函数可以将语法树Node转换为代码, astor.dump_tree() 可以很好地格式化整棵树。
除了这些基础操作外,我们还可以遍历和修改整棵语法树。
比如,对于a = 10 来说,我们可以先解析成抽象语法树,然后打印所有的结点,如下所示。根据输出,我们可以看到根节点是Module类型的,然后其bodyAssign类型的。对于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类型的结点,如果是连续调用,会是一个ast.Call结点。
  • 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结点,表示调用者和调用的方法名,paddle是调用者,to_tensor是方法名;一个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 nodecode = 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

来源地址:https://blog.csdn.net/qq_43705697/article/details/130443928

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

python ast 详解与用法

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

python ast 详解与用法

目录 基本概念节点类型ast.Assignast.Nameast.Constantast.Callast.Attribute 结点的遍历ast源码示例 结点的修改示例 参考链接 基本概念 在 python 中,我们可
2023-08-24

详解使用抽象语法树AST实现一个AOP切面逻辑

这篇文章主要为大家介绍了使用抽象语法树AST实现一个AOP切面逻辑的简单方法,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-14

详解Python枚举的定义与用法

这篇文章主要介绍了详解Python枚举的定义与用法,在Python中,枚举和我们在对象中定义的类变量时一样的,每一个类变量就是一个枚举项,需要的朋友可以参考下
2023-05-15

python yaml用法详解

YAML是一种直观的能够被电脑识别的的数据序列化格式,容易被人类阅读,并且容易和脚本语言交互。YAML类似于XML,但是语法比XML简单得多,对于转化成数组或可以hash的数据时是很简单有效的。 一、PyYaml 1、load() :返回一
2023-01-31

Python argv用法详解

想用python处理一下文件,发现有argv这个用法,搜来学习一下。如果想对python脚步传参数,那么就需要命令行参数的支持了,这样可以省的每次去改脚步了。用法是:python xx.py xxx举例如下:#-*- coding:utf-
2022-06-04

TF-IDF算法解析与Python实现方法详解

TF-IDF(term frequency?inverse document frequency)是一种用于信息检索(information retrieval)与文本挖掘(text mining)的常用加权技术。比较容易理解的一个应用场景
2022-06-04

python浅拷贝与深拷贝使用方法详解

浅拷贝,指的是重新分配一块内存,创建一个新的对象,但里面的元素是原对象中各个子对象的引用。深拷贝,是指重新分配一块内存,创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。因此,新对象和原对象没有任何关联
2022-11-13

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录