Предположим, что я хочу написать крошечный интерпретатор, который может вычислять выражения с бинарной операцией Plus
, унарной операцией Negate
и целочисленными константами.
В настоящее время меня интересует только интерпретация AST, поэтому для простоты пропустим токенизацию и синтаксический анализ.
В Haskell есть более-менее канонический способ сделать это:
data Ast = Plus Ast Ast | Negate Ast | IntConst Int
ev :: Ast -> Int
ev (Plus a b) = (ev a) + (ev b)
ev (Negate x) = - (ev x)
ev (IntConst i) = i
main = print $ show $ ev $ (Plus (IntConst 50) (Negate $ IntConst 8))
Теперь в Python 3.6, похоже, нет алгебраических типов данных. Моя проблема в том, что, кажется, есть много возможных обходных путей. Наиболее очевидным является использование isinstance
:
class Plus:
def __init__(self, first, second):
self.first = first
self.second = second
class Negate:
def __init__(self, first):
self.first = first
class IntConst:
def __init__(self, value):
self.value = value
def ev(ast):
if isinstance(ast, Plus):
return ev(ast.first) + ev(ast.second)
elif isinstance(ast, Negate):
return - ev(ast.first)
elif isinstance(ast, IntConst):
return ast.value
print(ev(Plus(IntConst(50), Negate(IntConst(8)))))
Это работает и печатает 42
, как и ожидалось, но выглядит несколько шумно.
Вот еще несколько вариантов, но у каждого есть свои недостатки:
- Используйте исправление обезьяны: например. этот пример определяет группу
???_execute
методов в интерпретаторе, а затем прикрепляет их к классам, которые представляют элементы АСТ. Мне это кажется очень страшным (я не хочу знать, что произойдет, если я попытаюсь выполнить два отдельных AST с двумя разными интерпретаторами параллельно: все сломается, верно?). - Определите универсальный
NodeVisitor
, который имеетvisit_???
-метод для каждого типа AST-узла, а затем выполняет некоторые диспетчеризация склейка правильного имени метода из строк и имени класса экземпляра, переданного вvisit
-метод. Это кажется несколько более надежным, но мне не нравится, что имена методов постоянно перестраиваются: интерпретатор должен сосредоточиться на AST, а не на создании собственного исходного кода (имен методов). - Используйте дополнительные макроприспособления, которые очевидно, могут генерировать case-классы. В настоящее время я не хочу использовать какие-либо сторонние инструменты, я хочу иметь крошечный скрипт, максимально независимый от всего остального.
Я не нашел ответа на этот связанный вопрос Удовлетворительно, потому что единственный ответ — это просто ссылки на какой-то внешний инструмент, который, к тому же, больше не поддерживается.
Итак, есть ли в Python 3.6.x какой-то стандартный способ определения интерпретаторов для AST, которые не имеют вышеупомянутых недостатков? Или мне просто придерживаться isinstance
? Или реализовать старый добрый Java-стиль Visitor
(не уверен, считается ли он питоническим)?
ИЗМЕНИТЬ
Используя functools
, предложенный @juanpa.arrivillaga, я придумал следующее:
Используйте
collections.namedtuple
иfunctools.singledispatch
:from collections import namedtuple from functools import singledispatch Plus = namedtuple('Plus', ['left', 'right']) Negate = namedtuple('Negate', ['arg']) IntConst = namedtuple('IntConst', ['value']) @singledispatch def ev(x): raise NotImplementedError("not exhaustive: %r" % (type(x))) ev.register(Plus, lambda p: ev(p.left) + ev(p.right)) ev.register(Negate, lambda n: -ev(n.arg)) ev.register(IntConst, lambda c: c.value) print(ev(Plus(IntConst(50), Negate(IntConst(8)))))
Однако кажется, что это не работает, если
ev
является методом, потому что он не может выполнять отправку по аргументуself
(см. связанный с этим вопрос), поэтому я могу получить только функциюev
, но не экземпляр, представляющий интерпретатор.
def visit(self, node): method_name = 'visit_' + type(node).__name__ [...]
часть. Если я правильно понимаю, он создаетmethod_name
каждый раз, когда методvisit
вызывается дляNodeVisitor
. Это, в свою очередь, означает, что простой вычислитель арифметических выражений потратит на манипуляции со строками на порядок больше времени, чем на фактические вычисления. - person Andrey Tyukin   schedule 29.03.2018