|
5 | 5 | ----------
|
6 | 6 | 问题
|
7 | 7 | ----------
|
8 |
| -todo... |
| 8 | +你使用观察者模式遍历一个很深的嵌套树形数据结构,并且因为超过嵌套层级限制而失败。 |
| 9 | +你想消除递归,并同时保持观察者编程模式。 |
| 10 | + |
| 11 | +| |
9 | 12 |
|
10 | 13 | ----------
|
11 | 14 | 解决方案
|
12 | 15 | ----------
|
13 |
| -todo... |
| 16 | +通过巧妙的使用生成器可以在树遍历或搜索算法中消除递归。 |
| 17 | +在8.21小节中,我们给出了一个观察者类。 |
| 18 | +下面我们利用一个栈和生成器重新实现这个类: |
| 19 | + |
| 20 | +.. code-block:: python |
| 21 | +
|
| 22 | + import types |
| 23 | +
|
| 24 | + class Node: |
| 25 | + pass |
| 26 | +
|
| 27 | + class NodeVisitor: |
| 28 | + def visit(self, node): |
| 29 | + stack = [node] |
| 30 | + last_result = None |
| 31 | + while stack: |
| 32 | + try: |
| 33 | + last = stack[-1] |
| 34 | + if isinstance(last, types.GeneratorType): |
| 35 | + stack.append(last.send(last_result)) |
| 36 | + last_result = None |
| 37 | + elif isinstance(last, Node): |
| 38 | + stack.append(self._visit(stack.pop())) |
| 39 | + else: |
| 40 | + last_result = stack.pop() |
| 41 | + except StopIteration: |
| 42 | + stack.pop() |
| 43 | +
|
| 44 | + return last_result |
| 45 | +
|
| 46 | + def _visit(self, node): |
| 47 | + methname = 'visit_' + type(node).__name__ |
| 48 | + meth = getattr(self, methname, None) |
| 49 | + if meth is None: |
| 50 | + meth = self.generic_visit |
| 51 | + return meth(node) |
| 52 | +
|
| 53 | + def generic_visit(self, node): |
| 54 | + raise RuntimeError('No {} method'.format('visit_' + type(node).__name__)) |
| 55 | +
|
| 56 | +如果你使用这个类,也能达到相同的效果。事实上你完全可以将它作为上一节中的观察者模式的替代实现。 |
| 57 | +考虑如下代码,遍历一个表达式的树: |
| 58 | + |
| 59 | +.. code-block:: python |
| 60 | +
|
| 61 | + class UnaryOperator(Node): |
| 62 | + def __init__(self, operand): |
| 63 | + self.operand = operand |
| 64 | +
|
| 65 | + class BinaryOperator(Node): |
| 66 | + def __init__(self, left, right): |
| 67 | + self.left = left |
| 68 | + self.right = right |
| 69 | +
|
| 70 | + class Add(BinaryOperator): |
| 71 | + pass |
| 72 | +
|
| 73 | + class Sub(BinaryOperator): |
| 74 | + pass |
| 75 | +
|
| 76 | + class Mul(BinaryOperator): |
| 77 | + pass |
| 78 | +
|
| 79 | + class Div(BinaryOperator): |
| 80 | + pass |
| 81 | +
|
| 82 | + class Negate(UnaryOperator): |
| 83 | + pass |
| 84 | +
|
| 85 | + class Number(Node): |
| 86 | + def __init__(self, value): |
| 87 | + self.value = value |
| 88 | +
|
| 89 | + # A sample visitor class that evaluates expressions |
| 90 | + class Evaluator(NodeVisitor): |
| 91 | + def visit_Number(self, node): |
| 92 | + return node.value |
| 93 | +
|
| 94 | + def visit_Add(self, node): |
| 95 | + return self.visit(node.left) + self.visit(node.right) |
| 96 | +
|
| 97 | + def visit_Sub(self, node): |
| 98 | + return self.visit(node.left) - self.visit(node.right) |
| 99 | +
|
| 100 | + def visit_Mul(self, node): |
| 101 | + return self.visit(node.left) * self.visit(node.right) |
| 102 | +
|
| 103 | + def visit_Div(self, node): |
| 104 | + return self.visit(node.left) / self.visit(node.right) |
| 105 | +
|
| 106 | + def visit_Negate(self, node): |
| 107 | + return -self.visit(node.operand) |
| 108 | +
|
| 109 | + if __name__ == '__main__': |
| 110 | + # 1 + 2*(3-4) / 5 |
| 111 | + t1 = Sub(Number(3), Number(4)) |
| 112 | + t2 = Mul(Number(2), t1) |
| 113 | + t3 = Div(t2, Number(5)) |
| 114 | + t4 = Add(Number(1), t3) |
| 115 | + # Evaluate it |
| 116 | + e = Evaluator() |
| 117 | + print(e.visit(t4)) # Outputs 0.6 |
| 118 | +
|
| 119 | +如果嵌套层次太深那么上述的Evaluator就会失效: |
| 120 | + |
| 121 | +.. code-block:: python |
| 122 | +
|
| 123 | + >>> a = Number(0) |
| 124 | + >>> for n in range(1, 100000): |
| 125 | + ... a = Add(a, Number(n)) |
| 126 | + ... |
| 127 | + >>> e = Evaluator() |
| 128 | + >>> e.visit(a) |
| 129 | + Traceback (most recent call last): |
| 130 | + ... |
| 131 | + File "visitor.py", line 29, in _visit |
| 132 | + return meth(node) |
| 133 | + File "visitor.py", line 67, in visit_Add |
| 134 | + return self.visit(node.left) + self.visit(node.right) |
| 135 | + RuntimeError: maximum recursion depth exceeded |
| 136 | + >>> |
| 137 | +
|
| 138 | +现在我们稍微修改下上面的Evaluator: |
| 139 | + |
| 140 | +.. code-block:: python |
| 141 | +
|
| 142 | + class Evaluator(NodeVisitor): |
| 143 | + def visit_Number(self, node): |
| 144 | + return node.value |
| 145 | +
|
| 146 | + def visit_Add(self, node): |
| 147 | + yield (yield node.left) + (yield node.right) |
| 148 | +
|
| 149 | + def visit_Sub(self, node): |
| 150 | + yield (yield node.left) - (yield node.right) |
| 151 | +
|
| 152 | + def visit_Mul(self, node): |
| 153 | + yield (yield node.left) * (yield node.right) |
| 154 | +
|
| 155 | + def visit_Div(self, node): |
| 156 | + yield (yield node.left) / (yield node.right) |
| 157 | +
|
| 158 | + def visit_Negate(self, node): |
| 159 | + yield - (yield node.operand) |
| 160 | +
|
| 161 | +再次运行,就不会报错了: |
| 162 | + |
| 163 | +.. code-block:: python |
| 164 | +
|
| 165 | + >>> a = Number(0) |
| 166 | + >>> for n in range(1,100000): |
| 167 | + ... a = Add(a, Number(n)) |
| 168 | + ... |
| 169 | + >>> e = Evaluator() |
| 170 | + >>> e.visit(a) |
| 171 | + 4999950000 |
| 172 | + >>> |
| 173 | +
|
| 174 | +如果你还想添加其他自定义逻辑也没问题: |
| 175 | + |
| 176 | +.. code-block:: python |
| 177 | +
|
| 178 | + class Evaluator(NodeVisitor): |
| 179 | + ... |
| 180 | + def visit_Add(self, node): |
| 181 | + print('Add:', node) |
| 182 | + lhs = yield node.left |
| 183 | + print('left=', lhs) |
| 184 | + rhs = yield node.right |
| 185 | + print('right=', rhs) |
| 186 | + yield lhs + rhs |
| 187 | + ... |
| 188 | +
|
| 189 | +下面是简单的测试: |
| 190 | + |
| 191 | +.. code-block:: python |
| 192 | +
|
| 193 | + >>> e = Evaluator() |
| 194 | + >>> e.visit(t4) |
| 195 | + Add: <__main__.Add object at 0x1006a8d90> |
| 196 | + left= 1 |
| 197 | + right= -0.4 |
| 198 | + 0.6 |
| 199 | + >>> |
14 | 200 |
|
15 | 201 | ----------
|
16 | 202 | 讨论
|
17 | 203 | ----------
|
18 |
| -todo... |
| 204 | +这一小节我们演示了生成器和协程在程序控制流方面的强大功能。 |
| 205 | +避免递归的一个通常方法是使用一个栈或队列的数据结构。 |
| 206 | +例如,深度优先的遍历算法,第一次碰到一个节点时将其压入栈中,处理完后弹出栈。``visit()`` 方法的核心思路就是这样。 |
| 207 | + |
| 208 | +另外一个需要理解的就是生成器中yield语句。当碰到yield语句时,生成器会返回一个数据并暂时挂起。 |
| 209 | +上面的例子使用这个技术来代替了递归。例如,之前我们是这样写递归: |
| 210 | + |
| 211 | +.. code-block:: python |
| 212 | +
|
| 213 | + value = self.visit(node.left) |
| 214 | +
|
| 215 | +现在换成yield语句: |
| 216 | + |
| 217 | +.. code-block:: python |
| 218 | +
|
| 219 | + value = yield node.left |
| 220 | +
|
| 221 | +它会将 ``node.left`` 返回给 ``visti()`` 方法,然后 ``visti()`` 方法调用那个节点相应的 ``vist_Name()`` 方法。 |
| 222 | +yield暂时将程序控制器让出给调用者,当执行完后,结果会赋值给value, |
| 223 | + |
| 224 | +看完这一小节,你也许想去寻找其它没有yield语句的方案。但是这么做没有必要,你必须处理很多棘手的问题。 |
| 225 | +例如,为了消除递归,你必须要维护一个栈结构,如果不使用生成器,代码会变得很臃肿,到处都是栈操作语句、回调函数等。 |
| 226 | +实际上,使用yield语句可以让你写出非常漂亮的代码,它消除了递归但是看上去又很像递归实现,代码很简洁。 |
0 commit comments