And*_*kin 5 python monkeypatching visitor-pattern algebraic-data-types python-3.x
假设我想编写一个小型解释器,可以使用二元运算
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))
Run Code Online (Sandbox Code Playgroud)
现在,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)))))
Run Code Online (Sandbox Code Playgroud)
42这可以按预期工作和打印,但看起来有点吵。
这里还有一些选项,但每个选项都有一些缺点:
???_execute
方法,然后将它们附加到表示 AST 元素的类。这对我来说看起来非常可怕(我不想知道如果我尝试使用两个不同的解释器并行执行两个单独的 AST 会发生什么:一切都会崩溃,对吧?)。NodeVisitor
它visit_???为每种类型的 AST 节点都有一个 -method,然后进行一些调度,将字符串中的正确方法名称和传递给 -method 的实例类名称粘合起来visit。这看起来更健壮,但我不喜欢永久重建方法名称:解释器应该专注于 AST,而不是生成自己的源代码(方法名称)。我没有找到令人满意的相关问题的答案 ,因为唯一的答案只是链接到一些外部工具,而且不再维护。
那么,Python 3.6.x 中是否有一些标准方法可以为没有上述缺点的 AST 定义解释器?还是我应该坚持下去isinstance?或者实现良好的旧 Java 风格Visitor(不确定它是否被认为是 pythonic)?
编辑
使用functools@juanpa.arrivilillaga的建议,我想出了以下内容:
使用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)))))
Run Code Online (Sandbox Code Playgroud)
但是,如果is a method ,它似乎不起作用ev,因为它无法在 -argument 上分派self(请参阅此相关问题),所以我只能得到一个 function ev,但没有代表解释器的实例。
如果您正在寻找更清晰的代码,我认为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)))))
Run Code Online (Sandbox Code Playgroud)