8.22 不用递归实现访问者模式
问题
你使用访问者模式遍历一个很深的嵌套树形数据结构,并且因为超过嵌套层级限制而失败。 你想消除递归,并同时保持访问者编程模式。
解决方案
通过巧妙的使用生成器可以在树遍历或搜索算法中消除递归。 下面我们利用一个栈和生成器重新实现这个类:
import types
class Node:
pass
class NodeVisitor:
def visit(self, node):
stack = [node]
last_result = None
while stack:
try:
last = stack[-1]
if isinstance(last, types.GeneratorType):
stack.append(last.send(last_result))
last_result = None
elif isinstance(last, Node):
stack.append(self._visit(stack.pop()))
else:
last_result = stack.pop()
except StopIteration:
stack.pop()
return last_result
def _visit(self, node):
methname = 'visit_' + type(node).__name__
meth = getattr(self, methname, None)
if meth is None:
meth = self.generic_visit
return meth(node)
def generic_visit(self, node):
raise RuntimeError('No {} method'.format('visit_' + type(node).__name__))如果你使用这个类,也能达到相同的效果。事实上你完全可以将它作为上一节中的访问者模式的替代实现。 考虑如下代码,遍历一个表达式的树:
如果嵌套层次太深那么上述的Evaluator就会失效:
现在我们稍微修改下上面的Evaluator:
再次运行,就不会报错了:
讨论
避免递归的一个通常方法是使用一个栈或队列的数据结构。 例如,深度优先的遍历算法,第一次碰到一个节点时将其压入栈中,处理完后弹出栈。visit() 方法的核心思路就是这样。
另外一个需要理解的就是生成器中yield语句。当碰到yield语句时,生成器会返回一个数据并暂时挂起。 上面的例子使用这个技术来代替了递归。
之前我们写的是递归,现在换成yield语句:
它会将 node.left 返回给 visit() 方法,然后 visit() 方法调用那个节点相应的 visit_Name() 方法。 yield暂时将程序控制器让出给调用者,当执行完后,结果会赋值给value。
Last updated
Was this helpful?