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

Python Ast介绍及应用

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

北京

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

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

看不清楚,换张图片

免费获取短信验证码

Python Ast介绍及应用

Abstract Syntax Trees即抽象语法树。Ast是python源码到字节码的一种中间产物,借助ast模块可以从语法树的角度分析源码结构。此外,我们不仅可以修改和执行语法树,还可以将Source生成的语法树unparse成python源码。因此ast给python源码检查、语法分析、修改代码以及代码调试等留下了足够的发挥空间。

1. AST简介 

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

  1. Parse source code into a parse tree (Parser/pgen.c)
  2. Transform parse tree into an Abstract Syntax Tree (Python/ast.c)
  3. Transform AST into a Control Flow Graph (Python/compile.c)
  4. 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)
}
View Code

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

 3. 遍历AST

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

3.1 ast.NodeTransfer

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

 1 class CodeVisitor(ast.NodeVisitor):
 2     def visit_BinOp(self, node):
 3         if isinstance(node.op, ast.Add):
 4             node.op = ast.Sub()
 5         self.generic_visit(node)
 6 
 7     def visit_FunctionDef(self, node):
 8         print 'Function Name:%s'% node.name
 9         self.generic_visit(node)
10         func_log_stmt = ast.Print(
11             dest = None,
12             values = [ast.Str(s = 'calling func: %s' % node.name, lineno = 0, col_offset = 0)],
13             nl = True,
14             lineno = 0,
15             col_offset = 0,
16         )
17         node.body.insert(0, func_log_stmt)
18 
19 r_node = ast.parse(func_def)
20 visitor = CodeVisitor()
21 visitor.visit(r_node)
22 # print astunparse.dump(r_node)
23 print astunparse.unparse(r_node)
24 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写的更加复杂一些,争取改的面目全非:-)

 1 class CodeTransformer(ast.NodeTransformer):
 2     def visit_BinOp(self, node):
 3         if isinstance(node.op, ast.Add):
 4             node.op = ast.Sub()
 5         self.generic_visit(node)
 6         return node
 7 
 8     def visit_FunctionDef(self, node):
 9         self.generic_visit(node)
10         if node.name == 'add':
11             node.name = 'sub'
12         args_num = len(node.args.args)
13         args = tuple([arg.id for arg in node.args.args])
14         func_log_stmt = ''.join(["print 'calling func: %s', " % node.name, "'args:'", ", %s" * args_num % args])
15         node.body.insert(0, ast.parse(func_log_stmt))
16         return node
17 
18     def visit_Name(self, node):
19         replace = {'add': 'sub', 'x': 'a', 'y': 'b'}
20         re_id = replace.get(node.id, None)
21         node.id = re_id or node.id
22 self.generic_visit(node) 23 return node 24 25 r_node = ast.parse(func_def) 26 transformer = CodeTransformer() 27 r_node = transformer.visit(r_node) 28 # print astunparse.dump(r_node) 29 source = astunparse.unparse(r_node) 30 print source 31 # exec compile(r_node, '<string>', 'exec') # 新加入的node func_log_stmt 缺少lineno和col_offset属性 32 exec compile(source, '<string>', 'exec') 33 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:

 1 class CNCheckHelper(object):
 2     # 待检测文本可能的编码方式列表
 3     VALID_ENCODING = ('utf-8', 'gbk')
 4 
 5     def _get_unicode_imp(self, value, idx = 0):
 6         if idx < len(self.VALID_ENCODING):
 7             try:
 8                 return value.decode(self.VALID_ENCODING[idx])
 9             except:
10                 return self._get_unicode_imp(value, idx + 1)
11 
12     def _get_unicode(self, from_str):
13         if isinstance(from_str, unicode):
14             return None
15         return self._get_unicode_imp(from_str)
16 
17     def is_any_chinese(self, check_str, is_strict = True):
18         unicode_str = self._get_unicode(check_str)
19         if unicode_str:
20             c_func = any if is_strict else all
21             return c_func(u'\u4e00' <= char <= u'\u9fff' for char in unicode_str)
22         return False

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

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

 1 class CodeCheck(ast.NodeVisitor):
 2 
 3     def __init__(self):
 4         self.cn_checker = CNCheckHelper()
 5 
 6     def visit_Str(self, node):
 7         self.generic_visit(node)
 8         # if node.s and any(u'\u4e00' <= char <= u'\u9fff' for char in node.s.decode('utf-8')):
 9         if self.cn_checker.is_any_chinese(node.s, True):
