惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Hackread – Cybersecurity News, Data Breaches, AI and More
C
Check Point Blog
Hacker News: Ask HN
Hacker News: Ask HN
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
WordPress大学
WordPress大学
P
Proofpoint News Feed
V
Visual Studio Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
N
Netflix TechBlog - Medium
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 聂微东
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 叶小钗
Cisco Talos Blog
Cisco Talos Blog
S
Schneier on Security
T
Threat Research - Cisco Blogs
腾讯CDC
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
The Hacker News
The Hacker News
Google DeepMind News
Google DeepMind News
Microsoft Security Blog
Microsoft Security Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
GbyAI
GbyAI
N
News | PayPal Newsroom
L
LINUX DO - 最新话题
酷 壳 – CoolShell
酷 壳 – CoolShell
P
Palo Alto Networks Blog
T
Tenable Blog
S
Secure Thoughts
T
Threatpost
V2EX - 技术
V2EX - 技术
大猫的无限游戏
大猫的无限游戏
Martin Fowler
Martin Fowler
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Vercel News
Vercel News
罗磊的独立博客
P
Privacy & Cybersecurity Law Blog
Engineering at Meta
Engineering at Meta
小众软件
小众软件
Google DeepMind News
Google DeepMind News
N
News and Events Feed by Topic
Y
Y Combinator Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
C
Cybersecurity and Infrastructure Security Agency CISA
P
Proofpoint News Feed
L
Lohrmann on Cybersecurity
P
Privacy International News Feed
H
Heimdal Security Blog
量子位
B
Blog

少数派

