Python抽象语法树

Python抽象语法树

前言

抽象语法树(Abstract Syntax Tree, AST)是任何语言源代码的抽象结构的树状表示,包括Python语言。
作为Python自己的抽象语法树,它是基于对Python源文件的解析而构建的。

基础

了解Python抽象语法树的最简单的方式就是解析一段Python代码并将其转储从而生成抽象语法树。要做到这一点,Python的ast模块就可以满足需要。

示例: 将Python代码解析成抽象语法树

>>> import ast
>>> ast.parse
<function parse at 0x10e7d6048>
>>> ast.parse("x=42")
<_ast.Module object at 0x10e7dd710>
>>> ast.dump(ast.parse("x=42"))
"Module(body=[Assign(targets=[Name(id='x', ctx=Store())], value=Num(n=42))])"
>>> 

ast.parse函数会返回一个_ast.Module对象,作为树的根。这个树可以通过ast.dump模块整个转储。如下所示:

抽象语法树的构建通常从根元素开始,根元素通常是一个ast.Module对象。这个对象在其body属性中包含一组待求值的语句或者表达式。它通常表示这个文件的内容。

很容易猜到,ast.Assign对象表示赋值,在Python语法中它对应=。Assign有一组目标,以及一个要赋的值。在这个例子中只有一个对象ast.Name,表示变量x。值是数值42。

抽象语法树能够被传入Python并编译和求值。作为Python内置函数提供的compile函数是支持抽象语法树的。

>>> compile(ast.parse("x=42"),'<input>','exec')
<code object <module> at 0x10e79ddb0, file "<input>", line 1>
>>> eval(compile(ast.parse("x=42"),'<input>','exec'))
>>> 

通过ast模块中提供的类可以手工构建抽象语法树。如下所示:
使用Python抽象语法树的Hello world

我这个在python3.5下没有成功。因为ast没有Print属性了。

抽象语法树中可用的完整对象列表通过阅读_ast模块的文档可以很容易获得。

首先需要考虑的两个分类是语句和表达式。
语句涵盖的类型包括assert(断言)、赋值(=)、增量赋值(+=、/=等)、global、def、if、return、for、class、pass、import等。 它们都继承自ast.stmt。
表达式涵盖的类型包括lambda、number、yield、name(变量)、compare或者call。它们都继承自ast.expr。

还有其他一些分类,例如,ast.operator用来定义标准的运算符,如加(+)、除(/)、右移(>>)等,ast.cmpop用来定义比较运算符。

很容易联想到有可能利用抽象语法树构造一个编译器,通过构造一个Python抽象语法树来解析字符串并生成代码。

如果需要遍历树,可以用ast.walk函数来做这件事。但ast模块还提供了NodeTransformer,一个可以创建其子类来遍历抽象语法树的某些节点的类。因此用它来动态修改代码很容易。为加法修改所有的二元运算如下所求。

class ReplaceBinOp(ast.NodeTransformer):
    def visit_BinOp(self,node):
        return ast.BinOp(left = node.left, op=ast.Add(),right=node.right)

tree = ast.parse("x = 1/3")
ast.fix_missing_locations(tree)
eval(compile(tree,'','exec'))
print(ast.dump(tree))
print(x)
tree = ReplaceBinOp().visit(tree)
ast.fix_missing_locations(tree)
print(ast.dump(tree))
eval(compile(tree,'','exec'))
print(x)

结果输出:

Module(body=[Assign(targets=[Name(id=’x’, ctx=Store())], value=BinOp(left=Num(n=1), op=Div(), right=Num(n=3)))])
0.3333333333333333
Module(body=[Assign(targets=[Name(id=’x’, ctx=Store())], value=BinOp(left=Num(n=1), op=Add(), right=Num(n=3)))])
4

Hy

初步了解抽象语法树之后,可以畅想一下为Python创建一种新的语法,并将其解析并编译成标准的Python抽象语法树。Hy编程语言(http://docs.hylang.org/en/latest/)做的正是这件事。它是Lisp的一个变种,可以解析类Lisp语言并将其转换为标准的Python抽象语法树。因此这同Python生态系统完全兼容。

安装hy可以通过pip install hy

使用

$hy
hy 0.12.1 using CPython(default) 3.5.2 on Darwin
=> (+ 1 1)
2

在Lisp语法中,圆括号表示一个列表,第一个元素是一个函数,其余元素是该函数的参数。因此,上面的代码相当于Python中的1+1

大多数结构都是从Python直接映射过来的,如函数定义。变量的设置则依赖于setv函数。

=> (defn hello [name]
... (print "Hello world!")
... (print (% "Nice to meet you %s" name)))
=> (hello "jd")
Hello world!
Nice to meet you jd
=>

在内部,Hy对提供的代码进行解析并编译成Python抽象语法树。幸运的是,Lisp比较容易解析成树,因为每一对圆括号都可以表示成列表树的一个节点。需要做的仅仅是将Lisp树转换为Python抽象语法树。

通过defclass结构可支持类定义。

你可以直接导入任何Python库到Hy中并随意使用。

=> (import uuid)
=> (uuid.uuid4)
UUID('d0ea0a6a-6b69-4a52-85b4-abf23749d121')

Hy是一个非常不错的项目,因为它允许你进入Lisp的世界又不用离开你熟悉的领域,因为你仍然在写Python。hy2py工具甚至能够显示Hy代码转换成Python之后的样子。

发表评论

电子邮件地址不会被公开。 必填项已用*标注