35

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

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

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

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

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

 5 + 2 * 444

谢谢,博达赛多。

4

3 回答 3

18

Wikipedia 很好地概述了访问者模式的工作原理,尽管他们使用的示例实现是用 Java 编写的。不过,您可以轻松地将其移植到 Python,不是吗?

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

于 2010-03-26T20:09:28.120 回答
15

请参阅文档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,

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

于 2010-03-26T18:32:54.440 回答
14

我在网上最常遇到的用 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装饰器不能直接与实例方法一起使用,尽管有一些方法可以使它工作注意:从 Python 3.8 开始functools.singledispatchmethod()提供与实例方法相同的功能functools.singledispatch()

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

于 2016-11-07T21:58:34.523 回答