派早报:Google 发布 Fitbit Air 等 - 少数派 「新人报到」確認需求,再開始 - 少数派 从 SOLO 独立开发者社区,我看到了越来越多开发者开始做自己的产品 - 少数派 我怎么管理那些"不常做,但总会忘"的生活事项 - 少数派 人形机器人量产元年,数据才是具身智能的“生死线” - 少数派 BuhoLaunchpad 高度还原 Mac 启动台:开发历程与思考 - 少数派 五年陪伴依然不舍,DIY 换壳后让罗技 MX Master 3 继续服役 - 少数派 新玩意 240|少数派的编辑们最近买了啥? - 少数派 一日一技|为什么你应该关闭 iOS 的键盘声音 - 少数派 我做了个插件和 Skills,一键提取任何网站的设计规范 Design.md - 少数派 住在三四线城市的你,该开始录播客了 - 少数派 甘南秘境,大白高国 - 少数派 AI的审美:谁让把我变成川内倫子 - 少数派 返工怎能不烦恼,打工人片单总有一部是你的「嘴替」 - 少数派 为了让「上厕所」更健康,我做了一个小工具 - 少数派 AI + Skill,能够让生成的文章去除 AI 味吗? - 少数派 新玩意|韶音OpenDots ONE 耳夹式耳机 - 少数派 《美满》| 在每一个春天的晚上相爱(362) - 少数派 新玩意|优篮子 PS01 MagSnap 磁吸支架 - 少数派 自我整合手记 | 我开始早睡了:用稳定规则,为自由托底 - 少数派 用龙虾(OpenClaw)两个多月,我最深的12个体会 - 少数派 听歌时间到,12 张你可能错过的 2025 华语乐坛好专辑 - 少数派 承诺能追吗 - 少数派 macOS 26启动台没了? 我做了个不一样的App启动器 - Keboard - 少数派 《四海为家的人》| INTJ对话INTJ(361) - 少数派 你发过的那些黑历史,是时候一次清干净了 - 少数派 新玩意:安安静静玩,越玩越专注:计客密码机 - 少数派 iPad 用户首次体验 Android 平板:vivo Pad6 Pro - 少数派 数据逻辑强 - 少数派 极北行+ | 一路向北,探访日本至北之地 | 001 - 少数派 万字剖析:千问App深度体验报告(2026) - 少数派 在2026年,如何真正防止别人抄袭你的作品 - 少数派 怎么用 50 块搭个 AI 语音助手?我踩了 3 天坑 - 少数派 YeeroAI:让 AI 对话真正成为知识管理的一部分 - 少数派 爬泰山 - 少数派 「旅图显影」 App 更新:这次,我们补上了一点「手感」 - 少数派 假期出门太折磨?我的 23 条经验帮你规划惬意旅行 - 少数派 工作流会变吗 - 少数派 Claude Opus 4.6 怎么用最省钱?我测了 5 种方案 - 少数派 GPT Image 2 让图文并茂不再稀罕 - 少数派 用户侧出发——什么是AI,我要不要学习? - 少数派 找片、转存、整理、播放一条龙!让你的付费网盘值回票价 - 少数派 欢迎试用!日课一问2.0插件 - 少数派 自己做的MDeditor,原本想购买 Typora 试了两次支付不成功,干脆自己做一个 - 少数派 vibe coding了一个 3MB 的小工具,让 ~/Downloads 彻底告别混乱 - 少数派 因为受不了 Mac 的风扇策略,我做了一个风扇控制工具 - 少数派 别只怪模型 - 少数派 Warp 终端的 AI 功能怎么用?我测了一周的体验 - 少数派 AI 写代码老是出 bug?这 5 个配置我后悔没早知道 - 少数派 「新玩意」苹果出相机可能就这样:Sigma BF + 45mm F2.8 DG Contemporary - 少数派 一个面向2030年的AI操作系统是什么样子的:浅谈cola这款有灵魂的Agent - 少数派 别只看写代码 - 少数派 每天解决10个问题,还是一口气攻坚解决400个? - 少数派 AI 交易机器人怎么搭?我用 Claude 跑了一周实盘 - 少数派 Maptoposter Online:把你爱的城市画成艺术海报 - 少数派 Function Calling 怎么用?我测了 3 个模型发现差距真大 - 少数派 Legend Talk:我做了个 AI 圆桌,让 160 位思想家围着你的问题转 - 少数派 如何找到自己的蓝方?在小县城寻找压力测试 - 少数派 语音输入与软件接口|2026年聊AI时,我们都聊些什么(上) - 少数派 混动已经卖爆,纯电又来补刀——钛7闪充版简直“不讲武德” - 少数派 本月玩什么|朋友收藏、识质存在、沙罗周期 - 少数派 为什么要每天坚持输出? - 少数派 Claude API 挂了好几个小时,你的项目有备用方案吗? - 少数派 Function Calling 没你想的复杂——我用它做了个有点用的工具 - 少数派 登录系统立即播放视频或者图片音乐的软件 - 少数派 我为什么创建 FlipHTML5 下载工具 - 少数派 残局没电?多品牌外设电量统一管理软件EasyBluetooth已支持RTSS游戏内显示以及AIDA64 - 少数派 前往通义路的路 - 少数派 太好看了,媲美Sun的个人导航页,NAS部署星云门户 - 少数派 乌黑嘴唇“一键检测”上线了 - 少数派 派早报:Claude AI 接入多个创意软件生态、FILCO 生产方接手品牌等 - 少数派 【更新】BearCLI、Claude 连接器与 MCP 服务器 - 少数派 记了上千条流水,还是看不懂财务?我做了一个让 AI 读懂账本的工作台 - 少数派 MINI R56 升级原厂 Sport 模式 - 少数派 新玩意 | 一棵柠檬树(仿真版) - 少数派 Momenta的“物理AI”野望,需迈过“含摩量”这道关 - 少数派 网页直接投屏控制手机!NAS一键部署PandaScrcpy,流畅丝滑可远程。 - 少数派 众测|邀你一同探索随身 AI 硬件入口 YoooClaw C·ONE - 少数派 2050大会:分享时间是真诚 参会记 - 少数派 iPad 赋能电影创作:国内首部宣纸手绘长片《燃比娃》的幕后故事 - 少数派 AI的审美:我用 8 个大模型给 100 张旅行照片打分 - 少数派 普通人如何破圈?去参加一个本地协会 - 少数派 把极空间的图标全换了,主题DIY全攻略打造你的专属NAS桌面 - 少数派 电子便签墙,帮你实现便签自由 - 少数派 我如何用三个 CLI 工具取代文档创建需求 - 少数派 原来真的有人可以玩一辈子 - 少数派 社区速递 139 | 派友热议三月买了啥、复古单反尼康 Df 体验 - 少数派 06 作品的赏析与评价 - 少数派 TDS REVIEW|索尼 WF-1000XM6 降噪真无线耳机体验 - 少数派 35.98万起售的第二代腾势D9,我看重的不是堆料,而是不凑合 - 少数派 鼠须管 Squirrel 皮肤配置指北 - 少数派 从watch ultra2换到redmi watch6 - 少数派 派早报:阿里巴巴发布视频生成模型 HappyHorse 1.0 等 - 少数派 别迷信1M - 少数派 家人们天塌了!网盘“大封杀”,多个渠道多条路,NAS部署PanHub - 少数派 AI与人勾心斗角!NAS一键部署AI狼人杀,假日休闲必备。 - 少数派 电商必备!Comfyui工作流批量生图插件,一次生成12张!支持Nano banana pro模型 - 少数派 Comfyui工作流配置Gpt-image-2模型教程,0.03/张 - 少数派 OpenClaw第三方APi怎么配置?可使用Gpt-image-2模型 - 少数派 会员社区话题精选 Ep. 103 - 少数派
23. 描述符和设计模式 - 少数派
2023-08-19 · via 少数派
cover

