引言
上一篇文章中,我们简单介绍了抽象基类ABC,也很简略地介绍了其使用场景,但是,估计不少同学还会觉得没什么卵用,反而是费力不讨好……
今天这篇文章中,我打算通过实现自定义序列类的实例,来展示到抽象基类的作用,进一步加深对抽象基类的理解。
业务场景
基于前面的打工人的实例,我们继续扩展,假设我们有若干个打工人组成了一个团队,然后写了如下代码:
from abc import ABC, abstractmethod
# 打工人抽象基类
class DaGongRen(ABC):
def __init__(self, name, age):
self.name = name
self.age = age
@abstractmethod
def work(self):
pass
# 具体打工人的子类:产品狗、程序猿
class ProductManager(DaGongRen):
def work(self):
print(f"{self.age}岁的产品经理{self.name}分析需求、设计产品")
class Programmer(DaGongRen):
def work(self):
print(f"{self.age}岁的程序员{self.name}依然在努力写Python代码")
# 定义一个团队的类
class Team:
def __init__(self, name, dgrs=None):
if dgrs is None:
dgrs = []
self.name = name
self.dgrs = dgrs
def team_work(self):
print(f"【{self.name}】总共有{self.count()}个成员:")
for dgr in self.dgrs:
dgr.work()
def add(self, dgr):
self.dgrs.append(dgr)
def count(self):
return len(self.dgrs)
if __name__ == '__main__':
# 新建一个团队
team = Team('散兵游勇俱乐部')
# 加入1个产品经理
team.add(ProductManager('狗子', 60))
# 加入3个程序员
team.add(Programmer('光头1', 35))
team.add(Programmer('光头2', 35))
team.add(Programmer('光头3', 35))
team.team_work()
执行结果:
看着似乎没啥问题,但是,我们能不能像是用list一样使用Team类的实例对象呢,比如,我们可以进行for循环遍历一个Team实例对象,可以对其进行[]索引的操作。
方法1:通过继承list实现
其实实现方法可以有很多种,比如最容易想到的直接继承自list类。
直接看代码:
from abc import ABC, abstractmethod
# 打工人抽象基类
class DaGongRen(ABC):
def __init__(self, name, age):
self.name = name
self.age = age
@abstractmethod
def work(self):
pass
# 这里添加了一个__str__方法,用于更加直观的print()输出对象信息
def __str__(self):
return f"打工人{self.name},今年{self.age}岁"
# 具体打工人的子类:产品狗、程序猿
class ProductManager(DaGongRen):
def work(self):
print(f"{self.age}岁的产品经理{self.name}分析需求、设计产品")
class Programmer(DaGongRen):
def work(self):
print(f"{self.age}岁的程序员{self.name}依然在努力写Python代码")
# 定义一个团队的类
class Team(list):
def __init__(self, name, dgrs=None):
if dgrs is None:
dgrs = []
self.name = name
super().__init__(dgrs)
if __name__ == '__main__':
# 新建一个团队
team = Team('散兵游勇俱乐部')
# 加入1个产品经理
team.append(ProductManager('狗子', 60))
# 加入3个程序员
team.append(Programmer('光头1', 35))
team.append(Programmer('光头2', 35))
team.append(Programmer('光头3', 35))
print(team[0])
print(len(team))
for dgr in team:
dgr.work()
执行结果:
通过直接继承list方法,其实很简洁,我们只需要定义类的时候声明继承list,然后通过super的方式调用list的初始化。
然后,看到的效果就是Team类的实例对象,已经支持了list的各种适应场景,比如可以[]索引、比如可以使用len()统计个数,可以放到for循环中。同时list本身的方法也都是可以使用的,比如:append()、pop()等,可以自行尝试。
方法2:继承自抽象基类
继承自list的方法很简洁,但是,因为是一种偷懒的方式,掩盖了实现的一些细节,而这些细节涉及到Python中特定语法解析的对象协议。
接下来,我们通过继承抽象基类:collections.MutableSequence的方法来实现需求,然后再简单解释相关的对象协议。
还是直接看代码:
from abc import ABC, abstractmethod
from collections.abc import MutableSequence
# 打工人抽象基类
class DaGongRen(ABC):
def __init__(self, name, age):
self.name = name
self.age = age
@abstractmethod
def work(self):
pass
# 这里添加了一个__str__方法,用于更加直观的print()输出对象信息
def __str__(self):
return f"打工人{self.name},今年{self.age}岁"
# 具体打工人的子类:产品狗、程序猿
class ProductManager(DaGongRen):
def work(self):
print(f"{self.age}岁的产品经理{self.name}分析需求、设计产品")
class Programmer(DaGongRen):
def work(self):
print(f"{self.age}岁的程序员{self.name}依然在努力写Python代码")
# 定义一个团队的类
class Team(MutableSequence):
def __init__(self, name, dgrs=None):
if dgrs is None:
dgrs = []
self.name = name
self.dgrs = dgrs
def insert(self, index, value):
self.dgrs.insert(index, value)
def __getitem__(self, index):
return self.dgrs[index]
def __setitem__(self, index, value):
self.dgrs[index] = value
def __delitem__(self, index):
del self.dgrs[index]
def __len__(self):
return len(self.dgrs)
if __name__ == '__main__':
# 新建一个团队
team = Team('散兵游勇俱乐部')
# 加入1个产品经理
team.append(ProductManager('狗子', 60))
# 加入3个程序员
team.append(Programmer('光头1', 35))
team.append(Programmer('光头2', 35))
team.append(Programmer('光头3', 35))
print(team[0])
print(len(team))
for dgr in team:
dgr.work()
执行结果:
从执行结果看,虽然没有继承list,但是,我们依然实现了前一种方法的各种使用场景。
这种实现方式中,我们通过继承MutableSequence这个抽象基类,然后实现了其中的几个抽象方法,大部分都是__打头的方法名。
简单说明一下这些方法:
1、insert()方法:当我们调用append()方法时,实际上会调用insert()方法的实现。
2、__getitem__()方法:当我们对对象进行[]索引,或者进行for循环遍历时,会自动调用对象中的__getitem__()方法。
3、__setitem__()方法:修改某个元素时,会自动调用该方法。
4、__delitem__()方法:通过del删除某个元素时,会调用该方法。
5、__len__()方法:当通过使用内置函数len()计算元素个数时,会自动调用该方法。
总结
这篇文章中,我们通过两种方式,实现了自定义序列类的效果,使得自定义类的实例对象也支持内置对象的类似的行为。
需要说明的是,这些__xxx__的方法,在Python中被称为“魔术方法”,可以理解为要想对象支持某种行为,需要遵守的协议。
感兴趣的同学,可以试一下,即使不显式继承任何类,只需要在类中实现特定的魔术方法,也能实现[]索引、for循环遍历等效果。
关于魔术方法的内容,在后续的文章中,将会进行系统的介绍。
感谢您的拨冗阅读,如果对您学习Python有些许帮助,欢迎点赞、收藏。