Python之面向对象:DRY原则与继承,如何更好地实现代码复用
off999 2024-12-07 15:56 19 浏览 0 评论
引言
面向对象有三大特性:封装、继承、多态。
在前面的几篇文章中,我们重点介绍了封装特性,同时稍微发散了一下,讲解了Python中对象的生命周期管理。
今天开始讲解面向对象的第二大特性——继承。
复制与复用 & DRY原则
在介绍Python中的继承之前,我们先来看一下勤奋的程序员与懒惰的程序员的区别,以下仅为个人观点,不喜可喷。
勤奋的程序员总是准点下班、很少加班;
懒惰的程序员总是忙忙碌碌、一直加班。
勤奋的程序员大多数时间在思考、然后才运指如飞;
懒惰的程序员似乎一直在噼噼啪啪、但时不时卡壳、停下来。
勤奋的程序员写的代码很少,言简意赅;
懒惰的程序员写的代码很多,长篇累牍。
当然,前面只是夸张的修辞,在某些血汗工厂里,勤奋的程序员也总要加班、始终有干不完的活。
其实,在我看来,两者的区别只在于是否践行DRY原则。
所谓的DRY原则,是Don't Repeat Yourself,即“不要重复自己”的意思。是软件开发中的一项很关键的原则,主旨在于减少系统中的代码冗余与信息重复。其基本思想在于,每一段知识(逻辑或者功能)都应该在系统中有且只有一个明确的表示。
遵循DRY原则的好处有很多,比如:
1、提高代码的可维护性,减少重复代码,修改某一个逻辑、功能时,只需要在一处修改,降低了出错的概率。
2、增加代码的可读性,没有重复代码,代码的结构更加清晰、易读。
3、降低代码冗余,减少了代码量,一定程度上可以优化资源的使用与性能提升。
4、逻辑清晰,简化错误排查与测试的工作量。
帕斯卡说,“人是一根会思考的芦苇”,践行DRY原则的关键在于思考。简单的复制、粘贴组合,其实是放弃了思考的表现。一个事情、一个功能如果要出现超过3次,就应该思考如何优化设计、如何实现代码的复用、如何降低人工成本提高自动化的程度。
所以面向对象、或者编程本身的难点或者说竞争优势,不在于代码的堆砌,因为那样做只会成为一个合格的“码农”;而在于写代码实现前的思考与设计,因为这样做才能称之为“工程师”。
之所以“离题万里”,来聊DRY原则,是因为在Python中,继承是实现代码复用、践行DRY原则的一个很好的实践(还有一个很好的实践,叫做函数重用,之前的文章中已经提到),只有理解了这个原则,才能更好地使用继承特性。
继承
说到继承,一定会涉及两个概念,一个是父类,一个是子类。在进入语法与代码的讲解之前,对继承的概念稍微再多说几句。
在不同的语境下,会有一些不同的表达方式,但是本质都是一样的。
1、子类继承父类的属性和方法。
2、父类是子类的泛化,子类是父类的特化,两者是一般与特殊的关系。
3、父类是对子类的抽象,子类是对父类的扩展。
在Python中如何使用继承呢,我们还是以打工人的代码为例,简化来说,打工人的属性有姓名、性别、年龄、薪资等,方法有上班打卡、工作、下班打卡。每个打工人的属性大部分都一样,上下班打卡也一样,但是工作方式会有所区别,则可以通过继承实现复用相同的属性和方法,仅重写不同的部分,代码如下:
class DaGongRen:
def __init__(self, name, gender, age, salary):
self.name = name
self.gender = gender
self.age = age
self.__salary = salary
def go_to_work(self):
print(f"打工人{self.name}上班打卡成功")
def get_off_work(self):
print(f"打工人{self.name}下班打卡成功")
def work(self):
print(f"打工人{self.name}在努力工作")
def __look_for_job(self):
print(f"打工人{self.name}在找新工作")
class Programmer(DaGongRen):
pass
if __name__ == '__main__':
print(DaGongRen.__bases__)
print(Programmer.__bases__)
zs = Programmer('张三', '女', 23, 8000)
zs.go_to_work()
zs.work()
zs.get_off_work()
print(zs.__dict__)
print(zs._DaGongRen__salary)
zs._DaGongRen__look_for_job()
执行结果:
从上面代码中可以看出:
1、继承的语法非常简单:class 子类名(父类名)即可,上面的Programmer类,直接完全继承了父类的属性及方法。
2、在Python 3中,一个自定义的类,如果没有写明继承的父类,则其父类为object,通过类的属性__bases__可以获取到一个类继承的所有父类(也称作基类),返回一个元组,可以看出,Python是支持多继承的。
3、有些教材或者大牛说,子类会把父类的所有非私有属性和方法继承下来,其实是不太严谨的,我们通过__dict__属性,可以看到,其实是继承了混淆后的私有属性的,而且我们通过混淆后的私有方法名,也是可以调用到对应的方法的。
在上面的例子中,我们的子类直接照搬了父类的所有属性和方法,但是,通常情况下,子类会在继承父类属性和方法的同时,重写父类的部分方法,或者新增一些方法,这就是扩展的部分。尤其重写父类的同名方法时,一定要注意,这是实现后续会介绍的面向对象的“多态”特性的一个常用的实现方式。
我们以具体的代码来看,子类重写及扩展父类的情况:
class DaGongRen:
def __init__(self, name, gender, age, salary):
self.name = name
self.gender = gender
self.age = age
self.__salary = salary
def go_to_work(self):
print(f"打工人{self.name}上班打卡成功")
def get_off_work(self):
print(f"打工人{self.name}下班打卡成功")
def work(self):
print(f"打工人{self.name}在努力工作")
def __look_for_job(self):
print(f"打工人{self.name}在找新工作")
class Programmer(DaGongRen):
# 程序员有工作使用的编程语言的属性,假如只有一种
def __init__(self, name, gender, age, salary, language):
self.language = language
self.name = name
self.gender = gender
self.age = age
self.__salary = salary
# super().__init__(name, gender, age, salary)
def work(self):
super().work()
print(f"程序员{self.name}在写{self.language}代码来实现需求")
if __name__ == '__main__':
zs = Programmer('张三', '女', 23, 8000, 'Java')
zs.work()
print(zs.__dict__)
执行结果:
需要注意的是:
1、一旦子类自定义了__init__魔法函数,则在实例化子类对象时,不会再调用父类的__init__方法函数。
2、在子类的方法中,可以通过super()的方式,调用父类中的同名方法。所以,在子类的__init__方法中,可以把通过调用父类的__init__方法,实现name、gender、age、salary属性的初始化,而不用将4个属性再写一遍。
3、需要区分的是,super是一个自定义类,而不是有些人所说的内置函数。
接下来,稍微介绍一下内置类super的使用。
super
首先看下super的帮助文档:
从文档中,可以看出:
1、super是一个内置类,显示继承自object。
2、super的使用场景是,用于在继承链路中,访问父类的属性或者方法。
3、无参的实例化super,相当于super(self.__class__, self)。
4、super(type, obj)可以在继承链上访问任意一个祖先类的方法。
以代码实例来看:
class DaGongRen:
def __init__(self, name, gender, age, salary):
self.name = name
self.gender = gender
self.age = age
self.__salary = salary
def work(self):
print(f"打工人{self.name}在努力工作")
class Programmer(DaGongRen):
def __init__(self, name, gender, age, salary, language):
super().__init__(name, gender, age, salary)
self.language = language
def work(self):
# 在这个继承关系中,等价于super().work()
super(self.__class__, self).work()
print(f"程序员{self.name}在写{self.language}代码来实现需求")
class SeniorProgrammer(Programmer):
def work(self):
print(f"高级程序员{self.name}在以更优雅的方式写{self.language}代码")
class Architect(SeniorProgrammer):
def work(self):
print(f"架构师{self.name}在进行架构设计")
def coding(self):
# 调用SeniorProgrammer的work方法
super(self.__class__, self).work()
def dgr_work(self):
super(Programmer, self).work()
if __name__ == '__main__':
zs = Architect('张三', '女', 23, 8000, 'Java')
zs.work()
zs.coding()
zs.dgr_work()
print(zs.__dict__)
print(Architect.__mro__)
执行结果:
从代码的执行中,可以看出:
1、super(cls, self)可以调用cls的父类的方法或属性,所以在一条继承连路上,子类可以向上溯源,调取任意一个层级的父类的方法或属性。
2、通过类属性__mro__可以查看继承链路中的方法解析熟悉(Method Resolution Order, MRO),也可以通过mro()方法来查看类的MRO。这里只是简单看一下,其实这个顺序就是在子类对象调用一个方法时,在继承路径上的查找顺序,找到了就停止,否则就一直按顺序查找。
type & isinstance & issubclass
从上面super内置类的定义中,可以看到,super()实例化的参数,需要符合一定的条件(isinstacne、issubclass),否则会报错。
接下来,通过代码来看下type、isinstance和issubclass的使用:
if __name__ == '__main__':
zs = Architect('张三', '女', 23, 8000, 'Java')
print(Architect.__mro__)
# 对象与类的关系
print(type(zs) == Architect)
print(isinstance(zs, Architect))
# 对象与父类的关系
print(type(zs) == SeniorProgrammer)
print(isinstance(zs, SeniorProgrammer))
# 类间的关系
print(issubclass(Architect, object))
print(issubclass(Architect, SeniorProgrammer))
执行结果:
从代码的执行结果可以得出如下结论:
1、type(obj) == cls的比较,不考虑继承关系的,所以,跟类对象比较是True,而跟父类对象比较则是False。
2、isinstance(obj, cls),则会考虑继承关系,实例对象与任何一个父类对象比较,均会返回True。
3、issubclass(cls1, cls2),适用于比较一个类对象cls1是否是cls2的子类,可以是间接子类。
总结
今天的文章中,以DRY原则及关于代码的复用,引入关于Python中继承的使用的介绍。
其实,继承可以理解是用功能增强的点(.)运算符实现的。具体来讲,如果搜索一个属性时未在实例或实例的类中找到匹配项,将会继续搜索基类。这个过程会一直继续下去,直到没有更多的基类可供搜索为止。
此外,Python中还支持多继承,虽然一般不建议使用多继承,但是,关于多继承的简单使用,还是可以稍微介绍一下的,从而稍微理解相关的设计实现思路。所以,下一篇文章中,我们会简单聊下多继承。
感谢您的拨冗阅读!
相关推荐
- win98安装教程(win98iso怎么安装)
-
如何安装windows98 一、具体安装步骤 备份好重要文件之后,就可以安装windows98了。 第一步:启动安装程序。 用户如果原来已安装了windows95/97/98,现在拟对其进行升...
- 雨林木风win7安装(雨林木风win732位安装教程)
-
安装步骤如下: 1、光盘放入光驱,复制光盘上的win7.gho和安装系统.exe到硬盘非C盘的文件夹;(gho文件名可以是其他名字,后缀为gho,体积最大的就是。) 2、双击安装系统.exe;...
- win10解绑管理员账户(win10管理员账户怎么取消开机密码)
-
要解除Windows10电脑上的管理员权限,您需要进行以下操作:1.打开“控制面板”:右键单击“开始”按钮,然后选择“控制面板”。2.进入“用户账户”:在控制面板中,选择“用户账户”。3.点击...
- win10家庭版没有组策略编辑器
-
Win10组策略编辑器找不到怎么办 解决方法 一、win10系统版本本身不提供组策略的功能。 1、运行gpedit.msc直接提示找到gpedit.msc(组策略)。 2、运行MMC,在“添加...
- tplogin管理员登录入口(tplogin重新设置密码)
-
tplogin.cn是新版tplink路由器的登录地址(管理页面地址),在浏览器中输入tplogin.cn,就可以打开tplink路由器的管理页面(登录页面)。具体的登录方法如下:1、打开电脑上的浏...
- psp模拟器怎么导入游戏(psp模拟器怎么导入游戏 Vivo手机)
-
方法如下:1、打开能操作文件的助手软件,用pp链接后点击左下文件,然后点常用目录下的程序用户,会出现ppsspp的文件夹。2、打开ppsspp文件夹,会出来四个选项文件夹,第一个进去后是psp文件夹,...
- 电脑系统怎样升级(电脑系统怎么升级)
-
电脑系统升级方法步骤,1、打开电脑,点击电脑左下角的开始菜单,在弹出的菜单选项中选择“控制面板”。2、点击“开始”,点击“控制面板”3、在控制面板中,点击“系统和安全”。4、点击启用或禁用自动更新。5...
- windows无法激活(windows无法激活有什么影响)
-
1.如果修复或重新组装了电脑,则可能是安装了不同版本的Windows。或者,如果在修复过程中为电脑使用了其他产品密钥,当使用该密钥的电脑数大于Microsoft软件许可条款允许的电脑数时,该密钥...
-
- u盘文件恢复软件免费(恢复u盘数据免费的软件)
-
u盘损坏文件恢复方法:1、打开电脑桌面的“计算机”或“我的电脑”。2、然后再找到需要修复的u盘。3、打开“运行”窗口(可以直接按“Windows+R”快捷打开),输入“CMD”并点击“确定”按钮以进入命令提符界面。4、从打开的“命令提示符”...
-
2025-12-28 22:03 off999
- 电脑uac是什么意思
-
UAC就是用户帐户控制,在对计算机进行更改之前,用户帐户控制(UAC)会通知您。比如安装软件驱动什么的,默认UAC设置会在程序尝试对计算机进行更改时通知您,但您可以通过调整设置来控制UAC...
- 笔记本找不到自己家的wifi怎么办
-
1.笔记本电脑缺少无线网卡驱动,需要下载驱动如果笔记本电脑开机之后,无法显示WiFi网络的图标,这个时候多半是因为电脑缺少无线网卡驱动造成的,有时候自己在清理电脑的时候,不小心清理了驱动程序,便会...
- 电信宽带办理电话是多少(电信宽带办理联系电话)
-
电信宽带不一定需要电信手机号码,可以根据自身需要选择,有单独的宽带业务,一般要求预存一定时间的使用费。不过一般包含了宽带、手机号码的融合套餐总体上更优惠,对客户来说更划算。如果有相应需求的话,建议同时...
欢迎 你 发表评论:
- 一周热门
-
-
抖音上好看的小姐姐,Python给你都下载了
-
全网最简单易懂!495页Python漫画教程,高清PDF版免费下载
-
Python 3.14 的 UUIDv6/v7/v8 上新,别再用 uuid4 () 啦!
-
飞牛NAS部署TVGate Docker项目,实现内网一键转发、代理、jx
-
python入门到脱坑 输入与输出—str()函数
-
宝塔面板如何添加免费waf防火墙?(宝塔面板开启https)
-
Python三目运算基础与进阶_python三目运算符判断三个变量
-
(新版)Python 分布式爬虫与 JS 逆向进阶实战吾爱分享
-
失业程序员复习python笔记——条件与循环
-
系统u盘安装(win11系统u盘安装)
-
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)
