элегантный способ проверить AST на Python на равенство (не ссылочную или объектную идентичность) - PullRequest
7 голосов
/ 23 июля 2010

Не уверен в терминологии здесь, но это будет разница между eq? и equal? в схеме или разница между == и strncmp со строками C; где в каждом случае первое возвращало бы false для двух разных строк, которые фактически имеют одинаковое содержимое, а второе возвращало бы true.

Я ищу последнюю операцию для AST Python.

Прямо сейчас я делаю это:

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

, который, очевидно, работает, но ощущается как катастрофа, ожидающая случившегося. Кто-нибудь знает лучший способ?

Редактировать : к сожалению, когда я иду сравнивать два AST __dict__, это сравнение по умолчанию использует методы __eq__ отдельных элементов. AST реализованы в виде деревьев других AST, и их __eq__, очевидно, проверяет идентичность ссылок. Так что ни прямая ==, ни решение в томасовой ссылке не работают. (Кроме того, я также не хочу создавать подклассы для каждого типа узла AST, чтобы вставить этот пользовательский __eq__.)

Ответы [ 3 ]

4 голосов
/ 25 октября 2013

Я столкнулся с той же проблемой. Я попытался пойти по этому пути: сначала тупой 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

EDIT

Просто понял, что создавать дикт не нужно, поэтому вы можете просто:

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
3 голосов
/ 01 июня 2015

Следующее работает с 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
1 голос
/ 23 июля 2010

В Python идентификатор объекта сравнивается с помощью оператора is (который, в отличие от ==, не может быть перегружен).Если не реализовано дебилом, == не будет сравнивать идентичность, но скорее равенство (если возможно и реализовано, конечно).И в случае встроенного строкового класса это, конечно, не так.

Может быть и другая проблема с вашей реализацией - поскольку dump выдает очень точную информацию (подходящую для отладки), два числа снапример, переменная, названная по-другому, может считаться !=.Это может или не может быть то, что вы хотите.

...