Hi, 大家好,我是茶桁。

上一节课中,我们讲解了面向对象中的一些高阶应用,给大家介绍了一些魔术方法。并在最后,我们预告这节课内容会讲解描述符和设计模式。

好了,让我们开始吧。

描述符

这个玩意,怎么讲合适呢?这么说吧,当某一个类中,包含了三个魔术方法(__get__, __set__, __delete__)中的任意一个,或者全部都有时,那么这个类就被称为是「描述符类」。

描述符的作用就是对一个类中的某一个成员进行一个详细的惯例操作,包括获取、赋值以及删除。也就是,描述符代理了一个类中的成员的操作,描述符属于类,只能定义为类的属性。

魔术方法咱们前面有提到,这里让咱们先来看看三个特殊的魔术方法,:

  1. __get__(self, instance, owner)

触发机制:在访问对象成员属性时自动触发(当该成员已经交给描述符管理时)
作用:设置当前属性获取的值
参数:1. self 描述符对象 2.被管理成员的类的对象。3.被管理成员的类
返回值:返回值作为成员属性获取的值
注意事项:无

  1. __set__(self, instance, value)

触发机制:在设置对象成员属性时自动触发(当该成员已经交给描述符管理时)
作用:对成员的赋值进行管理
参数:1. self 描述符对象 2.被管理成员的类的对象。3.要设置的值
**返回值:**无
注意事项:无

  1. __delete__(self, instance)

触发机制:在删除对象成员属性时自动触发(当该成员已经交给描述符管理时)
作用:对成员属性的删除进行管理
参数:1. self 描述符对象 2.被管理成员的类的对象。
返回值:无
注意事项:无

让我们先来看一个基本的类和实例化:

class Person():
    name = 'name'

# 实例化对象
zs = Person()
print(zs.name)

---
name

然后我们定义一个「描述符类」

# 定义描述符类
class PersonName():
    __name = 'abc'

    def __get__(self, instance, owner):
        pass

    def __set__(self, instance, value):
        pass

    def __delete__(self, instance):
        pass

接着我们重新更改一下刚才定义的普通类, 将其中的name成员属性交给刚定义的描述符类来实现:

# 定义的普通类
class Person():
    # 把类中的一个成员属性交给一个描述符类来实现
    name = PersonName()

这个时候我们实例化之后打印其中的成员属性会如何?

# 实例化对象
zs = Person()
print(zs.name)

---
None

我们可以看到,结果为None

现在让我们依次将类中的__get__方法的参数都打印出来观察一下:

# 修改其中的`__get__`方法
def __get__(self, instance, owner):
    print(self)
    print(instance)
    print(owner)
     
# 实例化后打印
print(zs.name)

---
<__main__.PersonName object at 0x108931d20>
<__main__.Person object at 0x108930250>
<class '__main__.Person'>
None

现在,具体self, instance, owner各自分别是什么,就非常清楚了。

