Python中通过@classmethod 实现多态的示例
Bruce小鬼 人气:2通过@classmethod 实现多态
1.概述
python中通常使用对象创建多态模式,python还支持类创建多态模式。下面通过一个例子展示它如何实现多态。
通过对象创建多态和类创建多态开发模式区别
- 对象多态模式:接收的是一个父类类型对象,然后通过传入父类的子类对象调用普通同名方法,实现不同的行为。
- 类多态模式:接收的一个父类,然后通过传入父类的子类调用同名的类方法,实现不通的行为
2.类方法创建多态模式示例
2.1.普通模式
先通过一个的示例看下常规方式发开的代码没有使用多态时候存在的问题,然后在通过类多态来优化代码。
下面实现一个读取数据,然后处理数据的示例,他有两条继承体系。
输入信息体系:将输入信息的方式创建为类继承关系,可以根据读取信息的方式创建不同的InputData子类。
处理数据体系:将处理信息的方式创建类继承关系,根据对信息处理的方式创建不同的Worker子类。
然后通过mapreduce函数组合业务执行逻辑,最后输出运行结果。
# 接收输入信息类体系 class InputData: def read(self): raise NotImplementedError class PathInputData(InputData): def __init__(self,path): super().__init__() self.path = path def read(self): with open(self.path) as f: return f.read() # 处理信息类体系 class Worker: def __init__(self, input_data): self.input_data = input_data self.result = None def map(self): raise NotImplementedError def reduce(self, other): raise NotImplementedError class LineCountWorker(Worker): def map(self): data = self.input_data self.result = data.count('/n') def reduce(self, other): self.result += other.result # 组合业务 import os def generate_inputs(data_dit): for name in os.listdir(data_dit): # 读文件内容 yield PathInputData(os.path.join(data_dit, name)) def create_workers(input_list): workers = [] for input_data in input_list: # 处理数据 workers.append(LineCountWorker(input_data)) return workers from threading import Thread 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 for worker in rest: first.reduce(worker) return first.result def mapreduce(data_dir): inputs = generate_inputs(data_dir) workers = create_workers(inputs) return execute(workers) import os import random def write_test_files(tmpdir): os.makedirs(tmpdir) for i in range(100): with open(os.path.join(tmpdir, str(i)), 'w') as f: f.write('\n' * random.randint(0, 100)) tmpdir = 'test_inputs' write_test_files(tmpdir) result = mapreduce(tmpdir) print(f'There are {result} lines')
上面接收信息处理信息的示例存在两个问题:
- mapreduce函数通用性不好,不易扩展。例如现在创建了新的InputData、Worker子类,那么就要创建一个新的mapreduce函数来组合新的业务流程,这样会导致重复的代码越来越多,不利于维护。
- python不能向java语言可以在一个类中以重载的形式创建多个构造器,创建一个构造器多态让子类可以根据不同的构造器接收不同的参数。而python不能这么做,因为python的类只能有一个构造方法(init),所以没有办法为不同的子类提供多种形式的形参构造器,在继承中也没有办法要求所有的子类都只接收只有一种方式参数的构造方法。因为子类要根据自身的特点接收不同类型的参数。
这个问题最好能够通过类方法多态来解决,这种多态与实例方法多态很像,只不过他针对的是类,而不是这些类的对象。
2.2.类方法多态重构业务
类方法多态的实现非常简单,下面将代码中关键的点提炼出来。
- 在父类中创建一个通用的方法,不需要实现任何业务逻辑,然后让子类中重写该方法,实现不同的行为。在方法上使用@classmethod装饰器声明为类方法,方便调用时候可以以类调用,而不是实例对象调用,通过子类重写父类的类方法就是类方法多态。
- 在GenericInputData类中定义一个类方法(generate_inputs)用来解决python只有一个构造方法不能实现多种方式接收参数的弊端,子类通过重写该方法,实现接收多种参数行为。
class GenericInputData: def read(self): raise NotImplementedError # 创建一个多态的类方法,让子类实现不同的功能 @classmethod def generate_inputs(cls, config): raise NotImplementedError class PathInputData(GenericInputData): def __init__(self, path): super().__init__() self.path = path def read(self): with open(self.path) as f: return f.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)) class GenericWorker: def __init__(self, input_data): self.input_data = input_data self.result = None def map(self): raise NotImplementedError def reduce(self, other): raise NotImplementedError @classmethod def create_workers(cls, input_class, config): workers = [] for input_data in input_class.generate_inputs(config): workers.append(cls(input_data)) return workers class LineCountWorker(GenericWorker): def map(self): data = self.input_data.read() self.result = data.count('\n') def reduce(self, other): self.result += other.result # 定义的形参类型为父类,实现了类方法多态 def mapreduce(worker_class, input_class, config): workers = worker_class.create_workers(input_class, config) return execute(workers) config = {'data_dir': tmpdir} result = mapreduce(LineCountWorker, PathInputData, config) print(f'There are {result} lines')
通过类方法多态重构代码后,mapreduce函数支持了多态,它接收的是一个父类类型,根据传入的实际子类实现把不同的功能。当再扩展GenericInputData、GenericWorker子类后,mapreduce函数不需要修改代码,实现了左开右闭原则。
加载全部内容