当我们需要编写代码来处理或者遍历一个由许多不同类型的对象组成的复杂数据结构,每种类型的对象处理的方式都不相同。例如遍历一个树结构,根据遇到的树节点的类型来执行不同的操作。
为了展示不同的类的处理,首先定义一些需要用到的类:
class Node:
pass
class UnaryOperator(Node):
def __inif__(self, operand):
self.operand = operand
class BinaryOperator(Node):
def __init__(self, left, right):
self.left = left
self.right = right
class Number(Node):
def __init__(self, value):
self.value = value
class Add(BinaryOperator):
pass
class Sub(BinaryOperator):
pass
class Mul(BinaryOperator):
pass
class Div(BinaryOperator):
pass
class Negate(UnaryOperator):
pass
之后我们可以如下使用这些类:
>>> #表示 1 + 2 * (3 - 4) / 5
>>> t1 = Sub(Number(3), Number(4))
>>> t2 = Mul(Number(2), t1)
>>> t3 = Div(t2, Number(5))
>>> t4 = Add(Number(1), t3)
但是问题并不在于如何创建这些数据结构上,而在编写处理它们的代码上。在访问中,想根据不同的数据类型来调度不同的处理办法,一种幼稚的办法是编写大量的if语句,但是这样的方法往往是不可行的。除了非常繁琐,运行速度慢之外,如果想要添加或者修改要处理的节点类型则会难以维护。
为此,我们可以选择构造访问者类,实现“访问者模式”,使用一些小技巧将方法名构建出来,再利用getattr()函数来获取方法会好得多。
如下构造节点访问者类:
class NodeVistor:
def vist(self, node):
methodname = 'vist_' + type(node).__name__
method = getattr(self, methodname, None)#未找到方法则返回None
if method is None:
raise RuntimeError('No {} method'.format('visit_'+type(node).__name__))
return method(node)
这个节点访问者类作为基类,它提供了访问者类的核心方法vist()的定义。并且使用methodname获取应该要调用的子类的方法,使用getattr获取应该调用的方法。如果该方法存在,则使用method进行调用,如果不存在,则抛出错误。
如下构建访问者类:
class Vistor(NodeVistor):
def vist_Number(self, node):
return node.value
def vist_Add(self, node):
return self.vist(node.left) + self.vist(node.right)
def vist_Sub(self, node):
return self.vist(node.left) - self.vist(node.right)
def vist_Mul(self, node):
return self.vist(node.left) * self.vist(node.right)
def vist_Div(self, node):
return self.vist(node.left) / self.vist(node.right)
def vist_Negate(self, node):
return -node.operand
这个访问者类使用了对vist()方法进行递归的方式来完成计算。正是因为递归才使得访问者类可以遍历整个数据结构,最终直到比如Number类结束。
以下是访问者类的使用示例:
>>> v = Vistor()
>>> v.vist(t4)
0.6
>>> v.vist(t3)
-0.4
>>> v.vist(t2)
-2
>>> v.vist(t1)
-1
访问者模式的一个缺点就是需要重度依赖于递归。如果要处理一个深度嵌套的数据结构,那么有可能会达到Python的递归深度限制(可以使用sys.getrecursionlimit()查看)。要避免这个问题,可以在构建数据结构时做一些特定的选择。比如使用普通的python列表来替代链表,或者在每一个节点中聚合更多的数据,使得数据更加扁平化而不是深度嵌套。