17

不确定这里的术语,但这将是方案之间的区别,或者是eq?C 字符串之间的区别;在每种情况下,对于两个实际上具有相同内容的不同字符串,第一个将返回 false,而第二个将返回 true。equal?==strncmp

我正在寻找后一种操作,用于 Python 的 AST。

现在,我正在这样做:

import ast
def AST_eq(a, b):
    return ast.dump(a) == ast.dump(b)

这显然有效,但感觉就像一场等待发生的灾难。有人知道更好的方法吗?

编辑:不幸的是,当我去比较两个 AST 时__dict__,该比较默认使用单个元素的__eq__方法。AST 被实现为其他 AST 的树,它们__eq__显然会检查引用身份。==因此,Thomas 的链接工作中既不是直的也不是解决方案。(除此之外,我也不想对每个 AST 节点类型进行子类化以插入此 custom __eq__。)

4

4 回答 4

7

我遇到了同样的问题。我试图这样做:首先将 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

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

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

会给你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
于 2013-10-25T19:55:36.720 回答
3

以下适用于 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
于 2015-06-01T19:40:01.293 回答
1

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

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", "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(node1, node2)])
    else:
        return node1 == node2
于 2021-03-21T14:53:17.513 回答
0

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

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

于 2010-07-22T20:19:19.720 回答