2015-09-06 129 views
2

我正在Python中動態生成代碼。什麼是Python AST中的Expr?

爲了解決這個問題,我編寫了一個輔助方法,它接收一串Python代碼並轉儲AST。下面是一個方法:

# I want print treated as a function, not a statement. 
import __future__ 
pfcf = __future__.print_function.compiler_flag 

from ast import dump, PyCF_ONLY_AST 

def d(s): 
    print(dump(compile(s, '<String>', 'exec', pfcf|PyCF_ONLY_AST)) 

當我運行一個簡單的Hello World這個功能,它吐出以下(格式爲便於閱讀):

d("print('Hello World!')") 

Module(body=[Expr(value=Call(func=Name(id='print', 
             ctx=Load()), 
          args=[Str(s='Hello World!')], 
          keywords=[], 
          starargs=None, 
          kwargs=None))]) 

我能夠動態生成此代碼,運行它 - 一切都很棒。

然後我試圖動態生成

print(len('Hello World!')) 

應該是很容易 - 只是一個函數調用。下面是我的代碼動態生成的:

Module(body=[Expr(value=Call(func=Name(id='print', 
             ctx=Load()), 
          args=[Expr(value=Call(func=Name(id='len', 
                  ctx=Load()), 
                args=[Str(s='Hello World!')], 
                keywords=[], 
                starargs=None, 
                kwargs=None))], 
          keywords=[], 
          starargs=None, 
          kwargs=None))]) 

雖然運行它沒有工作。相反,我得到這個消息:

TypeError: expected some sort of expr, but got <_ast.Expr object at 0x101812c10> 

於是我就前面提到的我的helper方法,看看它會輸出:

d("print(len('Hello World!')") 

Module(body=[Expr(value=Call(func=Name(id='print', 
             ctx=Load()), 
          args=[Call(func=Name(id='len', 
                ctx=Load()), 
             args=[Str(s='Hello World!')], 
             keywords=[], 
             starargs=None, 
             kwargs=None)], 
          keywords=[], 
          starargs=None, 
          kwargs=None))]) 

之間什麼我產生(不工作的區別)和它產生的(它的工作原理)是他們直接將Call傳遞給args,而我把它包裝在Expr中。

問題是,在第一行中,我需要將Call換成Expr。我很困惑 - 爲什麼有時需要將Call換成Expr而不是其他時間? Expr似乎應該只是Call繼承的抽象基類,但是它在Module之下的頂級級別中是必需的。爲什麼?我錯過了些微妙的東西嗎?當Call需要包裝在Expr中以及什麼時候可以直接使用?

回答

5

Expr不是表達式本身的節點,而是表達式語句---即僅包含表達式的語句。這並不完全明顯,因爲抽象語法使用了三種不同的標識符Expr,Expressionexpr,這些標識的含義都略有不同。

語句的語法允許Expr節點作爲子節點,但Expr節點的語法不允許另一個Expr節點作爲子節點。換句話說,你所指的args值應該是一個事物即表達式列表,而不是一個Expr節點列表。見the abstract grammar的文檔,其中包括:

stmt = FunctionDef(identifier name, arguments args, 
          stmt* body, expr* decorator_list) 
      | ClassDef(identifier name, expr* bases, stmt* body, expr* decorator_list) 
      #... 
      | Expr(expr value) 

換句話說,一個可能的說法是Expr(blah),其中blah東西匹配expr語法。這是語法中唯一使用Expr,所以這個全是Expr都可以;一個Expr是一個可能的聲明,沒有別的。其他地方的語法:

expr = BoolOp(boolop op, expr* values) 
     | BinOp(expr left, operator op, expr right) 
     # other stuff notably excluding Expr(...) 
     | Call(expr func, expr* args, keyword* keywords, 
      expr? starargs, expr? kwargs) 

由於Callargs參數必須匹配expr*,它必須是東西匹配expr列表。但Expr節點不匹配expr; expr語法匹配表達式,而不是表達式語句。

注意,如果您使用的compile的「EVAL」模式時,它會編譯一個表達式,而不是語句,所以Expr節點將缺席,而頂級Module節點將通過Expression被替換:

>>> print(dump(compile('print("blah")', '<String>', 'eval', pfcf|PyCF_ONLY_AST))) 
Expression(body=Call(func=Name(id='print', ctx=Load()), args=[Str(s=u'blah')], keywords=[], starargs=None, kwargs=None)) 

可以看到,一個Expression的主體是一個單一表達式(即,expr),所以body不是列表,而是直接設置到Call節點。但是,在「exec」模式下編譯時,必須爲模塊及其語句創建額外的節點,並且Expr就是這樣一個節點。

2

什麼@BreBarn所述達成一致:

「當一個表達式,如一個函數調用,顯示爲通過本身(表達式語句)的聲明中,與它的返回值不使用或貯存,它被包裹在這個容器裏。「

由於您將len函數的結果用於print,因此AST技術上在技術上並不是Expression

更多信息請參閱本:https://greentreesnakes.readthedocs.org/en/latest/nodes.html#expressions

+1

感謝您的聯繫 - 它看起來會是這個項目非常有價值。 – ArtOfWarfare