Перевод АСЦ в Python 3.6: isinstance и обезьяна-ямочный и перейти типа и макросы?
Предположим, что я хочу написать крошечный интерпретатор.
который может вычислять выражения с помощью двоичной операции
Plus
, унарная операция Negate
и целочисленные константы.
В настоящее время меня интересует только интерпретация АСТ, поэтому давайте для простоты пропустим токенизацию и синтаксический анализ.
У Хаскелла есть более или менее канонический способ сделать это: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
, как и ожидалось, но выглядит несколько шумно.
- используйте monkey-patching: например этот пример
определяет связку
???_execute
методы в интерпретаторе, а затем присоединяет их к классам, которые представляют собой элементы АСТ. Это выглядит очень страшно для меня (я не хочу знать, что произойдет, если я попытаюсь выполнить два разделите ASTs с двумя разными интерпретаторами параллельно: все будет перерыв, да?). - определите общий
NodeVisitor
это имеетvisit_???
- метод для каждого типа AST-узла, а затем выполняет некоторую диспетчеризацию склеивание правильного имени метода из строк и имени класса экземпляра, переданного вvisit
-метод. Это кажется несколько более надежным, но мне не нравится что имена методов перестраиваются постоянно: интерпретатор должен сосредоточиться на AST, не на генерацию собственный исходный код (имена методов). - используйте некоторые дополнительные макро-Гизмо, которые , по-видимому, могут генерировать case-классы. Я в настоящее время не хочу использовать никакие сторонние инструменты, я хочу иметь крошечный маленький сценарий, который настолько независим от всего остального, насколько это возможно.
Я не нашел ответа на этот связанный с этим вопрос удовлетворительно, потому что единственный ответ просто ссылки на какой-то внешний инструмент, который, кроме того, больше не поддерживается.
Итак, есть есть какой-то стандартный способ в Python 3.6.x определить интерпретаторы для ASTs, которые
не имеют ли вышеперечисленные недостатки? Или я должен просто придерживаться isinstance
? Или выполнять привычные Java-стиль Visitor
(не уверен, можно ли это считать обновления)?
EDIT
С использованием 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
, но не экземпляр, представляющий интерпертер.
1 ответ:
Если чистый код-это то, что вы ищете, я думаю, что декоратор
functools.singledispatch
будет работать в этом случае:import functools 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 @functools.singledispatch def ev(ast): raise NotImplementedError('Unsupported type') @ev.register(Plus) def _(ast): return ev(ast.first) + ev(ast.second) @ev.register(Negate) def _(ast): return -ev(ast.first) @ev.register(IntConst) def _(ast): return ast.value print(ev(Plus(IntConst(50), Negate(IntConst(8)))))