那,既然selfPersonName类本身,那我们在其中定义的name成员属性是不是就可以拿到了?

# 定义描述符类
class PersonName():
    __name = 'abc'

    def __get__(self, instance, owner):
        return self.__name

    def __set__(self, instance, value):
        pass

    def __delete__(self, instance):
        pass

# 定义的普通类
class Person():
    # 把类中的一个成员属性交给一个描述符类来实现
    name = PersonName()

# 实例化对象
zs = Person()
print(zs.name)

---
abc

没错,我们确实拿到了PersonName中的__name

我们现在可以这么理解,普通类中的一个成员属性交给了一个描述符类来实现,类中的成员的值是另一个描述符类的对象, 那么当对这个类中的成员进行操作时,可以理解为就是对另一个对象的操作。现在的PersonName这个描述符类,相对于一个代理人的角色。把当前的描述符类赋值给了一个需要代理的类中的成员属性。

既然我们看到了get方法的结果之后,那么剩下两个魔术方法的作用也就很容易想到了:

# 定义描述符类
class PersonName():
  	...
    def __set__(self, instance, value):
        self.__name = value
    ...
# 定义的普通类
class Person():
  	...
    
# 实例化对象
...
zs.name = '张三丰'
print(zs.name)

---
abc
张三丰

这里容易理解吧?当我们执行zs.name = ‘张三丰’这个赋值操作的时候,其就是走到了__set__方法内。其中的self不言而喻,就是PersonName, 而value就是刚才我们进行赋值操作的那个值。这个时候,我们可以设置self.__name = value,那就是满足了这个赋值操作。当然,我们也可以不这样给,来让我们调戏一下这个赋值。

# 只改动`__set__`
def __set__(self, instance, value):
        # self.__name = value
        self.__name = '茶桁'

zs.name = '张三丰'
print(zs.name)

---
茶桁

当我们这样去改的时候,那么无论我们怎样去赋值,最终的结果都是打印出茶桁

那么__del__怎么用呢?我们接着看:

# 前面的代码都不做改动

del zs.name
print(zs.name)

---
茶桁
茶桁

那么第一个茶桁是刚才我们赋值后的打印结果,第二个茶桁呢?就是我们在执行del zs.name之后的打印结果。按道理来说,我们执行了del命令之后。zs这个对象的name成员已经被删除了,现在应该是打印出类中的原始值,也就是abc, 那为什么这里打印出来的还是茶桁呢?

原因就在于我们的__del__方法内没有任何操作。我们来改一下__del__内部:

# 只改动`__del__`
def __delete__(self, instance):
        # print('我就是不行删除,气死你')
        del self.__name

del zs.name
print(zs.name)

---
abc

这样我们就执行了del本该有的操作。不过大家也看到了,我中间有一段代码注释了,现在让我们替换一下注释:

# 只改动`__del__`
def __delete__(self, instance):
        print('我就是不行删除,气死你')
        # del self.__name

del zs.name

---
我就是不行删除,气死你

当我们执行del zs.name的时候,触发方法内的打印命令。那么,这个时候再让我们打印一下zs.name来看看:

print(zs.name)

---
茶桁

毫无意外的,茶桁还在,并没有变成abc

需要注意的是,同时具备三个魔术方法的类才是「数据描述符类」,没有同时具备三个魔术方法的类呢?很简单,就是「非数据描述符类」。两者的区别就是一个是完整的,一个是不完整的。可以不可以应用呢?部分可以,但是不完整,__get__, __set__, __delete__中总有某些功能无法实现。

一个描述符应用

了解了描述符的概念以及怎么使用之后,我们来试试实现一个应用:定义一个学生类,需要记录学员的id, 名字和分数。

让我们先来起一个框架:

class Student():
    def __init__(self, id, name, score):
        self.id = id
        self.name = name
        self.score = score
    def __repr__(self):
        return f'学员编号:{self.id}\n学员姓名:{self.name}\n学员分数:{self.score}' 
    
# 实例化对象
zs = Student(37, '张三丰', 98)
print(zs)

---
学员编号:37
学员姓名:张三丰
学员分数:98

