• 通过@classmethod 实现多态


    通过@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')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    上面接收信息处理信息的示例存在两个问题:

    • 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')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    通过类方法多态重构代码后,mapreduce函数支持了多态,它接收的是一个父类类型,根据传入的实际子类实现把不同的功能。当再扩展GenericInputData、GenericWorker子类后,mapreduce函数不需要修改代码,实现了左开右闭原则。

  • 相关阅读:
    FPGA高端项目:FPGA基于GS2971+GS2972架构的SDI视频收发+图像缩放,提供3套工程源码和技术支持
    嵌入式工程师常见面试题1-C_C++语言
    6.4-为何要深度学习
    Arduino驱动AS7341可见光谱传感器(颜色传感器篇)
    npm后面的 -S和-D参数详解
    数据采集模拟输入配置的连接方式(NI数据采集卡)
    扫地机器人还能创新吗?云鲸给了个Yes
    【hadoop】mapreduce面试题总结
    【工具 & 技巧 & 笔试】PyCharm搜索快捷键大总结 | 【笔试题分享】2023美团算法策略方向题目 解析思路及实例代码(含时间复杂度分析)| 动态规划 求解:人在地图中行走 获得最大金币数量
    健康防猝指南1:体重和减肥的秘密
  • 原文地址:https://blog.csdn.net/m0_38039437/article/details/128050392