百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

Python之面向对象:DRY原则与继承,如何更好地实现代码复用

off999 2024-12-07 15:56 17 浏览 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中还支持多继承,虽然一般不建议使用多继承,但是,关于多继承的简单使用,还是可以稍微介绍一下的,从而稍微理解相关的设计实现思路。所以,下一篇文章中,我们会简单聊下多继承。

感谢您的拨冗阅读!

相关推荐

固态硬盘如何安装(固态硬盘如何安装系统)

1、首先要在在机箱内找到固态硬盘安装的电源连接线,是从电脑的电源引出的一根线。形状是扁嘴形上面一般印着一个白色的“P4”2、然后要在主板上找固态硬盘的数据接口,用于数据输入输出,俗称SATA接口,再找...

windows怎么打开注册表(windows怎么打开注册表管理器)

方法一、直接打开注册表1、点击屏幕左下角的“开始”按钮,再点击“运行”;2、或者直接按Win键+R键,打开“运行”对话框;3、在“运行”输入框中输入“regedit”命令;4、这样就能够打开注册表编辑...

windows7安装windows10(windows7安装光盘下载)

在安装Win7时,出现提示“Windows无法安装到这个磁盘。这台计算机的硬件可能不支持启动到此磁盘。请确保在计算机的bios菜单中启用了磁盘的控制器。” 解决方法: 1.如果之前你做过BIOS设置,...

装机配置模拟器(装机配置模拟器教程)
装机配置模拟器(装机配置模拟器教程)

装机模拟器2好装机模拟器2装系统方法1.在游戏PC装机模拟器里,有时候我们修理好电脑之后,发现电脑没有安装操作系统,这时候应该先安装系统。2.第一步,点击PC装机模拟器游戏,登录游戏。3.第二步,进入游戏之后,找到需要没有安装操作系统的电脑...

2025-11-10 21:51 off999

电脑网络正常但是上不了网(网络正常但电脑无法上网)

分析如下1、首先检查网卡的问题,打开电脑后,打开电脑右下角的WiFi连接,然后从里面的网络和共享中心检查,打开网络和共享中心后,出现对话框,在对话框左侧上方找到更改适配器设置,单击左键打开,就可以发现...

vs2008安装包下载(vs2008下载官方下载)

vs2008是面向WindowsVista、Office2007、Web2.0的下一代开发工具,VS2008引入了250多个新特性,整合了对象、关系型数据、XML的访问方式,语言更加简洁。使用V...

怎么换系统win7(怎么换系统盘固态硬盘)
  • 怎么换系统win7(怎么换系统盘固态硬盘)
  • 怎么换系统win7(怎么换系统盘固态硬盘)
  • 怎么换系统win7(怎么换系统盘固态硬盘)
  • 怎么换系统win7(怎么换系统盘固态硬盘)
cad2018序列号(cad2018序列码)

AutoCAD2018序列号和密钥:序列号:356-72378422,666-69696969,667-98989898,400-45454545,066-66666666等密钥:001J1CA...

恢复出厂设置win7(恢复出厂设置win11)
  • 恢复出厂设置win7(恢复出厂设置win11)
  • 恢复出厂设置win7(恢复出厂设置win11)
  • 恢复出厂设置win7(恢复出厂设置win11)
  • 恢复出厂设置win7(恢复出厂设置win11)
ip检测网站(ip地址测试)

IP检测工具(IPNetChecker)V1.5.2是一个简易实用,功能强大的网络监控软件,使您可以检查互联网和局域网上的IP主机的网络状态。IP检测工具(IPNetChecker)V1.5....

云电脑app哪个好(手机云电脑app哪个最好)

答:以下是一些比较好的云电脑应用程序推荐:1.AnyDesk-支持Windows、MacOS、Linux、Android和iOS,可用于远程访问和控制PC或移动设备。2.Splashtop...

怎样注册邮箱163免费(怎样注册邮箱163免费账号)

一、工具:电脑(联网)、浏览器二、操作步骤:【1】打开浏览器,找到“163邮箱”,点击。【2】点击右边的“注册”。【3】网站默认注册手机号码邮箱,填写信息,点击“注册”。若不想泄漏手机号码或不想使用手...

微软surface pro 6(微软surface pro 6可以扩容吗)

SurfacePro6的接口包含:1个标准尺寸USB3.0端口,3.5mm耳机插孔,MiniDisplayPort,1个SurfaceConnect端口,Surface专业键盘盖端口,microSDX...

电源已接通未充电怎么回事(电源已接通未充电 真正解决办法)

原因分析:出现这样的原因有可能是长时间没有充电,导致电池的内部电量耗完后亏电严重,只是电脑充电的保护,不让过充而已,只要设置一下电池选项一般就可以解决问题了。解决方法:1、关机,拔下电源,拔出电池,...

华为云会议app下载(华为云会议下载)

 华为云会议可以在PC客户端或者手机客户端上一键发起立即会议,1秒创会。然后在会中选择企业通讯录中的人加入,系统会自动呼叫这些与会人,接听后即加入会议。ZOOM是一个云会议服务平台,为客户提...

取消回复欢迎 发表评论: