在Python中,不僅對象支持多態,類也支持多態。
多態,使得繼承體系中的多個類能夠以各自獨有的方式來實現某個方法。這些類,都滿足相同的接口或繼承自相同的抽象類,但卻有着各自不同的功能。
案例1:實現MapReduce
流程,定義表示輸入數據的公共基類。
class InputData(object):
def read(self):
raise NotImplementedError
現在編寫InputData
類的具體子類,實現磁盤文件中的數據讀取。
class PathInputData(InputData):
def __init__(self,path) -> None:
super().__init__()
self.path = path
def read(self):
return open(self.path).read()
同時,我們可能需要多個像PathInputData
這樣的類充當InputData
的子類,以實現多個標準接口的read
方法,比如可以實現網絡讀取並解壓數據。
此外,我們還需要爲MapReduce
工作線程定義一套類似的抽象接口,以便於處理輸入的數據。
class Worker(object):
def __init__(self,input_data) -> None:
self.input_data = input_data
self.result = None
def map(self):
raise NotImplementedError
def reduce(self,other):
raise NotImplementedError
下面定義具體的子類,以實現我們想要的MapReduce
功能。本例實現簡單的換行符計數器。
class LineCountWorker(Worker):
def map(self):
data = self.input_data.read()
self.result += data.count('\n')
def reduce(self.other):
self.result += other.result
在實現了MapReduce
的各個組件後,需要將各個組件串聯起來,以實現整個流程,通常的方法是編寫輔助函數將這些類對象聯繫起來。
##生成器函數,生成數據
def generate_inputs(data_dir):
for name in os.listdir(data_dir):
yield PathInputData(os.path.join(data_dir,name))
### 創建多個worker對象
def create_workers(input_list):
workers = []
for input_data in input_list:
workers.append(LineCountWorker(input_data))
return workers
### 將每個對象分發到各個線程執行
def execute(workers):
threads = [Thread(target=w.map for w in workers)]
for thread in threads:thread.start()
for thread in threads:thread.join()
first,rest = workers[0],workers[1:]
for worker in rest:
first.reduce(worker)
return first.result
### 執行mapreduce函數
def mapreduce(data_dir):
inputs = generate_inputs(data_dir)
workers = create_workers(inputs)
return execute(workers)
整個調用的流程圖如下所示:
上述寫法存在的主要問題是MapReduce
函數不夠通用。如果編寫其他的InputData
或Worker
子類,那就得重寫generate_inputs
、create_workers
和mapreduce
函數。
其實,在C++
或者Java
當中,可以通過構造函數的重載來解決,但是在Python中只允許名爲__init__
的構造器方法,所以不能提供多個不同輸入參數的__init__
方法。
@classmethod
解決這個問題最好的方法,是使用@classmethod
形式的多態,即類方法的多態機制。
首先修改InputData
類,爲它添加通用的generate_inputs
類方法,該方法會根據通用的接口來創建新的InputData
實例
class GenericInputData(object):
def read(self):
raise NotImplementedError
@classmethod
def generate_inputs(cls,config):
raise NotImplementerError
## 修改子類
class PathInputData(GenericInputData):
#...
def read(self):
return open(self.path).read()
@classmethod
def generate_inputs(cls,config):
data_dir = config['data_dir']
for name in os.listdir(data_dir):
yield cls(os.path.join(data_dir,name))
按照同樣的方法修改Worker
類,並添加create_workers
方法。
class GenericWorker(object):
#...
def map(self):
raise NotImplementedError
def reduce(self,other):
raise NotImplementedError
@classmethod
def create_inputs(cls,input_class,config):
workers = []
for input_data in input_class.generate_inputs(config):
worker.append(cls(input_data))
return workers
上述代碼的重點在於input_class.generate_inputs
的調用,是類級別的多態調用,同時GenericWorker
對象通過cls形式構造。
具體的GenericWorker
子類,只需修改繼承的父類即可。
class LineCountWorker(GenericWorker):
#...
最後,重寫mapreduce
函數:
def mapreduce(worker_class,input_class,config):
wokers = worker_class.create_workers(input_class,config)
return execute(workers)
if __name__=='__main__':
config = {'data_dir':'./data/'}
result = mapreduce(LineCountWorker,PathInputData,config)
最後,我們可以編寫GenericInputData
和GenericWorker
的其他子類,而無需修改函數代碼,僅需要改動的是mapreduce
的輸入參數即可。