为此,我编写了一个辅助方法,该方法接收一串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
本身不是表达式的节点,而是表达式语句-即仅包含表达式的语句。这不是很明显,因为抽象语法使用三个不同的标识符Expr
,Expression
和expr
,它们的含义略有不同。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)
,其中blah
与expr
的语法相匹配。这是Expr
在语法中的唯一用法,因此,这就是Expr
的全部功能; Expr
是可能的语句,仅此而已。语法中的其他地方:由于args
的Call
参数必须匹配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
就是这样的节点。#2 楼
同意@BreBarn所说的内容:”当表达式(例如函数调用)本身作为语句(表达式语句)出现时,未使用或存储其返回值时,将对其进行包装在此容器中。“
由于您正在对
len
使用print
函数的结果,因此从技术上来说,它不是Expression
。有关更多信息,请参见此信息:https://greentreesnakes.readthedocs.org/en/latest/nodes.html#expressions
评论
感谢您提供的链接-在该项目上它似乎非常有价值。
–ArtOfWarfare
2015年9月6日在23:43
评论
它可能值得注意,也可能不值得注意,但是在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