10             print 'line no: %d, column offset: %d, CN_Str: %s' % (node.lineno, node.col_offset, node.s)
11 
12 project_dir = './your_project/script'
13 for root, dirs, files in os.walk(project_dir):
14     print root, dirs, files
15     py_files = filter(lambda file: file.endswith('.py'), files)
16     checker = CodeCheck()
17     for file in py_files:
18         file_path = os.path.join(root, file)
19         print 'Checking: %s' % file_path
20         with open(file_path, 'r') as f:
21             root_node = ast.parse(f.read())
22             checker.visit(root_node)

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

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

4.2 Closure 检查

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

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

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

 1 class LambdaCheck(ast.NodeVisitor):
 2 
 3     def __init__(self):
 4         self.illegal_args_list = []
 5         self._cur_file = None
 6         self._cur_lambda_args = []
 7 
 8     def set_cur_file(self, cur_file):
 9         assert os.path.isfile(cur_file), cur_file
10         self._cur_file = os.path.realpath(cur_file)
11 
12     def visit_Lambda(self, node):
13         """
14         lambda 闭包检查原则:
15         只需检测lambda expr body中args是否引用了lambda args list之外的参数
16         """
17         self._cur_lambda_args =[a.id for a in node.args.args]
18         print astunparse.unparse(node)
19         # print astunparse.dump(node)
20         self.get_lambda_body_args(node.body)
21         self.generic_visit(node)
22 
23     def record_args(self, name_node):
24         if isinstance(name_node, ast.Name) and name_node.id not in self._cur_lambda_args:
25             self.illegal_args_list.append((self._cur_file, 'line no:%s' % name_node.lineno, 'var:%s' % name_node.id))
26 
27     def _is_args(self, node):
28         if isinstance(node, ast.Name):
29             self.record_args(node)
30             return True
31         if isinstance(node, ast.Call):
32             map(self.record_args, node.args)
33             return True
34         return False
35 
36     def get_lambda_body_args(self, node):
37         if self._is_args(node): return
38         # for cnode in ast.walk(node):
39         for cnode in ast.iter_child_nodes(node):
40             if not self._is_args(cnode):
41                 self.get_lambda_body_args(cnode)

 遍历工程文件:

 1 project_dir = './your project/script'
 2 for root, dirs, files in os.walk(project_dir):
 3     py_files = filter(lambda file: file.endswith('.py'), files)
 4     checker = LambdaCheck()
 5     for file in py_files:
 6         file_path = os.path.join(root, file)
 7         checker.set_cur_file(file_path)
 8         with open(file_path, 'r') as f:
 9             root_node = ast.parse(f.read())
10             checker.visit(root_node)
11     res = '\n'.join([' ## '.join(info) for info in checker.illegal_args_list])
12     print res
View Code

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

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

免责声明:

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

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

Python Ast介绍及应用

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

下载Word文档

猜你喜欢

Python Ast介绍及应用

Abstract Syntax Trees即抽象语法树。Ast是python源码到字节码的一种中间产物,借助ast模块可以从语法树的角度分析源码结构。此外,我们不仅可以修改和执行语法树,还可以将Source生成的语法树unparse成pyt
2023-01-30

Python Fabric 模块 介绍及

来源:《Python自动化运维》Fabric的安装Fabric支持pip、easy_install或源码安装方式,很方便解决包依赖的问题,具体安装命令如下(根据用户环境,自行选择pip或easy_install):pip install f
2023-01-31

Node中的Events模块介绍及应用

events模块是node内置的核心模块这个模块是node中一个非常重要的核心模块,node中所有能触发事件的对象都是这个类的实例
2022-11-13

Python函数介绍:map函数的介绍及示例

