Skip to content

Commit bb8eef2

Browse files
committed
8.22小节完成
1 parent 36e92dc commit bb8eef2

File tree

2 files changed

+321
-3
lines changed

2 files changed

+321
-3
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env python
2+
# -*- encoding: utf-8 -*-
3+
"""
4+
Topic: 非递归的观察者模式
5+
Desc :
6+
"""
7+
8+
import types
9+
10+
11+
class Node:
12+
pass
13+
14+
15+
class NodeVisitor:
16+
def visit(self, node):
17+
stack = [node]
18+
last_result = None
19+
while stack:
20+
try:
21+
last = stack[-1]
22+
if isinstance(last, types.GeneratorType):
23+
stack.append(last.send(last_result))
24+
last_result = None
25+
elif isinstance(last, Node):
26+
stack.append(self._visit(stack.pop()))
27+
else:
28+
last_result = stack.pop()
29+
except StopIteration:
30+
stack.pop()
31+
32+
return last_result
33+
34+
def _visit(self, node):
35+
methname = 'visit_' + type(node).__name__
36+
meth = getattr(self, methname, None)
37+
if meth is None:
38+
meth = self.generic_visit
39+
return meth(node)
40+
41+
def generic_visit(self, node):
42+
raise RuntimeError('No {} method'.format('visit_' + type(node).__name__))
43+
44+
45+
class UnaryOperator(Node):
46+
def __init__(self, operand):
47+
self.operand = operand
48+
49+
50+
class BinaryOperator(Node):
51+
def __init__(self, left, right):
52+
self.left = left
53+
self.right = right
54+
55+
56+
class Add(BinaryOperator):
57+
pass
58+
59+
60+
class Sub(BinaryOperator):
61+
pass
62+
63+
64+
class Mul(BinaryOperator):
65+
pass
66+
67+
68+
class Div(BinaryOperator):
69+
pass
70+
71+
72+
class Negate(UnaryOperator):
73+
pass
74+
75+
76+
class Number(Node):
77+
def __init__(self, value):
78+
self.value = value
79+
80+
81+
# A sample visitor class that evaluates expressions
82+
class Evaluator(NodeVisitor):
83+
def visit_Number(self, node):
84+
return node.value
85+
86+
def visit_Add(self, node):
87+
yield (yield node.left) + (yield node.right)
88+
89+
def visit_Sub(self, node):
90+
yield (yield node.left) - (yield node.right)
91+
92+
def visit_Mul(self, node):
93+
yield (yield node.left) * (yield node.right)
94+
95+
def visit_Div(self, node):
96+
yield (yield node.left) / (yield node.right)
97+
98+
def visit_Negate(self, node):
99+
yield - (yield node.operand)
100+
101+
102+
if __name__ == '__main__':
103+
# 1 + 2*(3-4) / 5
104+
t1 = Sub(Number(3), Number(4))
105+
t2 = Mul(Number(2), t1)
106+
t3 = Div(t2, Number(5))
107+
t4 = Add(Number(1), t3)
108+
# Evaluate it
109+
e = Evaluator()
110+
print(e.visit(t4)) # Outputs 0.6

source/c08/p22_implementing_visitor_pattern_without_recursion.rst

Lines changed: 211 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,222 @@
55
----------
66
问题
77
----------
8-
todo...
8+
你使用观察者模式遍历一个很深的嵌套树形数据结构,并且因为超过嵌套层级限制而失败。
9+
你想消除递归,并同时保持观察者编程模式。
10+
11+
|
912
1013
----------
1114
解决方案
1215
----------
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+
>>>
14200
15201
----------
16202
讨论
17203
----------
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

Comments
 (0)