返回顶部
首页 > 资讯 > 后端开发 > Python >Python Ast抽象语法树的介绍及应用详解
  • 892
分享到

Python Ast抽象语法树的介绍及应用详解

2024-04-02 19:04:59 892人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

目录引言1. AST简介 2. 创建AST2.1 Compile函数2.2 生成ast 3. 遍历AST3.1 ast.nodeTransfer3.2 ast.N

引言

Abstract Syntax Trees即抽象语法树。Ast是python源码到字节码的一种中间产物,借助ast模块可以从语法树的角度分析源码结构。

此外,我们不仅可以修改和执行语法树,还可以将Source生成的语法树unparse成Python源码。因此ast给python源码检查、语法分析、修改代码以及代码调试等留下了足够的发挥空间。

1. AST简介 

Python官方提供的CPython解释器对python源码的处理过程如下:

Parse source code into a parse tree (Parser/pgen.c)

Transform parse tree into an Abstract Syntax Tree (Python/ast.c)

Transform AST into a Control Flow Graph (Python/compile.c)

Emit bytecode based on the Control Flow Graph (Python/compile.c)

即实际python代码的处理过程如下:

源代码解析 --> 语法树 --> 抽象语法树(AST) --> 控制流程图 --> 字节码

上述过程在python2.5之后被应用。python源码首先被解析成语法树,随后又转换成抽象语法树。在抽象语法树中我们可以看到源码文件中的python的语法结构。

大部分时间编程可能都不需要用到抽象语法树,但是在特定的条件和需求的情况下,AST又有其特殊的方便性。

下面是一个抽象语法的简单实例。

Module(body=[
    Print(
          dest=None,
          values=[BinOp( left=Num(n=1),op=Add(),right=Num(n=2))],
          nl=True,
 )])                                

2. 创建AST

2.1 Compile函数

先简单了解一下compile函数。

compile(source, filename, mode[, flags[, dont_inherit]]) 

  • source -- 字符串或者AST(Abstract Syntax Trees)对象。一般可将整个py文件内容file.read()传入。
  • filename -- 代码文件名称,如果不是从文件读取代码则传递一些可辨认的值。
  • mode -- 指定编译代码的种类。可以指定为 exec, eval, single。
  • flags -- 变量作用域,局部命名空间,如果被提供,可以是任何映射对象。
  • flags和dont_inherit是用来控制编译源码时的标志。
func_def = \
"""
def add(x, y):
    return x + y
print add(3, 5)
"""

使用Compile编译并执行:

>>> cm = compile(func_def, '<string>', 'exec')
>>> exec cm
>>> 8

上面func_def经过compile编译得到字节码,cm即code对象,

True == isinstance(cm, types.CodeType)。

compile(source, filename, mode, ast.PyCF_ONLY_AST)  <==> ast.parse(source, filename='<unknown>', mode='exec')

2.2 生成ast

使用上面的func_def生成ast.

r_node = ast.parse(func_def)
print astunparse.dump(r_node)    # print ast.dump(r_node)

 下面是func_def对应的ast结构:

Module(body=[
    FunctionDef(
        name='add',
        args=arguments(
            args=[Name(id='x',ctx=Param()),Name(id='y',ctx=Param())],
            vararg=None,
            kwarg=None,
            defaults=[]),
        body=[Return(value=BinOp(
            left=Name(id='x',ctx=Load()),
            op=Add(),
            right=Name(id='y',ctx=Load())))],
        decorator_list=[]),
    Print(
        dest=None,
        values=[Call(
                func=Name(id='add',ctx=Load()),
                args=[Num(n=3),Num(n=5)],
                keyWords=[],
                starargs=None,
                kwargs=None)],
        nl=True)
  ])

 除了ast.dump,有很多dump ast的第三方库,如astunparse, codegen, unparse等。这些第三方库不仅能够以更好的方式展示出ast结构,还能够将ast反向导出python source代码。

