Как написать шаблон посетителя для абстрактного синтаксического дерева в Python?
мой коллега предложил мне написать шаблон посетителя для навигации по AST. Может ли кто-нибудь сказать мне больше, как я начну писать?
насколько я понимаю, каждый узел в AST будет иметь visit()
способ (?) это как-то называется (откуда?). На этом мое понимание заканчивается.
чтобы упростить все, предположим, что у меня есть узлы Root
, Expression
, Number
, Op
и дерево выглядит так:
Root
|
Op(+)
/
/
Number(5)
Op(*)
/
/
/
Number(2) Number(444)
может кто-нибудь подумать о том, как шаблон посетителя посетит это дерево, чтобы произвести вывод:
5 + 2 * 444
Спасибо, Бодя Cydo.
3 ответов
Википедия имеет отличный обзор как работает шаблон посетителя, хотя пример реализации, который они используют в Java. Вы можете легко перенести это на Python, хотя, нет?
в принципе, вы хотите реализовать механизм двойной диспетчеризации. Каждый узел в вашем AST должен будет реализовать accept()
метод (не visit()
метод). Метод принимает в качестве аргумента объект посетителя. В реализации этого accept()
метод, вы называете а visit()
метод объекта посетителя (будет один для каждого типа узла AST; в Java вы будете использовать перегрузку параметров, в Python, я полагаю, вы можете использовать разные visit_*()
методов). Правильный посетитель будет отправлен с правильным типом узла в качестве аргумента.
посмотреть документы на ast.NodeVisitor
, например, грубая возможность может быть:
import ast
class MyVisitor(ast.NodeVisitor):
def visit_BinaryOp(self, node):
self.visit(node.left)
print node.op,
self.visit(node.right)
def visit_Num(self, node):
print node.n,
конечно, это не выделяет скобки даже там, где это необходимо, и т. д., Поэтому на самом деле больше работы сделано, но это начало;-).
два варианта реализации шаблона посетителя в Python, с которыми я столкнулся в Интернете чаще всего:
- один к одному перевод примера из книги Desigh Patterns by Gamma et al.
- использование дополнительных модулей для двойной отправки
переведенный пример из книги шаблонов Desigh
этот вариант использует accept()
методы в классах структуры данных и соответствующие visit_Type()
методы в посетители.
структуры данных
class Operation(object):
def __init__(self, op, arg1, arg2):
self.op = op
self.arg1 = arg1
self.arg2 = arg2
def accept(self, visitor):
visitor.visitOperation(self)
class Integer(object):
def __init__(self, num):
self.num = num
def accept(self, visitor):
visitor.visitInteger(self)
class Float(object):
def __init__(self, num):
self.num = num
def accept(self, visitor):
visitor.visitFloat(self)
expression = Operation('+', Integer('5'),
Operation('*', Integer('2'), Float('444.1')))
Infix Печать Посетитель
class InfixPrintVisitor(object):
def __init__(self):
self.expression_string = ''
def visitOperation(self, operation):
operation.arg1.accept(self)
self.expression_string += ' ' + operation.op + ' '
operation.arg2.accept(self)
def visitInteger(self, number):
self.expression_string += number.num
def visitFloat(self, number):
self.expression_string += number.num
Префикс Печати Посетитель
class PrefixPrintVisitor(object):
def __init__(self):
self.expression_string = ''
def visitOperation(self, operation):
self.expression_string += operation.op + ' '
operation.arg1.accept(self)
self.expression_string += ' '
operation.arg2.accept(self)
def visitInteger(self, number):
self.expression_string += number.num
def visitFloat(self, number):
self.expression_string += number.num
тест
infixPrintVisitor = InfixPrintVisitor()
expression.accept(infixPrintVisitor)
print(infixPrintVisitor.expression_string)
prefixPrintVisitor = PrefixPrintVisitor()
expression.accept(prefixPrintVisitor)
print(prefixPrintVisitor.expression_string)
выход
5 + 2 * 444.1
+ 5 * 2 444.1
использование дополнительных модулей
этот вариант использует @functools.singledispatch()
декоратор (доступен в стандартной библиотеке Python начиная с Python В3.4).
структуры данных
class Operation(object):
def __init__(self, op, arg1, arg2):
self.op = op
self.arg1 = arg1
self.arg2 = arg2
class Integer(object):
def __init__(self, num):
self.num = num
class Float(object):
def __init__(self, num):
self.num = num
expression = Operation('+', Integer('5'),
Operation('*', Integer('2'), Float('444.1')))
Infix Печать Посетитель
from functools import singledispatch
@singledispatch
def visitor_print_infix(obj):
pass
@visitor_print_infix.register(Operation)
def __(operation):
return visitor_print_infix(operation.arg1) + ' ' \
+ operation.op + ' ' \
+ visitor_print_infix(operation.arg2)
@visitor_print_infix.register(Integer)
@visitor_print_infix.register(Float)
def __(number):
return number.num
Префикс Печати Посетитель
from functools import singledispatch
@singledispatch
def visitor_print_prefix(obj):
pass
@visitor_print_prefix.register(Operation)
def __(operation):
return operation.op + ' ' \
+ visitor_print_prefix(operation.arg1) + ' ' \
+ visitor_print_prefix(operation.arg2)
@visitor_print_prefix.register(Integer)
@visitor_print_prefix.register(Float)
def __(number):
return number.num
тест
print(visitor_print_infix(expression))
print(visitor_print_prefix(expression))
выход
5 + 2 * 444.1
+ 5 * 2 444.1
причина, по которой я предпочитаю этот вариант заключается в том, что он исключает accept()
методы и полностью отделяет структуру данных от операций, реализованных в посетители. Расширение структуры данных новыми элементами не требует изменения посетителей. Посетители по умолчанию игнорируют неизвестные типы элементов (см. определения с помощью pass
ключевое слово). Недостатком этого метода является то, что singledispatch
декоратор не может использоваться с методами экземпляра напрямую, хотя, есть способы заставить его работать.
для Python перед v3.4 мультиметоды модуль может использоваться аналогично декоратору singledispatch. Одним из недостатков модуля multimethods является то, что метод посетителя, применяемый к заданному элементу структуры данных, выбирается не только на основе типа элемента, но и порядка, в котором объявляются методы. Сохранение определений методов в правильном порядке может быть громоздким и подверженным ошибкам для структур данных со сложной иерархией наследования.