2012-09-02 33 views
1

我決定嘗試在函數文本被編譯爲字節碼並執行後對其進行預處理。這僅僅是爲了訓練。我幾乎不能想象它會是一個令人滿意的解決方案。我遇到了一個我想用這種方式解決的問題,但最終找到了一個更好的方法。所以這只是爲了訓練和學習新東西,而不是真正的用法。在運行時bofore編譯中預處理函數文本

假設我們有一個功能,這是我們希望的源代碼在編譯之前相當多的修改:

def f(): 
    1;a() 
    print('Some statements 1') 
    1;a() 
    print('Some statements 2') 

設,例如,標誌着它的一些線與1;,他們有時被人評論有時候不是。我只是拿它舉例來說,功能的修改可能會有所不同。

要評論這些句子,我做了一個裝飾器。整個代碼它吼叫:

from __future__ import print_function 


def a(): 
    print('a()') 


def comment_1(s): 
    lines = s.split('\n') 
    return '\n'.join(line.replace(';','#;',1) if line.strip().startswith('1;') else line for line in lines) 


def remove_1(f):  
    import inspect 
    source = inspect.getsource(f)  
    new_source = comment_1(source) 
    with open('temp.py','w') as file: 
     file.write(new_source) 
    from temp import f as f_new 
    return f_new 


def f(): 
    1;a() 
    print('Some statements 1') 
    1;a() 
    print('Some statements 2') 


f = remove_1(f) #If decorator @remove is used above f(), inspect.getsource includes @remove inside the code. 

f() 

我以前inspect.getsourcelines檢索功能f代碼。然後我做了一些文本處理(在這種情況下,註釋行以1;開頭)。之後,我將它保存到temp.py模塊,然後導入該模塊。然後在主模塊中裝飾一個功能f

輸出,當施加裝飾,是這樣的:

Some statements 1 
Some statements 2 

不適用的情況是這樣的:

a() 
Some statements 1 
a() 
Some statements 2 

我不喜歡的是,我要使用硬盤驅動器加載編譯函數。無需將其寫入臨時模塊temp.py並從中導入?

第二個問題是關於放置裝飾以上f@replace。當我這樣做時,inspect.getsourcelines返回f與此裝飾器的文本。我可以手動從f的文本中刪除。但那會很危險,因爲可能會有多個裝飾器應用。所以我採用了舊式的裝飾語法f = remove_1(f),它完成了這項工作。但是,仍然可以允許正常的裝飾技術與@replace

+1

你應該修改AST,而不是源。 –

回答

1

可以通過在源上調用exec語句來避免創建臨時文件。 (如果要額外控制編譯,也可以在exec之前明確呼叫compile,但exec將爲您編譯,因此不需要)。正確調用exec還有另外一個好處,即該函數在訪問全局來自模塊命名空間的變量。

第二個問題中描述的問題可以通過臨時阻止裝飾器運行時解決。這樣,裝飾者仍然和其他所有人一樣,但是沒有任何作用。

這裏是更新後的來源。

from __future__ import print_function 

import sys 


def a(): 
    print('a()') 


def comment_1(s): 
    lines = s.split('\n') 
    return '\n'.join(line.replace(';','#;',1) if line.strip().startswith('1;') else line for line in lines) 

_blocked = False 

def remove_1(f): 
    global _blocked 
    if _blocked: 
     return f 
    import inspect 
    source = inspect.getsource(f)  
    new_source = comment_1(source) 
    env = sys.modules[f.__module__].__dict__ 
    _blocked = True 
    try: 
     exec new_source in env 
    finally: 
     _blocked = False 
    return env[f.__name__] 


@remove_1 
def f(): 
    1;a() 
    print('Some statements 1') 
    1;a() 
    print('Some statements 2') 


f() 

def remove_1(f):  
    import inspect 
    source = inspect.getsource(f)  
    new_source = comment_1(source) 
    env = sys.modules[f.__module__].__dict__.copy() 
    exec new_source in env 
    return env[f.__name__] 
+0

爲什麼我們需要製作模塊詞典字典的副本? – ovgolovin

+0

我們不必這樣做,但它似乎是一個好主意,不會意外地摧毀字典中的某些內容。我會在下一次編輯中收回它,這也會回答第二個問題。 – user4815162342

+0

哇!有用!如果我們不使用'copy','remove_1'可以變成沒有裝飾的普通功能。 http://ideone.com/avF3P – ovgolovin

0

我會user4815162342留在答案中給出的解決方案的修改版本。它使用ast模塊刪除f的某些部分,如comment to the question中所建議的那樣。爲了使我主要依靠this article中的信息。

該實現將所有出現的a都作爲獨立表達式刪除。

from __future__ import print_function 
import sys 
import ast 
import inspect 


def a(): 
    print('a() is called') 


_blocked = False 

def remove_1(f): 
    global _blocked 
    if _blocked: 
     return f 
    import inspect 
    source = inspect.getsource(f) 

    a = ast.parse(source) #get ast tree of f 

    class Transformer(ast.NodeTransformer): 
     '''Will delete all expressions containing 'a' functions at the top level''' 
     def visit_Expr(self, node): #visit all expressions 
      try: 
       if node.value.func.id == 'a': #if expression consists of function with name a 
        return None #delete it 
      except(ValueError): 
       pass 
      return node #return node unchanged 
    transformer = Transformer() 
    a_new = transformer.visit(a) 
    f_new_compiled = compile(a_new,'<string>','exec') 

    env = sys.modules[f.__module__].__dict__ 
    _blocked = True 
    try: 
     exec(f_new_compiled,env) 
    finally: 
     _blocked = False 
    return env[f.__name__] 


@remove_1 
def f(): 
    a();a() 
    print('Some statements 1') 
    a() 
    print('Some statements 2') 


f() 

輸出是:

Some statements 1 
Some statements 2