这里,我们对这个方法有一个要求,就是学员的分数只能在0-100范围中, 那其实很简单了对吧?

我们先来看看第一种最普通的实现方法:

class Student():
    def __init__(self, id, name, score):
        self.id = id
        self.name = name
        # 检测分数范围
        if score >= 0 and score <= 100:
            self.score = score
        else:
            print('当前分数不符号要求。')
    def __repr__(self):
        return f'学员编号:{self.id}\n学员姓名:{self.name}\n学员分数:{self.score}' 
    
# 实例化对象
zs = Student(37, '张三丰', 101)
print(zs)

---
当前分数不符号要求。
AttributeError: 'Student' object has no attribute 'score'

尝试一下,确实打印了“分数不符合要求”,同时报错。

先不说怎么解决报错的问题,这简单的解决方案只能适用于对象初始化的时候有效。如果我们是中间单独对成员属性进行赋值,那么就会失效了

...
zs.score = -1
print(zs)

---
学员编号:37
学员姓名:张三丰
学员分数:-1

那这个时候,大家还记不记得咱们之前学过的一个魔术方法setattr? 我们来给中间加一个__setattr__

def __setattr__(self, key, value):
    # 检测是否给score进行赋值操作
    if key == 'score':
        print(key, value)
        # 检测分数范围
        if value >= 0 and value <= 100:
            object.__setattr__(self, key, value)    
        else:
            print('当前分数不符号要求。')
    else:
        object.__setattr__(self, key, value)

def __repr__(self):
    info = f'学员编号:{self.id}\n学员姓名:{self.name}\n学员分数:{self.score}' 
    return info

...
zs.score = -1

---
score -1
当前分数不符合要求

我们这样就使用__setattr__方法,检测如果score分数进行赋值时候,进行了分数的检测判断。

那我们在看,现在的一个问题是,假如学员的分数不止一个,我需要赋值多个分数怎么办?当前学员有:语文,数学,英语分数。

另外就是当前这个类中的代码是否比较繁杂?

现在,我们再来看,思考一下使用描述符来代理我们的分数这个成员属性。让我们先来实现一下框架:

class Score():
    __score = None
    def __get__(self, instance, owner):
        pass

    def __set__(self, instance, value):
        pass

    def __delete__(self, instance):
        del self.__score

class Student():
    score = Score()
    def __init__(self, id, name, score):
        self.id = id
        self.name = name
        self.score = score

    def returnSelf(self):
        info = f'学员编号:{self.id}\n学员姓名:{self.name}\n学员分数:{self.score}' 
        return info

框架就实现好了,我们将原始的普通类中的score代理给了描述符类Score()

那现在让我们来完善一下整个类中的方法。

首先,当我们进行获取的时候,直接return现有的值就可以了,当我们进行设置的时候,就需要进行判断,如果不符合要求就打印一个不符合要求。

# 定义描述符类,代理分数的管理
class Score():
    __score = None
    def __get__(self, instance, owner):
        return self.__score

    def __set__(self, instance, value):
        if value >= 0 and value <= 100:
            self.__score = value
        else:
            print('分数不符合要求')

    def __delete__(self, instance):
        del self.__score

class Student():
    score = Score()
    def __init__(self, id, name, score):
        self.id = id
        self.name = name
        self.score = score

    def returnSelf(self):
        info = f'学员编号:{self.id}\n学员姓名:{self.name}\n学员分数:{self.score}' 
        return info

让我们对其进行一下检测,看看是不是符合要求:

# 实例化对象
zs = Student(37, '张三丰', 132)
zs.returnSelf()

---
分数不符合要求
'学员编号:37\n学员姓名:张三丰\n学员分数:None'

没毛病,被告知了当前赋值不符合要求,并且最后分数上也为None,并未进行赋值。

在看看单独赋值:

zs.score = -1
zs.score = 88
zs.returnSelf()

---
分数不符合要求

'学员编号:37\n学员姓名:张三丰\n学员分数:88'

当赋值为-1的时候也是提示不符合要求,再次赋值88之后,正确赋值。然后我们打印出来的结果也正确。

