python ast 详解与用法

这篇具有很好参考价值的文章主要介绍了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=[])

python ast 详解与用法

节点类型

上面的简单示例向我们展示了几种基本结点类型(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,表示函数的关键字参数。
python ast 详解与用法
下面我们看一个比较复杂的示例,多个函数的连续调用。根据输出结果可以看到,最后的调用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文章来源地址https://www.toymoban.com/news/detail-481909.html

到了这里,关于python ast 详解与用法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Thread 类基本用法详解

    Thread 是Java操作多线程 最核心 的类。 Java中创建线程的方法有很多种!!! 上述方法,只是语法规则不同,本质上是一样的方式,创造出的线程并无不同。 面试题一:请说明Thread类中run和start的区别 答案: 作用功能不同: a.run方法的作用是描述线程具体要执行的任务; b.

    2024年02月08日
    浏览(50)
  • TCP/IP详解——网络基本概念

    网络最开始是为了数据通信。 以前通过ARPA网络,卫星来实现几个计算机的互相通信。 IBM推出自己的网络协议,这时网络没有标准。 1977年:TCP/IP标准。 1980年:ARPAnet全面向TCP/IP迁移。 1984年:ISO-网络标准,国籍标准化组织机构-定制各行各业的标准。 OSI开放式系统互联,同时

    2024年02月05日
    浏览(44)
  • HTTPS协议详解:基本概念与工作原理

    个人主页: insist--个人主页​​​​​​ 本文专栏 :网络基础——带你走进网络世界 本专栏会持续更新网络基础知识,希望大家多多支持,让我们一起探索这个神奇而广阔的网络世界。 目录 一、HTTPS协议的基本概念

    2024年02月10日
    浏览(44)
  • 详解RDD基本概念、RDD五大属性

            RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。 RDD是spark core的底层核心 。 Dataset : RDD 可以不保存具体数据, 只保留创建自己的必备信息, 例如依赖和计算函数; RDD 也

    2023年04月08日
    浏览(35)
  • 【代码审计篇】 代码审计工具Fortify基本用法详解

    本篇文章讲解代码审计工具Fortify的基本用法,感兴趣的小伙伴可以研究学习一下,文中部分地方可能会有遗漏,麻烦各位大佬指正,深表感谢!!! Fortify全名叫 Fortify SCA ,是惠普公司HP的出品的一款源代码安全测试工具,这家公司也出品过另一款Web漏洞扫描器,叫做 Webin

    2024年02月05日
    浏览(41)
  • HTTP协议详解:基本概念与工作流程

    HTTP(Hypertext Transfer Protocol,超文本传输协议)是一种用于在计算机网络上进行数据交换的通信协议。它是互联网上最常用的协议之一,被广泛应用于Web浏览器和服务器之间的通信。本文将深入探讨HTTP协议的基本概念和工作流程,帮助读者更好地理解这个重要的通信协议。

    2024年02月10日
    浏览(45)
  • 【C++入门】学习使用二维数组基本知识及用法详解

    🧛‍♂️iecne个人主页: : iecne的学习日志 💡每天 关注 iecne的作品,一起进步 💪一起学习,必看iecne 🐳希望大家多多支持🥰一起进步呀! 二维数组就是在一维数组上多加一个维度。 建议:以下三种定义方式,利用第二种更加直观,提高代码可读性 第二种就是在定义一

    2024年01月25日
    浏览(55)
  • Python Selenium基本用法

    Selenium 作为一款 Web 自动化测试框架,提供了诸多操作浏览器的方法,本节对其中的常用方法做详细介绍。 定位节点 Selenium 提供了 8 种定位单个节点的方法,如下所示: 定位节点方法 方法 说明 find_element_by_id() 通过 id 属性值定位 find_element_by_name() 通过 name 属性值定位 find_

    2024年02月12日
    浏览(43)
  • 以 Golang 为例详解 AST 抽象语法树

    各位同行有没有想过一件事,一个程序文件,比如 hello.go 是如何被编译器理解的,平常在编写程序时,IDE 又是如何提供代码提示的。在这奥妙无穷的背后, AST(Abstract Syntax Tree) 抽象语法树功不可没,他站在每一行程序的身后,默默无闻的工作,为繁荣的互联网世界立下了

    2024年01月16日
    浏览(35)
  • JavaEE之网络初识(网络中的一些基本概念)详解

    😽博主CSDN主页: 小源_😽 🖋️个人专栏: JavaEE 😀努力追逐大佬们的步伐~ 目录 1. 前言 2. 网络中的一些基本概念 2.1 IP地址 2.2 端口号 2.3 网络协议 2.4 协议分层 2.5 封装 2.6 分用 (封装的逆向过程) 2.7 客户端 vs 服务器 2.8 请求, 响应 2.9 两台主机之间的网络通信流程 计算机进行

    2024年04月15日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包