Python函数介绍:map函数的介绍及示例在Python编程中,函数是一种非常重要的工具,可以用来封装一段可复用的代码。其中,map函数是一个强大且常用的函数,它可以对一个可迭代对象中的每一个元素应用一个指定的函数,然后返回一个新的可迭代
Python函数介绍:map函数的介绍及示例
2023-11-03

Python函数介绍:reversed函数的介绍及示例

Python函数介绍:reversed函数的介绍及示例在Python中,reversed()函数可以将一个序列类型的对象进行反转排列,返回一个包含了反转后元素的迭代器。在本文中,我们将介绍reversed()函数的具体用法,并提供一些代码示
Python函数介绍:reversed函数的介绍及示例
2023-11-04

Python函数介绍:enumerate函数的介绍及示例

Python函数介绍:enumerate函数的介绍及示例Python是一种高级编程语言,它提供了许多强大的函数,其中一个非常实用的函数是enumerate。这个函数可以帮助我们迭代序列,并返回元素的索引及其值。这篇文章将介绍enumerat
Python函数介绍:enumerate函数的介绍及示例
2023-11-03

Python函数介绍:ord函数的介绍及示例

Python函数介绍:ord函数的介绍及示例在Python编程中,ord()函数是一个很有用的函数,它用于返回给定字符的Unicode数值,即该字符在Unicode表中的位置。本篇文章将介绍ord()函数的用法、语法以及一些示例。一、ord
Python函数介绍:ord函数的介绍及示例
2023-11-04

Python函数介绍:input函数的介绍及示例

Python函数介绍:input函数的介绍及示例随着Python语言的快速发展,越来越多的人开始学习和使用Python进行编程。在Python中,函数是一种非常重要的概念,它可以帮助我们将一段代码逻辑封装起来,使得代码更加清晰和易于维护。其
Python函数介绍:input函数的介绍及示例
2023-11-04

Python函数介绍:hasattr函数的介绍及示例

Python函数介绍:hasattr函数的介绍及示例在Python中,hasattr()是一个内置函数。它的主要功能是检查一个对象是否有指定的属性或方法,并返回一个布尔值来指示是否存在。这个函数的使用方法非常简单,仅需要提供两个参数:一个对
Python函数介绍:hasattr函数的介绍及示例
2023-11-03

Python函数介绍:max函数的介绍及示例

Python函数介绍:max函数的介绍及示例函数在Python编程中是非常重要的概念。Python内置了许多有用的函数,其中一个是max函数。本文将介绍max函数的用法以及示例代码,帮助读者更好地理解和运用。max函数的作用是返回给定参数的
Python函数介绍:max函数的介绍及示例
2023-11-03

Python函数介绍:str函数的介绍及示例

Python函数介绍:str函数的介绍及示例Python是一种简单易学的编程语言,拥有丰富的内置函数来帮助开发者处理数据。其中一个常用的内置函数是str函数。str函数主要用于将其他数据类型转换为字符串类型。str函数的用法如下:str(o
Python函数介绍:str函数的介绍及示例
2023-11-03

Python函数介绍:delattr函数的介绍及示例

Python函数介绍:delattr函数的介绍及示例Python作为一门高级的编程语言,拥有丰富的内置函数库,提供了许多方便快捷的函数来进行各种操作。其中之一就是delattr函数。本文将详细介绍delattr函数的作用以及用法,并附上具体
Python函数介绍:delattr函数的介绍及示例
2023-11-03

Python函数介绍:zip函数的介绍及示例

Python函数介绍:zip函数的介绍及示例Python是一种高级语言,它提供了许多有用的函数来帮助开发人员快速地编写程序。其中一个函数就是zip函数。Zip函数是Python中的内置函数之一,它可以接受一组可迭代对象(包括列表、元组、集合
Python函数介绍:zip函数的介绍及示例
2023-11-03

Python函数介绍:range函数的介绍及示例

Python函数介绍:range函数的介绍及示例Python是一种广泛应用于各种领域的高级编程语言,它具有简单易学的特点,并且有着丰富的内置函数库。其中,range函数是Python中常用的一个内置函数之一。本文将详细介绍range函数的功
Python函数介绍:range函数的介绍及示例
2023-11-04

编程热搜

  • 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动态编译

目录