测试python AST是否相等(不是引用或对象标识)的优雅方法

测试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) 这显然有效,但感觉像是一场即将发生的灾难。有人知道更好的方法吗 编辑:

不确定此处的术语,但这可能是scheme中
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