W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
你使用訪問者模式遍歷一個很深的嵌套樹形數(shù)據(jù)結(jié)構(gòu),并且因為超過嵌套層級限制而失敗。你想消除遞歸,并同時保持訪問者編程模式。
通過巧妙的使用生成器可以在樹遍歷或搜索算法中消除遞歸。在8.21小節(jié)中,我們給出了一個訪問者類。下面我們利用一個棧和生成器重新實現(xiàn)這個類:
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__))
如果你使用這個類,也能達(dá)到相同的效果。事實上你完全可以將它作為上一節(jié)中的訪問者模式的替代實現(xiàn)??紤]如下代碼,遍歷一個表達(dá)式的樹:
class UnaryOperator(Node):
def __init__(self, operand):
self.operand = operand
class BinaryOperator(Node):
def __init__(self, left, right):
self.left = left
self.right = right
class Add(BinaryOperator):
pass
class Sub(BinaryOperator):
pass
class Mul(BinaryOperator):
pass
class Div(BinaryOperator):
pass
class Negate(UnaryOperator):
pass
class Number(Node):
def __init__(self, value):
self.value = value
# A sample visitor class that evaluates expressions
class Evaluator(NodeVisitor):
def visit_Number(self, node):
return node.value
def visit_Add(self, node):
return self.visit(node.left) + self.visit(node.right)
def visit_Sub(self, node):
return self.visit(node.left) - self.visit(node.right)
def visit_Mul(self, node):
return self.visit(node.left) * self.visit(node.right)
def visit_Div(self, node):
return self.visit(node.left) / self.visit(node.right)
def visit_Negate(self, node):
return -self.visit(node.operand)
if __name__ == '__main__':
# 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)
# Evaluate it
e = Evaluator()
print(e.visit(t4)) # Outputs 0.6
如果嵌套層次太深那么上述的Evaluator就會失效:
>>> a = Number(0)
>>> for n in range(1, 100000):
... a = Add(a, Number(n))
...
>>> e = Evaluator()
>>> e.visit(a)
Traceback (most recent call last):
...
File "visitor.py", line 29, in _visit
return meth(node)
File "visitor.py", line 67, in visit_Add
return self.visit(node.left) + self.visit(node.right)
RuntimeError: maximum recursion depth exceeded
>>>
現(xiàn)在我們稍微修改下上面的Evaluator: class Evaluator(NodeVisitor):
def visit_Number(self, node):
return node.value
def visit_Add(self, node):
yield (yield node.left) + (yield node.right)
def visit_Sub(self, node):
yield (yield node.left) - (yield node.right)
def visit_Mul(self, node):
yield (yield node.left) * (yield node.right)
def visit_Div(self, node):
yield (yield node.left) / (yield node.right)
def visit_Negate(self, node):
yield - (yield node.operand)
再次運(yùn)行,就不會報錯了:
>>> a = Number(0)
>>> for n in range(1,100000):
... a = Add(a, Number(n))
...
>>> e = Evaluator()
>>> e.visit(a)
4999950000
>>>
如果你還想添加其他自定義邏輯也沒問題:
class Evaluator(NodeVisitor):
...
def visit_Add(self, node):
print('Add:', node)
lhs = yield node.left
print('left=', lhs)
rhs = yield node.right
print('right=', rhs)
yield lhs + rhs
...
下面是簡單的測試:
>>> e = Evaluator()
>>> e.visit(t4)
Add: <__main__.Add object at 0x1006a8d90>
left= 1
right= -0.4
0.6
>>>
這一小節(jié)我們演示了生成器和協(xié)程在程序控制流方面的強(qiáng)大功能。避免遞歸的一個通常方法是使用一個?;蜿犃械臄?shù)據(jù)結(jié)構(gòu)。例如,深度優(yōu)先的遍歷算法,第一次碰到一個節(jié)點時將其壓入棧中,處理完后彈出棧。visit()
方法的核心思路就是這樣。
另外一個需要理解的就是生成器中yield語句。當(dāng)碰到y(tǒng)ield語句時,生成器會返回一個數(shù)據(jù)并暫時掛起。上面的例子使用這個技術(shù)來代替了遞歸。例如,之前我們是這樣寫遞歸:
value = self.visit(node.left)
現(xiàn)在換成yield語句:
value = yield node.left
它會將 node.left
返回給 visti()
方法,然后 visti()
方法調(diào)用那個節(jié)點相應(yīng)的 vist_Name()
方法。yield暫時將程序控制器讓出給調(diào)用者,當(dāng)執(zhí)行完后,結(jié)果會賦值給value,
看完這一小節(jié),你也許想去尋找其它沒有yield語句的方案。但是這么做沒有必要,你必須處理很多棘手的問題。例如,為了消除遞歸,你必須要維護(hù)一個棧結(jié)構(gòu),如果不使用生成器,代碼會變得很臃腫,到處都是棧操作語句、回調(diào)函數(shù)等。實際上,使用yield語句可以讓你寫出非常漂亮的代碼,它消除了遞歸但是看上去又很像遞歸實現(xiàn),代碼很簡潔。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: