我正在研究如何在Python中动态生成代码。

为此,我编写了一个辅助方法,该方法接收一串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中以及何时可以直接使用时,有什么规则?

#1 楼

Expr本身不是表达式的节点,而是表达式语句-即仅包含表达式的语句。这不是很明显,因为抽象语法使用三个不同的标识符ExprExpressionexpr,它们的含义略有不同。

Statement的语法允许Expr节点作为子代,但是一个Expr节点不允许另一个Expr节点作为子节点。换句话说,您所指的args值应该是表示事物的列表,而不是Expr节点的列表。请参阅抽象语法的文档,其中包括:

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),其中blahexpr的语法相匹配。这是Expr在语法中的唯一用法,因此,这就是Expr的全部功能; Expr是可能的语句,仅此而已。语法中的其他地方:由于argsCall参数必须匹配expr*,因此它必须是与expr匹配的事物的列表。但是一个Expr节点不匹配expr; expr语法与表达式匹配,而不与表达式语句匹配。

请注意,如果您使用compile的“ eval”模式,它将编译一个表达式,而不是语句,因此Expr节点将是如果没有,则顶层Module节点将被替换为Expression

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)


您会看到Expression的主体是单个表达式(即,expr ),因此body不是列表,而是直接设置到Call节点。但是,当您以“ exec”模式进行编译时,它必须为模块及其语句创建额外的节点,而Expr就是这样的节点。

评论


它可能值得注意,也可能不值得注意,但是在3.6版本中,ast docs和该参考文献在这里有些不同。 expression_stmt现在是starred_expression而不是表达式,实际上ast.parse('*(1,2,3)')会给您确切的含义,即直接位于Expr下的Starred。但是expr只允许在赋值上下文中加星标,实际上,如果您尝试评估*(1,2,3),则会得到一个特殊的SyntaxError:在AST修复后的步骤中不能在这里使用星标表达式。所以我想它们都是正确的,但是您希望ast模块是记录AST的模块。

–abarnert
18年5月3日在6:31

#2 楼

同意@BreBarn所说的内容:

”当表达式(例如函数调用)本身作为语句(表达式语句)出现时,未使用或存储其返回值时,将对其进行包装在此容器中。“

由于您正在对len使用print函数的结果,因此从技术上来说,它不是Expression

有关更多信息,请参见此信息:https://greentreesnakes.readthedocs.org/en/latest/nodes.html#expressions

评论


感谢您提供的链接-在该项目上它似乎非常有价值。

–ArtOfWarfare
2015年9月6日在23:43