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

posted @ 2020-07-23 15:01  Raigor  阅读(206)  评论(0)    收藏  举报