![eccab5eff599230c5d68e53d62d1567b.png](https://img-blog.csdnimg.cn/img_convert/eccab5eff599230c5d68e53d62d1567b.png)
在阅读 Odoo 的后台 Python 代码中,经常看到 AST 相关的代码,每次遇到这些代码都不自主跳过;不久前在 Conda Python 3.8 的新环境中,Odoo 12 竟然没有跑起来,抛出的异常在 AST 相关的位置上。
odoo.addons.base.models.qweb.QWebException: required field "posonlyargs" missing from arguments
Traceback (most recent call last):
File "odoo/odoo/addons/base/models/qweb.py", line 332, in compile
unsafe_eval(compile(astmod, '<template>', 'exec'), ns)
TypeError: required field "posonlyargs" missing from arguments
Error when compiling AST
TypeError: required field "posonlyargs" missing from arguments
Template: 173
Path: /templates/t/t/form/input[2]
Node: <input type="hidden" name="redirect" t-att-value="redirect"/>
从异常的信息来看是 AST 在处理 Node 的时候出现了问题,从上面的异常信息的最后一行可见。借着这个机会看看 Python AST 究竟是啥。
什么是 AST
AST 即抽象语法树,百度一个定义:
在计算机科学中,**抽象语法树**(**A**bstract **S**yntax **T**ree,AST),或简称**语法树**(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于 if-condition-then 这样的条件跳转语句,可以使用带有两个分支的节点来表示。
关于AST的Python 官方文档。
AST 模块可帮助 Python 应用程序处理 Python 抽象语法语法的树。 每个Python 版本都可能更改抽象语法。 该模块有助于以编程方式找出当前语法的外观。
通过将ast.PyCF_ONLY_AST作为标志传递给compile()内置函数,或使用此模块中提供的parse()帮助器,可以生成抽象语法树。 结果将是一棵对象树,其所有类均继承自ast.AST。 可以使用内置的compile()函数将抽象语法树编译为Python代码对象。
Odoo 借助 Python AST 在抽象语法树的层次动态构造 Python 程序,从而能够将 QWeb Template 中的语法映射成 Python 代码。通过直接构造 AST,然后再 Compile,再求值。从而能够在 QWeb 中使用 Python 的语法,在模板文件中直接使用Python代码逻辑。
AST 中的 Node 递归起来就是一个 Tree,每个节点就是 Node;ast.parse(code_string) 能把 Python 代码分析成 AST,Python 本身没有提供把 AST 变成 Python 代码的功能,有一些第三方库可以做这个事情。举个简单的例子:
>>> import ast
>>> ast.dump(ast.parse('-5'))
'Module(body=[Expr(value=UnaryOp(op=USub(), operand=Constant(value=5, kind=None)))], type_ignores=[])'
>>>
上面的例子是给 ‘-5’ 这个常数构造 AST,其body 是一个 Expr,value 是一个 UnaryOp,operand 是 Constant。
再看一个:
>>> ast.dump(ast.parse('print("Hello World")'))
"Module(body=[Expr(value=Call(func=Name(id='print', ctx=Load()), args=[Constant(value='Hello World', kind=None)], keywords=[]))], type_ignores=[])"
>>>
这个Expr 就是一个 Call,Call 需要的参数是 func,args。