module Python version "$Revision$"
{
  mod = Module(stmt* body)| Expression(expr body)
  stmt = FunctionDef(identifier name, arguments args, stmt* body, expr* decorator_list)
        | ClassDef(identifier name, expr* bases, stmt* body, expr* decorator_list)
        | Return(expr? value)
        | Print(expr? dest, expr* values, bool nl)| For(expr target, expr iter, stmt* body, stmt* orelse)
  expr = BoolOp(boolop op, expr* values)
       | BinOp(expr left, operator op, expr right)| Lambda(arguments args, expr body)| Dict(expr* keys, expr* values)| Num(object n) -- a number as a PyObject.
       | Str(string s) -- need to specify raw, unicode, etc?| Name(identifier id, expr_context ctx)
       | List(expr* elts, expr_context ctx) 
        -- col_offset is the byte offset in the utf8 string the parser uses
        attributes (int lineno, int col_offset)
  expr_context = Load | Store | Del | AugLoad | AugStore | Param
  boolop = And | Or 
  operator = Add | Sub | Mult | Div | Mod | Pow | LShift | RShift | BitOr | BitXor | BitAnd | FloorDiv
  arguments = (expr* args, identifier? vararg, identifier? kwarg, expr* defaults)
}

上面是部分摘自官网的 Abstract Grammar,实际遍历ast Node过程中根据Node的类型访问其属性。

 3. 遍历AST

python提供了两种方式来遍历整个抽象语法树。

3.1 ast.NodeTransfer

将func_def中的add函数中的加法运算改为减法,同时为函数实现添加调用日志

  class CodeVisitor(ast.NodeVisitor):
      def visit_BinOp(self, node):
          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)
          func_log_stmt = ast.Print(
              dest = None,
              values = [ast.Str(s = 'calling func: %s' % node.name, lineno = 0, col_offset = 0)],
              nl = True,
              lineno = 0,
              col_offset = 0,
          )
          node.body.insert(0, func_log_stmt)
  r_node = ast.parse(func_def)
  visitor = CodeVisitor()
  visitor.visit(r_node)
  # print astunparse.dump(r_node)
  print astunparse.unparse(r_node)
  exec compile(r_node, '<string>', 'exec')

 运行结果:

Function Name:add
def add(x, y):
    print 'calling func: add'
    return (x - y)
print add(3, 5)
calling func: add
-2

3.2 ast.NodeTransformer

使用NodeVisitor主要是通过修改语法树上节点的方式改变AST结构,NodeTransformer主要是替换ast中的节点。

既然func_def中定义的add已经被改成一个减函数了,那么我们就彻底一点,把函数名和参数以及被调用的函数都在ast中改掉,并且将添加的函数调用log写的更加复杂一些,争取改的面目全非:-)

  class 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)
          if node.name == 'add':
              node.name = 'sub'
          args_num = len(node.args.args)
          args = tuple([arg.id for arg in node.args.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))
          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
  r_node = ast.parse(func_def)
  transformer = CodeTransformer()
  r_node = transformer.visit(r_node)
  # print astunparse.dump(r_node)
  source = astunparse.unparse(r_node)
  print source
  # exec compile(r_node, '<string>', 'exec')        # 新加入的node func_log_stmt 缺少lineno和col_offset属性
  exec compile(source, '<string>', 'exec')
  exec compile(ast.parse(source), '<string>', 'exec')

结果:

def sub(a, b):
    print 'calling func: sub', 'args:', a, b
    return (a - b)
print sub(3, 5)
calling func: sub args: 3 5
-2
calling func: sub args: 3 5
-2

 代码中能够清楚的看到两者的区别。这里不再赘述。

 4.AST应用

AST模块实际编程中很少用到,但是作为一种源代码辅助检查手段是非常有意义的;语法检查,调试错误,特殊字段检测等。