那么我们的代理就正确的完成了它的工作。基本工作流程如下:

  1. 定义Score描述符类
  2. 把学生类中的score这个成员交给描述符类进行代理
  3. 只要在代理的描述符中对分数进行判断和赋值就可以了。

那么现在,我们就完成了一个描述符的应用案例。不知道大家是否都理解了?那么在下面,我给大家介绍一下描述符的三种定义格式:

  • 格式一: 通过定义描述符来实现(推荐)
class ScoreManage():
    def __get__(self, instance, owner):
        pass
    def __set__(self, instance, value):
        pass
    def __delete__(self, instance):
        pass

class Student():
    score = ScoreManage()
  • 格式二: 使用property函数来实现
class Student():
    def __init__(self, id, name, score):
        self.id = id
        self.name = name
        self._score = score

    def getScore(self):
        return self._score
    def setScore(self, score):
        self._score = score
    def delScore(self):
        del self._score

    # 在 property 函数中指定对应的三个方法
    # 对应的方法 1. `__get__`,2. `__set__`, 3. `__delete__`
    # 当然,名称不是固定的,也可以定义成其他的方法名
    # 不管定义成什么,`property`中的方法名必须一致。
    # 注意在类中将成员属性重新定义,可以为受保护的或者私有属性,避免递归调用。
    score = property(getscore,setscore,delscore)

  • 格式三:使用@property装饰器语法来实现
class Student():
    __score = None

    @property
    def score(self):
        print('get')
        return self.__score

    @score.setter
    def score(self,value):
        print('set')
        self.__score = value

    @score.deleter
    def score(self):
        print('delete')
        del self.__score

设计模式

我们谈设计模式的时候,实际上是一个比较抽象的东西。

设计模式,就是前人完成某个功能或者需求,根据经验和总结,对实现的代码步骤和代码设计进行了总结及归纳。成为了实现某个需求的经典模式。

设计模式可以说并不是什么固定的代码格式,而是一种面向对象编程的设计。

让我们先从单例开始。

单例(单态)设计模式

在当前脚本中,同一个类只能创建一个对象去使用,这种情况就称为单例(单态)。

我们以一个实际的思考案例来进行讲解,现在让我们来想:

单例和婚姻法的关系,特别像,就是一个人只能有一个结婚对象。在社会中是如何完成一夫一妻制的?如果想要结婚,必须要到民政局登记,民政局需要检测两个人的户口本,看看上面是否属于已婚的状态。如果是已婚,肯定就被撵出去了对吧。如果没有结婚,就可以盖章登记了。

那么按照这样的思路,我们又该如何去实现Python中的单例设计模式呢?来看哈:

需要一个方法,可以去控制当前对象的创建过程: 构建方法__new__

需要有一个标识来存储和表示是否有对象:创建一个私有属性进行存储,默认为None

在创建对象的方法中去检测和判断是否有对象: 如果没有对象,则创建对象,并且将对象存储起来,返回对象。那如果存储的是对象,则直接返回对象,就不需要创建新的对象了。

让我们依照这样一个思路来完成代码,让我们还是从框架开始:

class Demo():

    # 定义构造方法
    def __new__(cls, *args, **kwargs):
        return cls.obj

第一步我们完成了,现在让我们来看第二部,我们需要定义一个私有属性用于存储对象。

class Demo():
    
    # 定义私有属性存储对象
    __obj = None

    # 定义构造方法
    def __new__(cls, *args, **kwargs):
        return cls.__obj

接着,就要进入判断了:

class Demo():
    
    # 定义私有属性存储对象
    __obj = None

    # 定义构造方法
    def __new__(cls, *args, **kwargs):
        # 创建对象的过程中,判断是否有对象
        if not cls.__obj:
            # 如果没有,则创建,并且存储起来
            cls.__obj = object.__new__(cls)
        return cls.__obj

类完成了,让我们来证实一下看看,是否只会创建一个对象。

# 实例化对象
a = Demo()
b = Demo()
print(a)
print(b)

---
<__main__.Demo object at 0x10434e950>
<__main__.Demo object at 0x10434e950>

看到打印结果中,两次实例化对象不同,但是地址相同。可以证明确实为一个对象。

