Python Ast介紹及應用- IT閱讀

文章推薦指數: 80 %
投票人數:10人

Ast是python原始碼到位元組碼的一種中間產物,藉助ast模組可以從語法樹的角度分析原始碼結構。

此外,我們不僅可以修改和執行語法樹,還可以將Source生成的 ... 程式人生>>PythonAst介紹及應用 PythonAst介紹及應用 阿新••發佈:2018-12-15 AbstractSyntaxTrees即抽象語法樹。

Ast是python原始碼到位元組碼的一種中間產物,藉助ast模組可以從語法樹的角度分析原始碼結構。

此外,我們不僅可以修改和執行語法樹,還可以將Source生成的語法樹unparse成python原始碼。

因此ast給python原始碼檢查、語法分析、修改程式碼以及程式碼除錯等留下了足夠的發揮空間。

1.AST簡介  Python官方提供的CPython直譯器對python原始碼的處理過程如下: Parsesourcecodeintoaparsetree(Parser/pgen.c) TransformparsetreeintoanAbstractSyntaxTree(Python/ast.c) TransformASTintoaControlFlowGraph(Python/compile.c) EmitbytecodebasedontheControlFlowGraph(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.1Compile函式 先簡單瞭解一下compile函式。

compile(source,filename,mode[,flags[,dont_inherit]])  source--字串或者AST(AbstractSyntaxTrees)物件。

一般可將整個py檔案內容file.read()傳入。

filename--程式碼檔名稱,如果不是從檔案讀取程式碼則傳遞一些可辨認的值。

mode--指定編譯程式碼的種類。

可以指定為exec,eval,single。

flags--變數作用域,區域性名稱空間,如果被提供,可以是任何對映物件。

flags和dont_inherit是用來控制編譯原始碼時的標誌。

func_def=\ """ defadd(x,y): returnx+y printadd(3,5) """ 使用Compile編譯並執行: >>>cm=compile(func_def,'','exec') >>>execcm>>>8 上面func_def經過compile編譯得到位元組碼,cm即code物件,True==isinstance(cm,types.CodeType)。

compile(source,filename,mode,ast.PyCF_ONLY_AST) <==> ast.parse(source,filename='',mode='exec') 2.2生成ast 使用上面的func_def生成ast. r_node=ast.parse(func_def) printastunparse.dump(r_node)#printast.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,有很多dumpast的第三方庫,如astunparse,codegen,unparse等。

這些第三方庫不僅能夠以更好的方式展示出ast結構,還能夠將ast反向匯出pythonsource程式碼。

modulePythonversion"$Revision$" { mod=Module(stmt*body)|Expression(exprbody) stmt=FunctionDef(identifiername,argumentsargs,stmt*body,expr*decorator_list) |ClassDef(identifiername,expr*bases,stmt*body,expr*decorator_list) |Return(expr?value) |Print(expr?dest,expr*values,boolnl)|For(exprtarget,expriter,stmt*body,stmt*orelse) expr=BoolOp(boolopop,expr*values) |BinOp(exprleft,operatorop,exprright)|Lambda(argumentsargs,exprbody)|Dict(expr*keys,expr*values)|Num(objectn)--anumberasaPyObject. |Str(strings)--needtospecifyraw,unicode,etc?|Name(identifierid,expr_contextctx) |List(expr*elts,expr_contextctx) --col_offsetisthebyteoffsetintheutf8stringtheparseruses attributes(intlineno,intcol_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) } ViewCode   上面是部分摘自官網的 AbstractGrammar,實際遍歷astNode過程中根據Node的型別訪問其屬性。

 3.遍歷AST python提供了兩種方式來遍歷整個抽象語法樹。

3.1ast.NodeTransfer 將func_def中的add函式中的加法運算改為減法,同時為函式實現新增呼叫日誌。

1classCodeVisitor(ast.NodeVisitor): 2defvisit_BinOp(self,node): 3ifisinstance(node.op,ast.Add): 4node.op=ast.Sub() 5self.generic_visit(node) 6 7defvisit_FunctionDef(self,node): 8print'FunctionName:%s'%node.name 9self.generic_visit(node) 10func_log_stmt=ast.Print( 11dest=None, 12values=[ast.Str(s='callingfunc:%s'%node.name,lineno=0,col_offset=0)], 13nl=True, 14lineno=0, 15col_offset=0, 16) 17node.body.insert(0,func_log_stmt) 18 19r_node=ast.parse(func_def) 20visitor=CodeVisitor() 21visitor.visit(r_node) 22#printastunparse.dump(r_node) 23printastunparse.unparse(r_node) 24execcompile(r_node,'','exec')  執行結果: FunctionName:add defadd(x,y): print'callingfunc:add' return(x-y) printadd(3,5) callingfunc:add -2  3.2ast.NodeTransfer 使用NodeVisitor主要是通過修改語法樹上節點的方式改變AST結構,NodeTransformer主要是替換ast中的節點。

既然func_def中定義的add已經被改成一個減函數了,那麼我們就徹底一點,把函式名和引數以及被呼叫的函式都在ast中改掉,並且將新增的函式呼叫log寫的更加複雜一些,爭取改的面目全非:-) 1classCodeTransformer(ast.NodeTransformer): 2defvisit_BinOp(self,node): 3ifisinstance(node.op,ast.Add): 4node.op=ast.Sub() 5self.generic_visit(node) 6returnnode 7 8defvisit_FunctionDef(self,node): 9self.generic_visit(node) 10ifnode.name=='add': 11node.name='sub' 12args_num=len(node.args.args) 13args=tuple([arg.idforarginnode.args.args]) 14func_log_stmt=''.join(["print'callingfunc:%s',"%node.name,"'args:'",",%s"*args_num%args]) 15node.body.insert(0,ast.parse(func_log_stmt)) 16returnnode 17 18defvisit_Name(self,node): 19replace={'add':'sub','x':'a','y':'b'} 20re_id=replace.get(node.id,None) 21node.id=re_idornode.id22self.generic_visit(node) 23returnnode 24 25r_node=ast.parse(func_def) 26transformer=CodeTransformer() 27r_node=transformer.visit(r_node) 28#printastunparse.dump(r_node) 29source=astunparse.unparse(r_node) 30printsource 31#execcompile(r_node,'','exec')#新加入的nodefunc_log_stmt缺少lineno和col_offset屬性 32execcompile(source,'','exec') 33execcompile(ast.parse(source),'','exec') 結果: defsub(a,b): print'callingfunc:sub','args:',a,b return(a-b) printsub(3,5) callingfunc:subargs:35 -2 callingfunc:subargs:35 -2  程式碼中能夠清楚的看到兩者的區別。

這裡不再贅述。

 4.AST應用 AST模組實際程式設計中很少用到,但是作為一種原始碼輔助檢查手段是非常有意義的;語法檢查,除錯錯誤,特殊欄位檢測等。

上面通過為函式新增呼叫日誌的資訊是一種除錯python原始碼的一種方式,不過實際中我們是通過parse整個python檔案的方式遍歷修改原始碼。

4.1漢字檢測 下面是中日韓字元的unicode編碼範圍 CJKUnifiedIdeographs  Range: 4E00— 9FFF Numberofcharacters: 20992 Languages: chinese,japanese,korean,vietnamese 使用unicode範圍 \u4e00-\u9fff 來判別漢字,注意這個範圍並不包含中文字元(e.g. u';'== u'\uff1b') . 下面是一個判斷字串中是否包含中文字元的一個類CNCheckHelper: 1classCNCheckHelper(object): 2#待檢測文字可能的編碼方式列表 3VALID_ENCODING=('utf-8','gbk') 4 5def_get_unicode_imp(self,value,idx=0): 6ifidx



請為這篇文章評分?