上面通过为函数添加调用日志的信息是一种调试python源代码的一种方式,不过实际中我们是通过parse整个python文件的方式遍历修改源码。

4.1 汉字检测

下面是中日韩字符的unicode编码范围

CJK Unified Ideographs 

Range: 4E00— 9FFF

Number of characters: 20992

Languages: chinese, japanese, korean, vietnamese

使用 unicode 范围 \u4e00 - \u9fff 来判别汉字,注意这个范围并不包含中文字符(e.g. u';' == u'\uff1b') .

下面是一个判断字符串中是否包含中文字符的一个类CNCheckHelper:

  class CNCheckHelper(object):
      # 待检测文本可能的编码方式列表
      VALID_ENcoding = ('utf-8', 'gbk')
      def _get_unicode_imp(self, value, idx = 0):
          if idx < len(self.VALID_ENCODING):
              try:
                  return value.decode(self.VALID_ENCODING[idx])
              except:
                  return self._get_unicode_imp(value, idx + 1)
      def _get_unicode(self, from_str):
          if isinstance(from_str, unicode):
              return None
          return self._get_unicode_imp(from_str)
      def is_any_chinese(self, check_str, is_strict = True):
          unicode_str = self._get_unicode(check_str)
          if unicode_str:
              c_func = any if is_strict else all
              return c_func(u'\u4e00' <= char <= u'\u9fff' for char in unicode_str)
          return False

 接口is_any_chinese有两种判断模式,严格检测只要包含中文字符串就可以检查出,非严格必须全部包含中文。

下面我们利用ast来遍历源文件的抽象语法树,并检测其中字符串是否包含中文字符。

  class CodeCheck(ast.NodeVisitor):
      def __init__(self):
          self.cn_checker = CNCheckHelper()
      def visit_Str(self, node):
          self.generic_visit(node)
          # if node.s and any(u'\u4e00' <= char <= u'\u9fff' for char in node.s.decode('utf-8')):
          if self.cn_checker.is_any_chinese(node.s, True):
              print 'line no: %d, column offset: %d, CN_Str: %s' % (node.lineno, node.col_offset, node.s)
  project_dir = './your_project/script'
  for root, dirs, files in os.walk(project_dir):
      print root, dirs, files
      py_files = filter(lambda file: file.endswith('.py'), files)
      checker = CodeCheck()
      for file in py_files:
          file_path = os.path.join(root, file)
          print 'Checking: %s' % file_path
          with open(file_path, 'r') as f:
              root_node = ast.parse(f.read())
              checker.visit(root_node)

 上面这个例子比较的简单,但大概就是这个意思。

关于CPython解释器执行源码的过程可以参考官网描述:PEP 339

4.2 Closure 检查

一个函数中定义的函数或者lambda中引用了父函数中的local variable,并且当做返回值返回。特定场景下闭包是非常有用的,但是也很容易被误用。

关于python闭包的概念可以参考我的另一篇文章:理解Python闭包概念

