W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
你想寫解析并分析Python源代碼的程序。
大部分程序員知道Python能夠計(jì)算或執(zhí)行字符串形式的源代碼。例如:
>>> x = 42
>>> eval('2 + 3*4 + x')
56
>>> exec('for i in range(10): print(i)')
0
1
2
3
4
5
6
7
8
9
>>>
盡管如此,<span class="pre" style="box-sizing: border-box;">ast</span>
?模塊能被用來(lái)將Python源碼編譯成一個(gè)可被分析的抽象語(yǔ)法樹(AST)。例如:
>>> import ast
>>> ex = ast.parse('2 + 3*4 + x', mode='eval')
>>> ex
<_ast.Expression object at 0x1007473d0>
>>> ast.dump(ex)
"Expression(body=BinOp(left=BinOp(left=Num(n=2), op=Add(),
right=BinOp(left=Num(n=3), op=Mult(), right=Num(n=4))), op=Add(),
right=Name(id='x', ctx=Load())))"
>>> top = ast.parse('for i in range(10): print(i)', mode='exec')
>>> top
<_ast.Module object at 0x100747390>
>>> ast.dump(top)
"Module(body=[For(target=Name(id='i', ctx=Store()),
iter=Call(func=Name(id='range', ctx=Load()), args=[Num(n=10)],
keywords=[], starargs=None, kwargs=None),
body=[Expr(value=Call(func=Name(id='print', ctx=Load()),
args=[Name(id='i', ctx=Load())], keywords=[], starargs=None,
kwargs=None))], orelse=[])])"
>>>
分析源碼樹需要你自己更多的學(xué)習(xí),它是由一系列AST節(jié)點(diǎn)組成的。 分析這些節(jié)點(diǎn)最簡(jiǎn)單的方法就是定義一個(gè)訪問(wèn)者類,實(shí)現(xiàn)很多?<span class="pre" style="box-sizing: border-box;">visit_NodeName()</span>
?方法,?<span class="pre" style="box-sizing: border-box;">NodeName()</span>
?匹配那些你感興趣的節(jié)點(diǎn)。下面是這樣一個(gè)類,記錄了哪些名字被加載、存儲(chǔ)和刪除的信息。
import ast
class CodeAnalyzer(ast.NodeVisitor):
def __init__(self):
self.loaded = set()
self.stored = set()
self.deleted = set()
def visit_Name(self, node):
if isinstance(node.ctx, ast.Load):
self.loaded.add(node.id)
elif isinstance(node.ctx, ast.Store):
self.stored.add(node.id)
elif isinstance(node.ctx, ast.Del):
self.deleted.add(node.id)
# Sample usage
if __name__ == '__main__':
# Some Python code
code = '''
for i in range(10):
print(i)
del i
'''
# Parse into an AST
top = ast.parse(code, mode='exec')
# Feed the AST to analyze name usage
c = CodeAnalyzer()
c.visit(top)
print('Loaded:', c.loaded)
print('Stored:', c.stored)
print('Deleted:', c.deleted)
如果你運(yùn)行這個(gè)程序,你會(huì)得到下面這樣的輸出:
Loaded: {'i', 'range', 'print'}
Stored: {'i'}
Deleted: {'i'}
最后,AST可以通過(guò)?<span class="pre" style="box-sizing: border-box;">compile()</span>
?函數(shù)來(lái)編譯并執(zhí)行。例如:
>>> exec(compile(top,'<stdin>', 'exec'))
0
1
2
3
4
5
6
7
8
9
>>>
當(dāng)你能夠分析源代碼并從中獲取信息的時(shí)候,你就能寫很多代碼分析、優(yōu)化或驗(yàn)證工具了。 例如,相比盲目的傳遞一些代碼片段到類似?<span class="pre" style="box-sizing: border-box;">exec()</span>
?函數(shù)中,你可以先將它轉(zhuǎn)換成一個(gè)AST, 然后觀察它的細(xì)節(jié)看它到底是怎樣做的。 你還可以寫一些工具來(lái)查看某個(gè)模塊的全部源碼,并且在此基礎(chǔ)上執(zhí)行某些靜態(tài)分析。
需要注意的是,如果你知道自己在干啥,你還能夠重寫AST來(lái)表示新的代碼。 下面是一個(gè)裝飾器例子,可以通過(guò)重新解析函數(shù)體源碼、 重寫AST并重新創(chuàng)建函數(shù)代碼對(duì)象來(lái)將全局訪問(wèn)變量降為函數(shù)體作用范圍,
# namelower.py
import ast
import inspect
# Node visitor that lowers globally accessed names into
# the function body as local variables.
class NameLower(ast.NodeVisitor):
def __init__(self, lowered_names):
self.lowered_names = lowered_names
def visit_FunctionDef(self, node):
# Compile some assignments to lower the constants
code = '__globals = globals()\n'
code += '\n'.join("{0} = __globals['{0}']".format(name)
for name in self.lowered_names)
code_ast = ast.parse(code, mode='exec')
# Inject new statements into the function body
node.body[:0] = code_ast.body
# Save the function object
self.func = node
# Decorator that turns global names into locals
def lower_names(*namelist):
def lower(func):
srclines = inspect.getsource(func).splitlines()
# Skip source lines prior to the @lower_names decorator
for n, line in enumerate(srclines):
if '@lower_names' in line:
break
src = '\n'.join(srclines[n+1:])
# Hack to deal with indented code
if src.startswith((' ','\t')):
src = 'if 1:\n' + src
top = ast.parse(src, mode='exec')
# Transform the AST
cl = NameLower(namelist)
cl.visit(top)
# Execute the modified AST
temp = {}
exec(compile(top,'','exec'), temp, temp)
# Pull out the modified code object
func.__code__ = temp[func.__name__].__code__
return func
return lower
為了使用這個(gè)代碼,你可以像下面這樣寫:
INCR = 1
@lower_names('INCR')
def countdown(n):
while n > 0:
n -= INCR
裝飾器會(huì)將?<span class="pre" style="box-sizing: border-box;">countdown()</span>
?函數(shù)重寫為類似下面這樣子:
def countdown(n):
__globals = globals()
INCR = __globals['INCR']
while n > 0:
n -= INCR
在性能測(cè)試中,它會(huì)讓函數(shù)運(yùn)行快20%
現(xiàn)在,你是不是想為你所有的函數(shù)都加上這個(gè)裝飾器呢?或許不會(huì)。 但是,這卻是對(duì)于一些高級(jí)技術(shù)比如AST操作、源碼操作等等的一個(gè)很好的演示說(shuō)明
本節(jié)受另外一個(gè)在?<span class="pre" style="box-sizing: border-box;">ActiveState</span>
?中處理Python字節(jié)碼的章節(jié)的啟示。 使用AST是一個(gè)更加高級(jí)點(diǎn)的技術(shù),并且也更簡(jiǎn)單些。參考下面一節(jié)獲得字節(jié)碼的更多信息。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: