35、Python之面向对象:基于抽象类半成品,高效实现自定义序列类

引言

上一篇文章中,我们简单介绍了抽象基类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()

执行结果:

beacb702d47742259362ea8ebf541af5.jpeg

看着似乎没啥问题,但是,我们能不能像是用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()

执行结果:

2a895ad593db0f0b8b2ea4e3aee4976a.jpeg

通过直接继承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()

执行结果:

a28a6685a8db10a41d1ab05652e265e5.jpeg

从执行结果看,虽然没有继承list,但是,我们依然实现了前一种方法的各种使用场景。

这种实现方式中,我们通过继承MutableSequence这个抽象基类,然后实现了其中的几个抽象方法,大部分都是__打头的方法名。

简单说明一下这些方法:

1、insert()方法:当我们调用append()方法时,实际上会调用insert()方法的实现。

2、__getitem__()方法:当我们对对象进行[]索引,或者进行for循环遍历时,会自动调用对象中的__getitem__()方法。

3、__setitem__()方法:修改某个元素时,会自动调用该方法。

4、__delitem__()方法:通过del删除某个元素时,会调用该方法。

5、__len__()方法:当通过使用内置函数len()计算元素个数时,会自动调用该方法。

总结

这篇文章中,我们通过两种方式,实现了自定义序列类的效果,使得自定义类的实例对象也支持内置对象的类似的行为。

需要说明的是,这些__xxx__的方法,在Python中被称为“魔术方法”,可以理解为要想对象支持某种行为,需要遵守的协议。

感兴趣的同学,可以试一下,即使不显式继承任何类,只需要在类中实现特定的魔术方法,也能实现[]索引、for循环遍历等效果。

关于魔术方法的内容,在后续的文章中,将会进行系统的介绍。

感谢您的拨冗阅读,如果对您学习Python有些许帮助,欢迎点赞、收藏。

  • 16
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南宫理的日知录

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值