这里简单介绍一下如何借助ast来检测lambda中闭包的引用。代码如下:

  class LambdaCheck(ast.NodeVisitor):
      def __init__(self):
          self.illegal_args_list = []
          self._cur_file = None
          self._cur_lambda_args = []
      def set_cur_file(self, cur_file):
          assert os.path.isfile(cur_file), cur_file
          self._cur_file = os.path.realpath(cur_file)
      def visit_Lambda(self, node):
          """
          lambda 闭包检查原则:
          只需检测lambda expr body中args是否引用了lambda args list之外的参数
          """
          self._cur_lambda_args =[a.id for a in node.args.args]
          print astunparse.unparse(node)
          # print astunparse.dump(node)
          self.get_lambda_body_args(node.body)
          self.generic_visit(node)
      def record_args(self, name_node):
          if isinstance(name_node, ast.Name) and name_node.id not in self._cur_lambda_args:
              self.illegal_args_list.append((self._cur_file, 'line no:%s' % name_node.lineno, 'var:%s' % name_node.id))
      def _is_args(self, node):
          if isinstance(node, ast.Name):
              self.record_args(node)
              return True
          if isinstance(node, ast.Call):
              map(self.record_args, node.args)
              return True
          return False
      def get_lambda_body_args(self, node):
          if self._is_args(node): return
          # for cnode in ast.walk(node):
          for cnode in ast.iter_child_nodes(node):
              if not self._is_args(cnode):
                  self.get_lambda_body_args(cnode)

 遍历工程文件:

  project_dir = './your project/script'
  for root, dirs, files in os.walk(project_dir):
      py_files = filter(lambda file: file.endswith('.py'), files)
      checker = LambdaCheck()
      for file in py_files:
          file_path = os.path.join(root, file)
          checker.set_cur_file(file_path)
          with open(file_path, 'r') as f:
              root_node = ast.parse(f.read())
              checker.visit(root_node)
      res = '\n'.join([' ## '.join(info) for info in checker.illegal_args_list])
      print res

 由于Lambda(arguments args, expr body)中的body expression可能非常复杂,上面的例子中仅仅处理了比较简单的body expr。可根据自己工程特点修改和扩展检查规则。为了更加一般化可以单独写一个visitor类来遍历lambda节点。

Ast的应用不仅限于上面的例子,限于篇幅,先介绍到这里。期待ast能帮助你解决一些比较棘手的问题。

以上就是Python Ast抽象语法树的介绍及应用详解的详细内容,更多关于Python Ast抽象语法树的资料请关注编程网其它相关文章!

--结束END--

本文标题: Python Ast抽象语法树的介绍及应用详解

