优雅的方式来测试python ASTs的相等性(不是引用或对象标识)

Wan*_*ang 8 python equality abstract-syntax-tree

不知道这里的术语,但是这将是区别eq?equal?的差额,方案,或者==strncmp与C字符串; 在每种情况下,第一个将返回false为两个不同的字符串,实际上具有相同的内容,第二个将返回true.

对于Python的AST,我正在寻找后一种操作.

现在,我这样做:

import ast
def AST_eq(a, b):
    return ast.dump(a) == ast.dump(b)
Run Code Online (Sandbox Code Playgroud)

这看起来很有效,但感觉就像是等待发生的灾难.谁知道更好的方法?

编辑:不幸的是,当我去比较两个AST时__dict__,这个比较默认使用单个元素的__eq__方法.AST被实现为其他AST的树,并且它们__eq__显然检查参考标识.因此,无论是直接==还是托马斯链接的解决方案.(除此之外,我也不想将每个AST节点类型子类化以插入此自定义__eq__.)

Yor*_*sar 6

我遇到了同样的问题。我尝试这样做:首先将 AST 简化为一些更简单的表示(字典树):

def simplify(node):
    if isinstance(node, ast.AST):
        res = vars(node).copy()
        for k in 'lineno', 'col_offset', 'ctx':
            res.pop(k, None)
        for k, v in res.iteritems():
            res[k] = simplify(v)
        res['__type__'] = type(node).__name__
        return res
    elif isinstance(node, list):
        return map(simplify, node)
    else:
        return node
Run Code Online (Sandbox Code Playgroud)

然后你可以比较这些表示:

data = open("/usr/lib/python2.7/ast.py").read()
a1 = ast.parse(data)
a2 = ast.parse(data)
print simplify(a1) == simplify(a2)
Run Code Online (Sandbox Code Playgroud)

会给你 True

编辑

刚刚明白没有必要创建一个字典,所以你可以这样做:

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
Run Code Online (Sandbox Code Playgroud)

  • 我可能会放弃 itertools 和 starmap 并只检查 `all(compare_ast(n1, n2) for n1, n2 in zip(node1, node2))`。此外,还需要检查长度,因为当其中一个迭代器比另一个迭代器短时,zip 和 izip 会愉快地完成操作,而无需进一步通知。 (3认同)
  • 使用 python3.8,您应该包含 'end_lineno', 'end_col_offset' 来忽略属性 (2认同)

Sea*_*123 5

我修改了 @Yorik.sar 对 Python 3.9+ 的答案:

from itertools import zip_longest
from typing import Union


def compare_ast(node1: Union[ast.expr, list[ast.expr]], node2: Union[ast.expr, list[ast.expr]]) -> bool:
    if type(node1) is not type(node2):
        return False

    if isinstance(node1, ast.AST):
        for k, v in vars(node1).items():
            if k in {"lineno", "end_lineno", "col_offset", "end_col_offset", "ctx"}:
                continue
            if not compare_ast(v, getattr(node2, k)):
                return False
        return True

    elif isinstance(node1, list) and isinstance(node2, list):
        return all(compare_ast(n1, n2) for n1, n2 in zip_longest(node1, node2))
    else:
        return node1 == node2
Run Code Online (Sandbox Code Playgroud)


小智 -1

is在 Python 中,使用运算符(与 不同==,不能重载)来比较对象标识。除非由白痴实现,否则== 不会比较身份,而是比较平等(当然,如果可能并实现的话)。对于内置字符串类,情况肯定不是这样。

不过,您的实现可能存在另一个问题 - 由于转储会产生非常精确的信息(适合调试),因此可以考虑两个具有不同名称的变量的 ast !=。这可能是也可能不是您想要的。

  • 这种精度实际上正是我想要的,因为我正在研究一种特定于领域的语言,其解释器将其重写为标准 python。 (2认同)