zoukankan      html  css  js  c++  java
  • Python基础学习笔记(24)利用类理解queue和stack 经典类与新式类 抽象类 多态 鸭子类型

    Python基础学习(24)利用类理解queue和stack 经典类与新式类 抽象类 多态 鸭子类型

    一、今日内容大纲

    • 利用类理解 queue 和 stack(类的练习)
    • 自定义 Pickle 类(类的练习)
    • 经典类、新式类和 C3 算法
    • 抽象类
    • 多态
    • 鸭子类型 Duck Typing

    二、利用类理解 queue 和 stack

    在引入队列(queue)和栈(stack)的概念的同时,我们需要引入一个数据结构(Data Structure)的概念,队列和栈都属于数据结构的一种。

    • 数据结构:相互之间存在一种或多种特定关系的数据元素的集合。
    • 队列:是一种特殊的线性表,它满足FIFO(First In First Out)的条件,只能从一端进入且只能从另一端取出。
    • 栈:是一种特殊的线性表,它满足 LIFO(Last In First Out)的条件,只能从一端进入且只能从此端取出。

    现在我们利用类的方式,实现自定义类的栈和队列(伪数据结构,只实现功能):

    class Lis:
        def __init__(self):
            self.elements = []
            self.length = 0
    
        def my_push(self, *args):
            for i in args:
                self.elements.append(i)
                self.length += 1
    
        def my_pop(self):
            ret = self.elements.pop()
            self.length -= 1
            return ret
    
    
    class Queue(Lis):
        def __init__(self):
            Lis.__init__(self)
            self.tp = 'queue'
    
        def my_pop(self):
            return self.elements.pop(0)
    
    
    class Stack(Lis):
        def __init__(self):
            Lis.__init__(self)
            self.tp = 'stack'
    
        def my_pop(self):
            ret = Lis.my_pop(self)
            return ret
    
    
    q = Queue()
    s = Stack()
    q.my_push(5, 2, 3, 4, 5, 6)
    for i in range(q.length):
        print(q.my_pop())
    s.my_push(5, 2, 3, 4, 5, 6)
    for i in range(s.length):
        print(s.my_pop())
    

    三、自定义 Pickle 类

    自定义 Pickle 类,借助 pickle 模块来完成简化的 dump 和 load:

    class My_pickle:
        def __init__(self, path):
            self.path = path
            self.load_count = 1
    
        def my_load(self):
            with open(self.path, mode='rb') as file_handler:
                for i in range(self.load_count):
                    ret = pickle.load(file_handler)
            self.load_count += 1
            return ret
    
        def my_dump(self, obj):
            with open(self.path, mode='ab') as file_handler:
                pickle.dump(obj, file_handler)
    
    # 我们借助刚刚实现的
    s = Stack()
    q = Queue()
    s.my_push(5, 2, 3, 4, 5, 6)
    q.my_push(5, 2, 3, 4, 5, 6)
    print(s, q)
    obj = My_pickle('temp')
    obj.my_dump(s)
    obj.my_dump(q)
    obj_out1 = obj.my_load()
    obj_out2 = obj.my_load()
    print(obj_out1)
    print(obj_out2)
    

    四、经典类、新式类和 C3 算法

    今天主要介绍经典类、新式类的继承算法,首先先介绍什么是经典类和新式类:

    • 经典类:在 Python3x中不存在,在Python2x中不主动继承object的类都是经典类,一般在继承时采取深度优先查找DFS策略。
    • 新式类:只要继承object的类就是新式类,Python3x中所有的类都继承object类,Python3x中所有的类都是新式类,一般在继承时采取广度优先查找 BFS 策略(实际上是使用和 BFS 略有不同的 C3 算法)。

    而继承时如何查找父类的方法的问题,我们称为 MRO(method resolution order) 问题,一组类的 MRO 序列就是在调用方法时在父类中查找方法的顺序序列:

    # 在单继承方面
    class A:
        def func(self): pass
    class B(A):
        def func(self): pass
    class C(B):
        def func(self): pass
    class D(C):
        def func(self): pass
    # 寻找顺序D->C->B->A
    # 深度优先
    
    # 多继承
    class A:
        def func(self): print('A')
    class B(A):
        # def func(self): print('B')
        pass
    class C(A):
        def func(self): print('C')
    class D(B, C):
        # def func(self): print('D')
        pass
    # 新式查找顺序D->B->C->A 广度优先
    # 经典类查找顺序D->B->A->C 深度优先
    temp = D()
    temp.func()
    
    
    # 新式类查找使用C3算法
    #    A              A(object) = [AO]
    #  /               B(A)      = [BAO]
    # B     C           C(A)      = [CAO]
    # |     |           D(B)      = [DBAO]
    # D     E           E(C)      = [ECAO]
    #     /            F(D,E)    = merge(D(B) + E(C))
    #    F                        = merge([F] + [DBAO] + [ECAO])
    #                             = [F] + merge([DBAO] + [ECAO])
    #                             = [FD] + merge([BAO] + [ECAO])
    #                             = [FDB] + merge([AO] + [ECAO])
    #                             = [FDBE] + merge([AO] + [CAO])
    #                             = [FDBEC] + merge([AO] + [AO])
    #                             = [FDBECA] + merge([O] + [O])
    #                             = [FDBECAO]
    

    C3 算法规则,其实就是一个不断进行merge操作的过程,它遵循的基本流程如下:

    • 从左只有读取到的第一个在其他临近节点遍历顺序未出现过的节点;
    • 同时出现在每个临近节点第一个遍历顺序的节点;
    • 提取后再节点遍历顺序中删除已加入节点。

    另外,新式类本身也提供了一种方法classname.mro()返回 MRO 顺序。

    五、抽象类

    首先先介绍什么是抽象类:

    • 抽象类:是一种开发的规范,约束他的子类必须拥有和它方法里同名的方法。

    那么我么需要再什么样的情境下使用抽象类呢,加入我们在某购物网站编写了微信支付和支付宝的支付程序:

    # 支付程序:
    # 微信支付:url链接,告诉你参数是什么格式
    # {'username': 'alex', 'money': 200}
    # 支付宝支付: url链接,也告诉你参数是什么格式
    # {'uname': 'alex', 'price': 200}
    
    class Alipay:
        def __init__(self, name):
            self.name = name
    
        def pay(self, money):
            dic = {'uname': self.name, 'price': money}
            # 想办法调用支付宝支付 url链接 把dic传过去
            print('%s通过支付宝支付%s成功' % (self.name, money))
    
    
    class WechatPay:
        def __init__(self, name):
            self.name = name
    
        def pay(self, money):
            dic = {'username': 'alex', 'money': 200}
            # 想办法调用微信支付 url链接 把dic传过去
            print('%s通过微信支付%s成功' % (self.name, money))
    
    
    aw = WechatPay('alex')
    aw.pay(400)  # alex通过微信支付400成功
    aa = Alipay('alex')
    aa.pay(500)  # alex通过支付宝支付500成功
    

    在实现基本功能后,我们对其进行了归一化设计:

    # 归一化设计
    def pay(name, price, mode):
        if mode == 'Wechat':
            obj = WechatPay(name)
        elif mode == 'Alipay':
            obj = Alipay(name)
        obj.pay(price)
    
    
    pay('alex', 400, 'Wechat')  # alex通过微信支付400成功
    pay('alex', 500, 'Alipay')  # alex通过支付宝支付500成功
    

    这时又来了一个程序员,它来负责写苹果支付的支付程序并植入归一化设计中,而他是这么写的:

    class Applepay:
        def __init__(self, name):
            self.name = name
    
        def fuqian(self, money):
            dic = {'name': self.name, 'qian': money}
            # 想办法调用苹果支付 url链接 把dic传过去
            print('%s通过苹果支付%s成功' % (self.name, money))
            
    
    # 我们按照原来的规则归一化设计
    def pay(name, price, mode):
        if mode == 'Wechat':
            obj = WechatPay(name)
        elif mode == 'Alipay':
            obj = Alipay(name)
        elif mode == 'Applepay':
            obj = Applepay(name)
        obj.pay(price)
        
        
    # 但是由于苹果支付类的设计里用的方法名不是pay,发现使用过程中会报错
    pay('alex', 400, 'Wechat')  # alex通过微信支付400成功
    pay('alex', 500, 'Alipay')  # alex通过支付宝支付500成功
    pay('alex', 500, 'Applepay')  # 报错
    

    这时我们就可以约定一个规范,让他如果在类定义中不添加方法pay就会报错:

    # 我们可以约定一个规范,如果他在类定义中不添加方法pay就会出现报错
    class Payment:
        def pay(self, money):
            raise NotImplementedError('请在子类中重写同名pay方法')
    
    
    # 然后将其他的支付函数继承这个Payment类
    class Alipay(Payment):
        def __init__(self, name):
            self.name = name
    
        def pay(self, money):
            dic = {'uname': self.name, 'price': money}
            # 想办法调用支付宝支付 url链接 把dic传过去
            print('%s通过支付宝支付%s成功' % (self.name, money))
    
    
    class WechatPay(Payment):
        def __init__(self, name):
            self.name = name
    
        def pay(self, money):
            dic = {'username': 'alex', 'money': 200}
            # 想办法调用微信支付 url链接 把dic传过去
            print('%s通过微信支付%s成功' % (self.name, money))
    
    
    class Applepay(Payment):
        def __init__(self, name):
            self.name = name
    
        def fuqian(self, money):
            dic = {'name': self.name, 'qian': money}
            # 想办法调用苹果支付 url链接 把dic传过去
            print('%s通过苹果支付%s成功' % (self.name, money))
    
    
    def pay(name, price, mode):
        if mode == 'Wechat':
            obj = WechatPay(name)
        elif mode == 'Alipay':
            obj = Alipay(name)
        elif mode == 'Applepay':
            obj = Applepay(name)
        obj.pay(price)
    
    
    # pay('alex', 400, 'Wechat')  # alex通过微信支付400成功
    # pay('alex', 500, 'Alipay')  # alex通过支付宝支付500成功
    # pay('alex', 500, 'Applepay')  # NotImplementedError: 请在子类中重写同名pay方法
    

    那么,为什么定义一个共同父类会实现这样的功能呢?它的实际流程实际是,在我们自己定义的类中寻找pay方法,没有找到回去Payment类中寻找,这时会根据Payment类的方法pay抛出异常。

    另外也可以利用abc模块中的ABCMetaabstractmethod实现抽象类,具体编写方法如下:

    # 另一种方式
    from abc import  ABCMeta, abstractmethod
    class Payment(metaclass=ABCMeta):
        @abstractmethod
        def pay(self, money): pass
    
    class Alipay(Payment):
        def __init__(self, name):
            self.name = name
    
        def pay(self, money):
            dic = {'uname': self.name, 'price': money}
            # 想办法调用支付宝支付 url链接 把dic传过去
            print('%s通过支付宝支付%s成功' % (self.name, money))
    
    
    class WechatPay(Payment):
        def __init__(self, name):
            self.name = name
    
        def pay(self, money):
            dic = {'username': 'alex', 'money': 200}
            # 想办法调用微信支付 url链接 把dic传过去
            print('%s通过微信支付%s成功' % (self.name, money))
    
    
    class Applepay(Payment):
        def __init__(self, name):
            self.name = name
    
        def fuqian(self, money):
            dic = {'name': self.name, 'qian': money}
            # 想办法调用苹果支付 url链接 把dic传过去
            print('%s通过苹果支付%s成功' % (self.name, money))
    
    # 用这种方式约束力很强,连实例化都不行,但是依赖abc模块
    Applepay('alex')  # TypeError: Can't instantiate abstract class Applepay with abstract methods pay
    

    六、多态

    我们知道,在 Python 中处处皆多态,一切皆对象;现在我们已经了解了什么是对象,那么什么是多态呢,又为什么处处皆多态呢?

    我们先引入一段 java 代码:

    def add(int a, int b):
    	return a + b
    

    我们可以看到,与 Python 不同,Java 的形式参数在定义时,都会规定数据类型,而 Python 这种无需规定数据类型的调用其实就是多态,因为 Python 语法不用声明变量类型,所以 Python 一切皆多态:

    • 多态:一个类型表现出来的多种状态,在 Java 中可以将多个类指定一个共同的父类来实现。

    我们引入刚刚编写的支付程序代码:

    # 引用之前的归一化设计代码
    class Alipay:
        def __init__(self, name):
            self.name = name
    
        def pay(self, money):
            dic = {'uname': self.name, 'price': money}
            # 想办法调用支付宝支付 url链接 把dic传过去
            print('%s通过支付宝支付%s成功' % (self.name, money))
    
    
    class WechatPay:
        def __init__(self, name):
            self.name = name
    
        def pay(self, money):
            dic = {'username': 'alex', 'money': 200}
            # 想办法调用微信支付 url链接 把dic传过去
            print('%s通过微信支付%s成功' % (self.name, money))
    
    
    # 归一化设计
    # def pay(WechatPay obj, int price):
    #     obj.pay(price)
    # 在这中情况下出现问题,因为微信支付和阿里支付的类型不同,而只能通过下面这种笨拙的方法支付:
        
    obj1 = WechatPay('alex')
    pay(obj1, 400)
    
    obj2 = Alipay('alex')
    pay(obj2, 500)
    

    这时我们引入一个共同的父类:

    # 这种情况下我们可以利用这种方式实现多态,定义一个新的父类Payment,让两个支付方式的类都继承它
    class Payment: pass
    class Alipay(Payment):
        def __init__(self, name):
            self.name = name
    
        def pay(self, money):
            dic = {'uname': self.name, 'price': money}
            # 想办法调用支付宝支付 url链接 把dic传过去
            print('%s通过支付宝支付%s成功' % (self.name, money))
    
    
    class WechatPay(Payment):
        def __init__(self, name):
            self.name = name
    
        def pay(self, money):
            dic = {'username': 'alex', 'money': 200}
            # 想办法调用微信支付 url链接 把dic传过去
            print('%s通过微信支付%s成功' % (self.name, money))
    
    # 这样我们就可以把归一化的函数修改成这样:
    def pay(Payment obj, int price):
        obj.pay(price)
    

    七、鸭子类型

    鸭子类型和多态的概念息息相关," When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.",就是指一个鸟走路像鸭子游泳也像鸭子叫声也像鸭子,那么这只鸟就可以叫做鸭子。在 Python 中,鸭子类型就是指只关注对象能够实现的方法,而不关注对象本身的类型,如 Python 中的迭代器:它只关注对象本身能否具有__iter__方法和__next__方法,而不关注对象本身的类型。

  • 相关阅读:
    java-集合框架-泛型2-泛型限定
    进程间通信
    多进程编程基础概念
    linux deb 打包流程
    linux RPM 打包流程
    Python 第一個程序
    从注册验证码入手,我保住了30%的流失用户
    为什么Web端登录需要验证码?
    网络验证码的进化:从简单图文到无感验证
    公开课 | 金融知识图谱的应用探索
  • 原文地址:https://www.cnblogs.com/raygor/p/13366382.html
Copyright © 2011-2022 走看看