Python中通过@classmethod 实现多态的示例
作者:Bruce小鬼 发布时间:2021-02-25 06:10:33
通过@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函数不需要修改代码,实现了左开右闭原则。
来源:https://blog.csdn.net/m0_38039437/article/details/128050392


猜你喜欢
- 1.如果每页都增加打印时间,又如何设置?打印时间的,你可以参考 for(var i=0;i<page.length;i++)
- 1,jdk配置由于jdk官网的链接不直接支持wget,可以使用下面的方法下载jdk,其中jdk版本为jdk1.8.0_91:wget --n
- tensorflow利用anaconda在ubuntu下安装方法及jupyter notebook运行目录及远程访问配置Ubuntu下安装A
- 一、pip异常有一小部分童鞋在打开cmd输入pip后出现下面情况:Did not provide a commandDid not prov
- write()方法把字符串str写入文件。没有返回值。由于缓冲,字符串可能不实际显示文件,直到flush()或close()方法
- Go Gin 实现文件的上传下载流读取文件上传routerrouter.POST("/resources/common/uploa
- 在使用django-rest-framework开发项目的时候我们总是避免不了跨域的问题,因为现在大多数的项目都是前后端分离,前后端项目部署
- 前期的入库筛选工作已经由url管理器完成了,整理的工作自然要由url下载器接手。当我们需要爬取的数据已经去重后,下载器的主要任务的是这些数据
- 有时候会用到查询倒数第二条记录 last=HolderChangeHistory.find_by_sql([" &nbs
- 前言第一次看go基础语法的时候,用使用到了defer。但是一直不知道它到底是什么,有什么用途。这几天通过查询、学习。算是对defer有了一点
- 在修改后的 《闲谈 Web 图片服务器》 一文中也提及了"IE 浏览器的连接数问题",这也是个有趣的话题。值得补充记录一
- 源代码:# coding=utf-8import loggingimport osimport timeLEVELS={'debug
- Sql server聚合函数在实际工作中应对各种需求使用的还是很广泛的,对于聚合函数的优化自然也就成为了一个重点,一个程序优化的好不好直接决
- 数据API数据集方法不会修改数据集,而是创建新数据集。可通过调用 map() 方法将转换应用于每个元素:dataset = dataset.
- 下面是我已经证实可用的自动备份的方法. 1、打开企业管理器->管理->sql server代理 2、新建一个作业,作业名称随便取
- 首先下载 MySQLdb #encoding=GBK import MySQLdb #import sys # #reload(sys) #
- 一般我们可以使用背景图的方式给图片添加阴影,但对于不固定尺寸的图片如何实现呢?我们可以采取“视觉欺骗 * ”——定义渐变边框来实现运行代码框&
- 一些简单的代码简化下面是一个简单示例,它说明了 jQuery 对代码的影响。要执行一些真正简单和常见的任务,比方说为页面的某一区域中的每个链
- 在上一篇文章中,我介绍了MySQL对XML支持的部分功能,包括--xml命令行选项,以及MySQL 5.1.5中开始引入的新功能。今天我将介
- 启发式搜索在人工智能中起着关键作用。在本章中,您将详细了解它。AI中的启发式搜索的概念启发式是一个经验法则,它引导我们找到可能的解决方案。人