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
模块中的ABCMeta
和abstractmethod
实现抽象类,具体编写方法如下:
# 另一种方式
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__
方法,而不关注对象本身的类型。