Mixin类

  • Mixin 必须是表示一种功能,而不是一个对象。
  • Mixin 的功能必须单一,如果有多个功能,那就多定义Mixin
  • python 中的Mixin是通过多继承实现的
  • Mixin 这个类通常不单独使用,而是混合到其它类中,去增加功能的
  • Mixin 类不依赖子类的实现,即便子类没有继承这个Mixin,子类也能正常运行,可能就是缺少了一些功能。。

那使用Mixin混入类有什么好处呢?

这个混入类的设计模式,在不对类的内容修改的前提下,扩展了类的功能。也提高代码的重用性,使的代码结构更加的简单清晰。可以根据开发需要任意调整功能(也就是创建新的Mixin混入类),避免设计多层次的复杂的继承关系。

我们之前学习继承,知道继承需要有一个必要的前提,就是继承应该是一个is-a的关系。

比如,苹果可以去继承水果,因为苹果is a水果, 那苹果是不能继承午饭的,因为午饭可以有苹果,也可以没有。

再比如,汽车可以继承交通工具,又是因为汽车本身is a交通工具。

遵循这样的一个规律,我们来思考,交通工具都有哪些呢?

汽车、飞机、直升飞机,这些都属于交通工具对吧?当然,高铁什么的也是,我们无法穷举出来,那样就太多了。

那么如何去设计这些类的关系呢?我们可以创建一个交通工具类,然后属于交通工具的都来继承,再去实现... 等等,我们再来思考一个问题:飞机、直升飞机都可以飞,可是汽车呢?汽车并不能飞行。那么交通工具中如果去定义飞行这个功能,是不是就不合适了?

你们现在是不是在想:那就在飞机和直升飞机类中分别实现飞行这个功能。可以是可以,但是重复代码是不是过多了?代码无法重用。

那该怎么办?其实,让我们分别去定义交通工具和飞行器这两个父类,这样飞机和直升飞机就可以去继承这两个类。对吧?

来,让我们开始实现,一样的,先来个框架

# 定义交通工具
class vehicle():
    # 运输货物
    def cargo():
        print('货物')
    
    # 搭载乘客
    def person():
        print('人')

# 定义飞行器
class flying():
    def fly(self):
        print('可以飞')

现在刚才的思考得以实现,我们定义了两个父类。接着是不是就要考虑继承了?

# 定义飞机
class airplane(vehicle, flying):
    pass

# 定义直升机
class helicopter(vehicle, flying):
    pass

# 定义汽车
class car(vehicle):
    pass

根据我们之前学习的继承关系,这样就完成了子类对父类的继承关系。来让我们分析下:

此时去定义一个飞行器的类Flying, 让需要飞行的交通工具直接继承这个类,可以解决问题。但是又两个问题,出现的类多继承,就违背了is-a, 飞行器这个类很容易被误解。那怎么办?

其实解决方案还是使用多继承,但是给飞行器这个类定义为一个Mixin混合类,此时就是等于把飞行器这个类,作为一个扩展的功能,来扩展其他类。

让我们来改一下:

# 定义交通工具
class vehicle():
    # 运输货物
    def cargo():
        print('货物')
    
    # 搭载乘客
    def person():
        print('人')

# 定义飞行器
class flyingMixin():
    def fly(self):
        print('可以飞')

# 定义飞机
class airplane(vehicle, flyingMixin):
    pass

# 定义直升机
class helicopter(vehicle, flyingMixin):
    pass

# 定义汽车
class car(vehicle):
    pass

嗯,你没看错,就是这么简单,改一下名称。

那么在这段代码中,虽然直升机和飞机都是用了多继承,也就是继承了flyingMixin,但是由于flyingMixin类加了Mixin这个名,就告诉了后面阅读代码的人,这个类是一个Mixin类。

我知道你们在想什么,这个是不是太随便了?其实并不是,我们目前在谈论的是「设计模式」,这个flyingMixin类中除了名称之外,还要遵循一些特定的惯例规则,就是这个类中的功能必须是单一的。

