python - 相同的抽象语法树是否保证相同的行为?
问题描述
给定两个生成相同抽象语法树 (AST) 的程序,它们是否保证在相同输入的情况下以相同的行为运行?
举一个具体的例子,我想对 Python 模块运行格式化程序以更改样式。为了检查格式化程序没有修改程序的逻辑,我想比较格式化模块的AST和原始的。这是一个好方法吗?
解决方案
对这个问题的非迂腐回答是肯定的,因为 AST 是编译器使用的中间表示;一旦生成了 AST,这就是用来生成字节码的东西。检查两个 AST 是否相同的一种简单方法是使用该ast.dump
函数,然后将结果作为字符串进行比较。
迂腐的答案是,这取决于您所说的“相同”是什么意思 - 具体来说,您希望比较两个 AST 的哪些属性以确定它们是否相同。
例如,x = 1; raise ValueError()
并x = 1\nraise ValueError()
编译为“相同”的 AST:
>>> import ast
>>> print(ast.dump(ast.parse('x = 1; raise ValueError()')))
Module(body=[
Assign(targets=[Name(id='x', ctx=Store())], value=Num(n=1)),
Raise(exc=Call(func=Name(id='ValueError', ctx=Load()), args=[], keywords=[]), cause=None)
])
>>> print(ast.dump(ast.parse('x = 1\nraise ValueError()')))
Module(body=[
Assign(targets=[Name(id='x', ctx=Store())], value=Num(n=1)),
Raise(exc=Call(func=Name(id='ValueError', ctx=Load()), args=[], keywords=[]), cause=None)
])
但是,AST 还包含有关行号和位置的元数据,因此这两个 AST 并不完全相同:
>>> ast.parse('x = 1; raise ValueError()').body[1].lineno
1
>>> ast.parse('x = 1\nraise ValueError()').body[1].lineno
2
此外,这些行号在运行时在错误消息中可用;第一个说line 1
,第二个说line 2
:
>>> exec('x = 1; raise ValueError()')
Traceback (most recent call last):
File "<pyshell#13>", line 1, in <module>
exec('x = 1; raise ValueError()')
File "<string>", line 1, in <module>
ValueError
>>> exec('x = 1\nraise ValueError()')
Traceback (most recent call last):
File "<pyshell#14>", line 1, in <module>
exec('x = 1\nraise ValueError()')
File "<string>", line 2, in <module>
ValueError
从技术上讲,代码也可以检查错误消息中的行号,然后据此决定其行为。任何这样的代码都是可憎的,应该被拿出来并枪毙,但作为一个经过认证的书呆子,我有责任注意到这样的代码是可以存在的。
因此,从技术上讲,您的代码格式化程序不会产生真正“相同”的 AST,因为它们的行/位置元数据可能不同 - 您的代码格式化程序必须更改该元数据才能做任何有用的事情。但是,对于像您这样的自动代码格式化工具来说,这是一个合理的警告,因为编写在您重新格式化时会中断的代码的人应该知道他们的代码太脆弱而无法被自动工具重新格式化。
为了完整起见,如果您想确保编译的字节码是相同的,您可以使用该dis.get_instructions
函数:这是一个更严格的检查,ast.dump
因为字节码包含行号(但不包括行内的位置),但如果您的格式化程序不应该t 在不同的行之间移动代码,那么您可能更喜欢这种方式。
>>> import dis
>>> instructions1 = list(dis.get_instructions('x = 1; y = 2'))
>>> instructions2 = list(dis.get_instructions('x = 1\ny = 2'))
>>> instructions1 == instructions2
False
推荐阅读
- python - “str”对象没有属性“光标”
- parameter-passing - Hippomocks 指针参数 - 提供输入,检查输出
- javascript - 如何在 localstorage 中保存 textarea 时保留换行符?
- c++ - 如何找到用于在屏幕上绘制像素并在鼠标单击时返回它们的坐标
- java - 以下方法不存在:disableRegistry()
- r - 在 R 中执行最近邻匹配时,是否可以查看哪些案例与哪些控件匹配的身份?
- stripe-payments - Stripe events:如何通过 Stripe events 和 webhook 捕获成功支付的产品?
- .net-core - DevExpress 路由似乎与 dotnet core 3.1 中断
- angular - 如何使用Angular 8中表格形式的静态数组进行排序
- java - Springframework 构建失败