本文链接: https://lsjlt.com/news/119669.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Python Ast抽象语法树的介绍及应用详解
    目录引言1. AST简介 2. 创建AST2.1 Compile函数2.2 生成ast 3. 遍历AST3.1 ast.NodeTransfer3.2 ast.N...
    99+
    2024-04-02
  • 浅析AST抽象语法树及Python代码实现
    在计算机科学中,抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。树上的每个节点都...
    99+
    2022-06-04
    抽象 语法 代码
  • Python Ast介绍及应用
    Abstract Syntax Trees即抽象语法树。Ast是python源码到字节码的一种中间产物,借助ast模块可以从语法树的角度分析源码结构。此外,我们不仅可以修改和执行语法树,还可以将Source生成的语法树unparse成py...
    99+
    2023-01-30
    Python Ast
  • 详解使用抽象语法树AST实现一个AOP切面逻辑
    目录开篇一、实现目的1、work.js2、aop.js二、利用语法树添加切面事件三、总结与思考开篇 AST 功能很灵活,可以通过改变一些自定义结构便可以输入自定义的功能,下面简单的...
    99+
    2023-05-14
    AST抽象语法树实现AOP AOP切面逻辑
  • C/C++中抽象类详解及其作用介绍
    目录概述抽象类 vs 具体类案例抽象类的作用总结概述 抽象类 (abstract class), 是一些不用来定义对象, 而只作为基类被继承的类. 由于抽象类常用作基类, 所以通常称...
    99+
    2024-04-02
  • C#抽象类的用法介绍
    假设有2个类,一个类是主力球员,一个类是替补球员。 public class NormalPlayer { public int ID { get; ...
    99+
    2024-04-02
  • 怎么使用抽象语法树AST实现一个AOP切面逻辑
    这篇文章主要介绍了怎么使用抽象语法树AST实现一个AOP切面逻辑的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么使用抽象语法树AST实现一个AOP切面逻辑文章都会有所收获,下面我们一起来看看吧。一、实现目的将...
    99+
    2023-07-05
  • PHP5接口和抽象类的语法介绍
    这篇文章主要讲解了“PHP5接口和抽象类的语法介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“PHP5接口和抽象类的语法介绍”吧!大家对PHP5接口和PHP5抽象类的语法了解有多少呢?下面...
    99+
    2023-06-17
  • 详细介绍python类及类的用法
    目录一、类的成员1.1 类的属性1.1.1 静态属性的创建方式1.1.2 实例属性1.2 类的方法二、类成员的修饰符三、类的特殊成员3.1 __doc__3.2  ...
    99+
    2024-04-02
  • Java超详细介绍抽象类与接口的使用
    目录1.抽象类的语法和特性1.1语法1.2特性2.接口的语法和使用2.1语法2.2特性1.抽象类的语法和特性 1.1语法 1.在Java中,一个类如果被abstract 修饰称为抽象...
    99+
    2024-04-02
  • JDBC中PreparedStatement详解及应用场景介绍
    前言 在Java中,当需要向数据库中执行SQL语句并传递参数时,我们通常会使用PreparedStatement接口。PreparedStatement继承自Statement接口,用于预编译SQL语句并执行参数化查询,这样可以提高执行...
    99+
    2023-09-22
    sql java mybatis 数据库 mysql
  • C++中对象与类的详解及其作用介绍
    目录什么是对象面向过程 vs 面向对象面向过程面向对象什么是类类的格式类的成员函数函数访问权限方法一方法二方法三inline 成员函数什么是对象 任何事物都是一个对象, 也就是传说中...
    99+
    2024-04-02
  • MySQL ISNULL 函数详解及用法介绍
    MySQL中的ISNULL()函数是用于判断指定表达式或列是否为NULL的函数。它返回一个布尔值,如果表达式为NULL则返回1,否则返回0。ISNULL()函数可以在SELECT...
    99+
    2024-03-01
    mysql 用法 isnull sql语句
  • C语言中函数的介绍及用法
    本篇内容介绍了“C语言中函数的介绍及用法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!目录函数定义库函数定义介绍Example 1 strc...
    99+
    2023-06-20
  • Python匿名函数的介绍及用法
    这篇文章主要介绍“Python匿名函数的介绍及用法”,在日常操作中,相信很多人在Python匿名函数的介绍及用法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Python匿名函数的介绍及用法”的疑惑有所帮助!...
    99+
    2023-06-15
  • Golang中的抽象类使用方法详解
    Golang中的抽象类使用方法详解 在Go语言中,并没有传统意义上的抽象类和接口继承的概念,但是可以通过结构体嵌套和接口组合来实现类似的功能。本文将详细介绍如何在Golang中实现类似...
    99+
    2024-03-14
    方法 golang 抽象类 go语言
  • 详解Java中对象池的介绍与使用
    目录1. 什么是对象池2. 对象池解决什么问题3. 对象池的优缺点3.1 对象池的优点3.2 对象池弊端4. 对象池有什么特征5. 池的大小选择6. 对象池的使用6.1 接入6.2 ...
    99+
    2023-02-21
    Java对象池使用 Java对象池
  • pytorch框架的详细介绍与应用详解
    目录pytorch框架的详细介绍与应用一.pytorch概述1.pytorch概念2.pytorch与tensorflow的区别3.pytorch包含的内容二.pytorch常用模块...
    99+
    2023-05-15
    pytorch框架介绍 pytorch框架应用
  • Python列表list的详细用法介绍
    目录一. 创建列表1.1 第一种1.2 第二种二. 查询列表2.1 获取列表元素索引2.2 获取列表单个元素2.3 获取列表多个元素2.3 判断元素是否存在于列表三. 列表添加操作四...
    99+
    2024-04-02
  • C语言中操作符的介绍及用法
    这篇文章主要讲解了“C语言中操作符的介绍及用法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C语言中操作符的介绍及用法”吧!目录操作符分类算术操作符移位操作符整数存储规则左右移位规则赋值操作...
    99+
    2023-06-20
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作