在名称的含义上,Mixin表示混入(mix-in), Mixin必须是表示一种功能,而不是一个对象。Mixin的功能必须单一,如果有多个功能,那就需要多定义几个Mixin类。在Python中的Mixin是通过多继承实现的。Mixin类通常不单独使用,而是混合到其他类中,去增加功能的。Mixin类不依赖子类的实现,即便子类没有继承这个Mixin类,子类也能正常运行,只是可能缺少一些功能。

抽象类

首先我们要明白,抽象类也是一个类。但是,这又是一个特殊的类,抽象类不能用,不能直接实例化称为一个对象。抽象类包含了抽象方法,抽象方法就是没有实现代码的方法。抽象类需要子类继承,并重写父类的抽象方法,才可以使用。

抽象类,一般应用在程序设计,程序设计中一般是要对功能和需求进行规划,其中有一些需求是明确的并且可以完成的,但是也可能会有一些需求是不明确的,或者不确定具体需要怎么实现,此时就可以把这个不确定怎么实现或者需要后面再去实现的方法,定义为抽象方法(只定义方法名,不写具体代码)。

我们还是拿一个实例来讲解:

比如公司有一项新的产品需要开发,交给了开发部门的大拿,也就是你。那么你就开始去规划设计怎么去完成这个产品的开发。比如项目需要用到不同的技术,不同的人来完成。这样,你作为老大,自己完成了一部分功能,但是依然有一部分定义了需求,但是还没有具体实现,需要其他人来进行实现。

那么此时,你已经写完的部分就是普通方法,定义了需求但是未完成的就可以理解为是抽象方法。

还是来直接看代码:

import abc

# 必须使用metaclass, 属性必须是abc.ABCMeta
class WriteCode(metaclass=abc.ABCMeta):

    # 需要抽象方法,使用装饰器进行装饰
    @abc.abstractmethod
    def write_swift(self):
        pass

    def write_java(self):
        print('实现了Java代码的开发')

    def write_python(self):
        print('实现了Python代码的开发')

这样我们就在一个抽象类中定义好了一个抽象方法,和几个普通方法。至于为什么必须metaclass=abc.ABCMeta,那就又要扩展着去讲了。这里先记住,就跟背单词一样,到这里了就这么用就可以了。

前面我们讲了,抽象类是不能直接实例化的,让我们试试看:

# 抽象类不能直接实例化对象
obj = WriteCode()

---
TypeError: Can't instantiate abstract class WriteCode with abstract method write_swift

报错了,直接告诉我们无法实例化抽象类。

那么我们到底要怎么用呢?

我们可以定义一个子类来继承,并实现抽象类中的抽象方法。

# 定义子类,继承抽象类,并实现抽象类中的抽象方法
class Demo(WriteCode):
    def write_swift(self):
        print('实现了swift代码的开发')

好了,现在让我们实例化子类试试:

obj = Demo()
print(obj)

---
<__main__.Demo object at 0x104ade8c0>

没有报错,似乎是完成了继承和实现。接着当然是一次执行子类中的方法来看看:

obj.write_java()
obj.write_python()
obj.write_swift()

---
实现了Java代码的开发
实现了Python代码的开发
实现了swift代码的开发

没毛病,现在我们完成了整个代码。

那小伙伴们现在估计最大的疑问是:抽象类我要应用在什么地方呢?

比如说,我们现在要开发一个框架,这个框架要有一大堆的功能,包括a,b,c(哎,我忽然理解导入的为什么是abc这样起名了)。但是呢,具体用这个框架开发什么样的产品我们并不清楚,因此这个框架中能否知道你要做什么样的开发吗?肯定不知道。

框架具备了一定的功能即可,剩下的,需要具体开发项目的人来实现自己的业务逻辑。

那这个时候,我们就要用到抽象类了。

好了,到目前为止,我们关于面向对象编程也就介绍的差不多了。而我们的Python课程基本上也到了尾声,在后面的课程中,我们会介绍一下Python中的装饰器。然后去学习一下几个用的特别广的库,包括matplotlib,numpy以及pandas

小伙伴们,大家对于此前的Python基础一定要好好的理解,好好练习。这样,越往后我们才能越轻松的开展后面的学习。

行,本节课到这里就结束了,下课。