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?