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__方法,而不关注对象本身的类型。

  • 相关阅读:
    poj 1088 滑雪
    位运算与bitset
    hdu 4607 Park Visit
    树的直径
    codeforces 495D Sonya and Matrix
    German Collegiate Programming Contest 2015(第三场)
    BAPC 2014 Preliminary(第一场)
    Benelux Algorithm Programming Contest 2014 Final(第二场)
    E. Reachability from the Capital(tarjan+dfs)
    poj2104 K-th Number(划分树)
  • 原文地址:https://www.cnblogs.com/raygor/p/13366382.html
Copyright © 2011-2022 走看看