我的同事建议我写一个访客模式来导航AST。任何人都可以告诉我更多我将如何开始写它?
据我了解,AST中的每个节点都会有visit()
方法(?)以某种方式被调用(从哪里?)。那就是我的理解。
为了简化一切,假设我有节点Root
,Expression
,Number
,Op
,树看起来像这样:
Root
|
Op(+)
/ \
/ \
Number(5) \
Op(*)
/ \
/ \
/ \
Number(2) Number(444)
任何人都可以想到访问者模式将如何访问此树以产生输出:
5 + 2 * 444
谢谢,Boda Cydo。
答案 0 :(得分:10)
ast.NodeVisitor
见the docs,例如粗略的可能性可能是:
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,
当然,即使在需要的地方也不会发出括号等等,所以实际上还有更多的工作要做,但是,这是一个开始; - )。
答案 1 :(得分:10)
Wikipedia对how the Visitor pattern works有一个很好的概述,尽管他们使用的示例实现是Java。你可以轻松地将它移植到Python,不,不是吗?
基本上,您希望为double dispatch实施机制。 AST中的每个节点都需要实现accept()
方法(不是visit()
方法)。该方法采用访问者对象作为参数。在这个accept()
方法的实现中,你调用访问者对象的visit()
方法(每个AST节点类型都有一个;在Java中,你将使用参数重载,在Python中我想您可以使用不同的visit_*()
方法)。然后将使用正确的节点类型作为参数调度正确的访问者。
答案 2 :(得分:3)
我最常在Internet上遇到的两种用于实现Python访问者模式的变体:
此变体在数据结构类中使用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之前的v3.4 multimethods模块可以使用类似于singledispatch装饰器。多方法模块的一个缺点是,应用于给定数据结构元素的访问者方法不仅基于元素的类型而且基于声明方法的顺序来选择。对于具有复杂继承层次结构的数据结构,保持方法定义的顺序可能很麻烦且容易出错。