【问题标题】:How to write the Visitor Pattern for Abstract Syntax Tree in Python?如何用 Python 编写抽象语法树的访问者模式?
【发布时间】:2010-03-26 18:14:07
【问题描述】:

我的同事建议我编写一个访问者模式来导航 AST。谁能告诉我更多我将如何开始写它?

据我了解,AST 中的每个节点都有visit() 方法(?),它会以某种方式被调用(从哪里?)。我的理解到此结束。

为了简化一切,假设我有节点 RootExpressionNumberOp,树看起来像这样:

       Root
        |
       Op(+)
      /   \
     /     \
 Number(5)  \
             Op(*)
             /   \
            /     \
           /       \
       Number(2)   Number(444)

谁能想到访问者模式将如何访问这棵树以产生输出:

 5 + 2 * 444

谢谢,博达·赛多。

【问题讨论】:

    标签: python parsing compiler-construction abstract-syntax-tree visitor-pattern


    【解决方案1】:

    维基百科对how the Visitor pattern works 有一个很好的概述,尽管他们使用的示例实现是用Java 编写的。不过,您可以轻松地将其移植到 Python 中,不是吗?

    基本上,您希望为double dispatch 实现一种机制。 AST 中的每个节点都需要实现 accept() 方法(不是 visit() 方法)。该方法将访问者对象作为参数。在此accept() 方法的实现中,您调用访问者对象的visit() 方法(每种AST 节点类型都有一个;在Java 中,您将使用参数重载,在Python 中我想您可以使用不同的visit_*() 方法)。然后将使用正确的节点类型作为参数分派正确的访问者。

    【讨论】:

      【解决方案2】:

      请参阅 the docs 以获取 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,
      

      当然,即使在需要的地方,这也不会发出括号等,因此实际上还有更多工作要做,但是,这是一个开始;-)。

      【讨论】:

        【解决方案3】:

        我在网上最常遇到的用Python实现访问者模式的两种变体:

        • 一对一翻译 Gamma 等人的 Design Patterns 书中的示例。
        • 使用附加模块进行双重调度

        设计模式书的翻译示例

        此变体在数据结构类中使用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')))
        

        中缀印刷访问者

        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 v3.4 起可在 Python 标准库中使用)。

        数据结构

        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')))
        

        中缀印刷访问者

        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 装饰器不能直接与实例方法一起使用,尽管there are ways to make it work注意:从 Python 3.8 开始,functools.singledispatchmethod() 提供与 functools.singledispatch() 相同的功能,但用于实例方法。

        对于 v3.4 之前的 Python,multimethods 模块可以类似于 singledispatch 装饰器使用。 multimethods 模块的一个缺点是,应用于给定数据结构元素的访问者方法的选择不仅基于元素的类型,而且还基于声明方法的顺序。对于具有复杂继承层次结构的数据结构,将方法定义保持在正确的顺序可能很麻烦且容易出错。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-10-23
          相关资源
          最近更新 更多