Python 如何實(shí)現(xiàn)訪問(wèn)者模式
問(wèn)題
你要處理由大量不同類型的對(duì)象組成的復(fù)雜數(shù)據(jù)結(jié)構(gòu),每一個(gè)對(duì)象都需要需要進(jìn)行不同的處理。比如,遍歷一個(gè)樹形結(jié)構(gòu),然后根據(jù)每個(gè)節(jié)點(diǎn)的相應(yīng)狀態(tài)執(zhí)行不同的操作。
解決方案
這里遇到的問(wèn)題在編程領(lǐng)域中是很普遍的,有時(shí)候會(huì)構(gòu)建一個(gè)由大量不同對(duì)象組成的數(shù)據(jù)結(jié)構(gòu)。假設(shè)你要寫一個(gè)表示數(shù)學(xué)表達(dá)式的程序,那么你可能需要定義如下的類:
class Node:
pass
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
然后利用這些類構(gòu)建嵌套數(shù)據(jù)結(jié)構(gòu),如下所示:
# Representation of 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)
這樣做的問(wèn)題是對(duì)于每個(gè)表達(dá)式,每次都要重新定義一遍,有沒有一種更通用的方式讓它支持所有的數(shù)字和操作符呢。這里我們使用訪問(wèn)者模式可以達(dá)到這樣的目的:
class NodeVisitor:
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__))
為了使用這個(gè)類,可以定義一個(gè)類繼承它并且實(shí)現(xiàn)各種 visit_Name() 方法,其中Name是node類型。例如,如果你想求表達(dá)式的值,可以這樣寫:
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 -node.operand
使用示例:
>>> e = Evaluator() >>> e.visit(t4) 0.6 >>>
作為一個(gè)不同的例子,下面定義一個(gè)類在一個(gè)棧上面將一個(gè)表達(dá)式轉(zhuǎn)換成多個(gè)操作序列:
class StackCode(NodeVisitor):
def generate_code(self, node):
self.instructions = []
self.visit(node)
return self.instructions
def visit_Number(self, node):
self.instructions.append(('PUSH', node.value))
def binop(self, node, instruction):
self.visit(node.left)
self.visit(node.right)
self.instructions.append((instruction,))
def visit_Add(self, node):
self.binop(node, 'ADD')
def visit_Sub(self, node):
self.binop(node, 'SUB')
def visit_Mul(self, node):
self.binop(node, 'MUL')
def visit_Div(self, node):
self.binop(node, 'DIV')
def unaryop(self, node, instruction):
self.visit(node.operand)
self.instructions.append((instruction,))
def visit_Negate(self, node):
self.unaryop(node, 'NEG')
使用示例:
>>> s = StackCode()
>>> s.generate_code(t4)
[('PUSH', 1), ('PUSH', 2), ('PUSH', 3), ('PUSH', 4), ('SUB',),
('MUL',), ('PUSH', 5), ('DIV',), ('ADD',)]
>>>
討論
剛開始的時(shí)候你可能會(huì)寫大量的if/else語(yǔ)句來(lái)實(shí)現(xiàn),這里訪問(wèn)者模式的好處就是通過(guò) getattr() 來(lái)獲取相應(yīng)的方法,并利用遞歸來(lái)遍歷所有的節(jié)點(diǎn):
def binop(self, node, instruction): self.visit(node.left) self.visit(node.right) self.instructions.append((instruction,))
還有一點(diǎn)需要指出的是,這種技術(shù)也是實(shí)現(xiàn)其他語(yǔ)言中switch或case語(yǔ)句的方式。比如,如果你正在寫一個(gè)HTTP框架,你可能會(huì)寫這樣一個(gè)請(qǐng)求分發(fā)的控制器:
class HTTPHandler:
def handle(self, request):
methname = 'do_' + request.request_method
getattr(self, methname)(request)
def do_GET(self, request):
pass
def do_POST(self, request):
pass
def do_HEAD(self, request):
pass
訪問(wèn)者模式一個(gè)缺點(diǎn)就是它嚴(yán)重依賴遞歸,如果數(shù)據(jù)結(jié)構(gòu)嵌套層次太深可能會(huì)有問(wèn)題,有時(shí)候會(huì)超過(guò)Python的遞歸深度限制(參考 sys.getrecursionlimit() )。
在跟解析和編譯相關(guān)的編程中使用訪問(wèn)者模式是非常常見的。Python本身的 ast 模塊值的關(guān)注下,可以去看看源碼。
以上就是Python 如何實(shí)現(xiàn)訪問(wèn)者模式的詳細(xì)內(nèi)容,更多關(guān)于Python 訪問(wèn)者模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在Python中操作列表之list.extend()方法的使用
這篇文章主要介紹了在Python中操作列表之list.extend()方法的使用,是Python入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-05-05
Python數(shù)據(jù)類型學(xué)習(xí)筆記
這篇文章主要針對(duì)Python數(shù)據(jù)類型為大家進(jìn)行了詳細(xì)介紹,整理一篇關(guān)于Python數(shù)據(jù)類型的學(xué)習(xí)筆記,感興趣的小伙伴們可以參考一下2016-01-01
Python多進(jìn)程方式抓取基金網(wǎng)站內(nèi)容的方法分析
這篇文章主要介紹了Python多進(jìn)程方式抓取基金網(wǎng)站內(nèi)容的方法,結(jié)合實(shí)例形式分析了Python多進(jìn)程抓取網(wǎng)站內(nèi)容相關(guān)實(shí)現(xiàn)技巧與操作注意事項(xiàng),需要的朋友可以參考下2019-06-06
Django JSonResponse對(duì)象的實(shí)現(xiàn)
本文主要介紹了Django JSonResponse對(duì)象的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
Python靜態(tài)類型檢查新工具之pyright 使用指南
這篇文章主要介紹了Python靜態(tài)類型檢查新工具之pyright 使用指南,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04
Python標(biāo)準(zhǔn)庫(kù)defaultdict模塊使用示例
這篇文章主要介紹了Python標(biāo)準(zhǔn)庫(kù)defaultdict模塊使用示例,本文講解了如何使用defaultdict給字典value元素添加默認(rèn)類型以及defaultdict的兩個(gè)使用小案例,需要的朋友可以參考下2015-04-04
PyQt5實(shí)現(xiàn)五子棋游戲(人機(jī)對(duì)弈)
這篇文章主要為大家詳細(xì)介紹了PyQt5實(shí)現(xiàn)五子棋游戲,人機(jī)對(duì)弈,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01
打印tensorflow恢復(fù)模型中所有變量與操作節(jié)點(diǎn)方式
這篇文章主要介紹了打印tensorflow恢復(fù)模型中所有變量與操作節(jié)點(diǎn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-05-05

