AST 模块:用Python 修改Python 代码 - PyCoder's Weekly ...
文章推薦指數: 80 %
在这篇文章中,我们将看到如何使用 ast 模块对Python 代码进行修改,同时还将看到一些使用了这个技术的工具。
CPython 的编译过程¶. http://pyimg.fanhe.org/pep339.png.
Navigation
PyCoder'sWeelkyCN»
issue3:CodeHard»
AST模块:用Python修改Python代码¶
原文地址:http://blueprintforge.com/blog/2012/02/27/static-modification-of-python-with-python-the-ast-module/
翻译:Upsuper
修改代码在有时会变的十分有用,比如在进行测试和分析的时候。
在这篇文章中,我们将看到如何使用ast模块对Python代码进行修改,同时还将看到一些使用了这个技术的工具。
CPython的编译过程¶
在开始之前,我们应该先看看CPython的编译过程,这个过程在PEP339中有详细的描述。
当然,在读这篇文章的时候,你并不需要对这个步骤有很深入的理解,不过这可以帮助你对整个过程有一个大体的了解。
首先,编译器会根据源代码生成一棵语法分析树(ParseTree),随后,再根据语法分析树建立抽象语法树(AST,AbstractSyntaxTree)。
从AST中可以生成出控制流图(CFG,ControlFlowGraph),最后再将控制流图编译为代码对象(CodeObject)。
图中标蓝的部分就是AST这一步,也就是我们今天所关注的部分。
Python从2.6开始就提供了现在这样的ast模块,它提供了一种访问和修改AST的简单方式。
通过这个,我们可以从AST中生成代码对象,也可以出于某些原因,根据修改过的AST重新生成源代码。
创建AST¶
先来写一点简单的代码,我们写一个叫做add的函数,然后观察它所生成的AST。
>>>importast
>>>expr="""
...defadd(arg1,arg2):
...returnarg1+arg2
..."""
>>>expr_ast=ast.parse(expr)
>>>expr_ast
<_ast.moduleobjectat0x10a7a09d0>
现在我们已经生成了一个ast.Module对象,我们来看看它的内容:
>>>ast.dump(expr_ast)
"Module(
body=[
FunctionDef(
name='add',args=arguments(
args=[
Name(id='arg1',ctx=Param()),
Name(id='arg2',ctx=Param())
],
vararg=None,
kwarg=None,
defaults=[]),
body=[
Return(
value=BinOp(
left=Name(id='arg1',ctx=Load()),
op=Add(),
right=Name(id='arg2',ctx=Load())))
],
decorator_list=[])
])"
正如我们所见,Module是父节点,它的body中包含了一个函数定义的元素,这个函数定义包含了函数名、参数列表和函数体。
函数体又包含了一个单独的Return节点,节点中含有一个Add运算。
修改AST¶
我们如何修改这棵树以改变代码的作用呢?为了说明这个问题,我们来做点也许你永远也不会在你自己代码中做的疯狂的事情吧。
我们将遍历这棵树,并且将Add运算修改为Mult运算。
看,我说过这很疯狂吧!
我们要先建立一个NodeTransformer变换器的子类,并且定义visit_BinOp方法。
每当这个变换器访问到一个二元运算符节点时,就会调用这个方法。
classCrazyTransformer(ast.NodeTransformer):
defvisit_BinOp(self,node):
printnode.__dict__
node.op=ast.Mult()
printnode.__dict__
returnnode
现在我们已经定义好了我们这个奇怪的变换器,让我们看看将它应用于我们开始时写的那些代码会怎么样:
>>>transformer=CrazyTransformer()
>>>transformer.visit(expr_ast)
{
'op':<_ast.addobjectat0x10a8321d0>,
'right':<_ast.nameobjectat0x10a839390>,
'lineno':3,'col_offset':8,
'left':<_ast.nameobjectat0x10a839350>}
{
'op':<_ast.multobjectat0x10a839510>,
'right':<_ast.nameobjectat0x10a839390>,
'lineno':3,'col_offset':8,
'left':<_ast.nameobjectat0x10a839350>}
你可以从输出的结果对比发现,Add节点已经被替换成了一个Mult。
我们有许多方法没有提到,比如访问子节点,不过这个例子已经足以刻画出它的基本原理。
编译和执行修改后的AST¶
我们在最初的代码后面加上一个调用,比如:
printadd(4,5)
让我们看看这些代码是如何运行的:
>>>unmodified=ast.parse(expr)
>>>execcompile(unmodified,'
重新翻译回源代码¶
最后,我们可以用unparse模块将修改后的代码转换回对应的源代码,unparse模块可以在这里找到。
>>>unparse.Unparser(modified,sys.stdout)
defadd(arg1,arg2):
return(arg1*arg2)
printadd(4,5)
正如我们所看到的,*运算符取代了+。
在这个反解析工具对于理解你的AST变换器如何修改代码很有帮助。
实践应用¶
显然,我们上面的例子在实际应用中几乎没有意义。
然而静态分析和修改代码却是十分有用的。
比如你可以为测试程序而注入一些代码。
你可以看看这篇PyCon演讲(origin)以理解如何使用一个节点转换器注入指令代码来测试程序。
除此之外,Pythonscope项目也使用了AST访问器(visitor)来处理源代码并根据函数签名生成测试。
还有像pylint这样的项目使用AST步移法(walkingmethod)来分析源代码。
在pylint中,Logilab还建立了一个模块专门用于:
“提供一个通用的Python源代码基本表示方式以为如pychecker、pyreverse或pylint等项目的开发提供方便。
”
你可以在这里看到更多关于这个项目的信息。
引用¶
MatthewJDesmarais的这篇PyCon演讲(origin)以及EliBendersky的这篇博客对于本文的帮助是无可估量的。
TableOfContents
AST模块:用Python修改Python代码
CPython的编译过程
创建AST
修改AST
编译和执行修改后的AST
重新翻译回源代码
实践应用
引用
ThisPage
ShowSource
Quicksearch
Navigation
PyCoder'sWeelkyCN»
issue3:CodeHard»
延伸文章資訊
- 1python ast用法 - 掘金
python ast用法技术、学习、经验文章掘金开发者社区搜索结果。掘金是一个帮助开发者成长的社区,python ast用法技术文章由稀土上聚集的技术大牛和极客共同编辑为你筛选 ...
- 2Python AST 抽象语法树 - 简书
因此ast给python源码检查、语法分析、修改代码以及代码调试等留下了足够的发挥空间。 1. AST简介. Python官方提供的CPython解释器对python源码的处理过程如下:. Pa...
- 3Python ast 模块使用
- 4Python Ast介绍及应用 - 博客园
Ast是python源码到字节码的一种中间产物,借助ast模块可以从语法树的角度分析源码结构。此外,我们不仅可以修改和执行语法树,还可以将Source生成的语法树 ...
- 5AST 模块:用Python 修改Python 代码 - PyCoder's Weekly ...
在这篇文章中,我们将看到如何使用 ast 模块对Python 代码进行修改,同时还将看到一些使用了这个技术的工具。 CPython 的编译过程¶. http://pyimg.fanhe.org/...