测试python AST是否相等(不是引用或对象标识)的优雅方法
不确定此处的术语,但这可能是scheme中测试python AST是否相等(不是引用或对象标识)的优雅方法,python,equality,abstract-syntax-tree,Python,Equality,Abstract Syntax Tree,不确定此处的术语,但这可能是scheme中eq?和equal?之间的差异,或者==和strncmp与C字符串之间的差异;其中,在每种情况下,对于实际具有相同内容的两个不同字符串,第一个将返回false,第二个将返回true 我正在寻找后一种操作,用于Python的AST 现在,我正在这样做: import ast def AST_eq(a, b): return ast.dump(a) == ast.dump(b) 这显然有效,但感觉像是一场即将发生的灾难。有人知道更好的方法吗 编辑:
eq?
和equal?
之间的差异,或者==
和strncmp
与C字符串之间的差异;其中,在每种情况下,对于实际具有相同内容的两个不同字符串,第一个将返回false,第二个将返回true
我正在寻找后一种操作,用于Python的AST
现在,我正在这样做:
import ast
def AST_eq(a, b):
return ast.dump(a) == ast.dump(b)
这显然有效,但感觉像是一场即将发生的灾难。有人知道更好的方法吗
编辑:不幸的是,当我去比较两个AST的
\uuuu dict\uuuu
时,该比较默认使用单个元素的\uuu eq\uu
方法。AST被实现为其他AST的树,它们的\uuuuu eq\uuu
显然检查引用标识。因此,无论是直接的==
,还是托马斯链接中的解决方案都不起作用。(除此之外,我也不想对每个AST节点类型进行子类化以插入此自定义\uuuueq\uuuu
)在Python中,使用is
操作符比较对象标识(与=
不同,它不能重载)。除非由一个白痴来实现,=
不会比较身份,而是比较平等(当然,如果可能并实现的话)。对于内置的string类,情况肯定不是这样
不过,您的实现可能还有另一个问题-由于dump生成非常精确的信息(适合调试),两个AST(例如,具有不同名称的变量)可能会被视为
=代码>。这可能是你想要的,也可能不是。我遇到了同样的问题。我试着这样做:首先,将AST简化为更简单的表示(一个目录树):
然后您可以比较这些表示:
data = open("/usr/lib/python2.7/ast.py").read()
a1 = ast.parse(data)
a2 = ast.parse(data)
print simplify(a1) == simplify(a2)
将为您提供True
编辑
只需了解无需创建dict,因此您可以执行以下操作:
def compare_ast(node1, node2):
if type(node1) is not type(node2):
return False
if isinstance(node1, ast.AST):
for k, v in vars(node1).iteritems():
if k in ('lineno', 'col_offset', 'ctx'):
continue
if not compare_ast(v, getattr(node2, k)):
return False
return True
elif isinstance(node1, list):
return all(itertools.starmap(compare_ast, itertools.izip(node1, node2)))
else:
return node1 == node2
以下内容适用于Python 2或3,并且比使用itertools更快:
编辑:警告:
显然,这段代码在某些(奇怪的)情况下会挂起。因此,我不能推荐它
def compare_ast(node1, node2):
if type(node1) != type(node2):
return False
elif isinstance(node1, ast.AST):
for kind, var in vars(node1).items():
if kind not in ('lineno', 'col_offset', 'ctx'):
var2 = vars(node2).get(kind)
if not compare_ast(var, var2):
return False
return True
elif isinstance(node1, list):
if len(node1) != len(node2):
return False
for i in range(len(node1)):
if not compare_ast(node1[i], node2[i]):
return False
return True
else:
return node1 == node2
我修改了@Yorik.sar对Python 3.9+的回答:
def compare_ast(node1:Union[ast.expr,List[ast.expr]],node2:Union[ast.expr,List[ast.expr]])->bool:
如果类型(节点1)不是类型(节点2):
返回错误
如果isinstance(节点1,ast.ast):
对于变量中的k,v(节点1)。项()
如果k in(“行号”、“列偏移量”、“ctx”):
持续
如果不比较_ast(v,getattr(node2,k)):
返回错误
返回真值
elif isinstance(节点1,列表)和isinstance(节点2,列表):
返回全部([比较zip(node1,node2)中n1,n2的ast(n1,n2)])
其他:
返回node1==node2
您正在寻找的术语是“值相等”(实际上与“引用相等”相反)。这可能会有帮助:不要使用is
,这是肯定的-{}is{}
在我的机器上返回False
。这种精度实际上是我想要的,由于我正在研究一种特定于领域的语言,其解释器会将其重写为标准python。我可能会放弃itertools和starmap,只需检查all(比较zip(node1,node2)中n1,n2的ast(n1,n2))
。此外,还需要检查长度,因为当一个迭代器比另一个迭代器短时,zip和izip只是愉快地完成了操作,没有进一步通知。在python3.8中,您应该在忽略的属性中包含“end_lineno”、“end_col_offset”“显然这段代码在某些(奇怪的)情况下会挂起。”-最好提供更多信息。这似乎缺少一个基本条件,导致了一个递归错误,至少在Python 3.9中,您必须向元组添加end\u lineno
end\u col\u offset
:如果k in(“lineno”,“col\u offset”,“ctx”,“end\u lineno”,“end\u col\u offset”):
def compare_ast(node1, node2):
if type(node1) != type(node2):
return False
elif isinstance(node1, ast.AST):
for kind, var in vars(node1).items():
if kind not in ('lineno', 'col_offset', 'ctx'):
var2 = vars(node2).get(kind)
if not compare_ast(var, var2):
return False
return True
elif isinstance(node1, list):
if len(node1) != len(node2):
return False
for i in range(len(node1)):
if not compare_ast(node1[i], node2[i]):
return False
return True
else:
return node1 == node2