Python 学习笔记(09)面向对象高级

Python 学习笔记(09)面向对象高级

9.1 面向对象典型特征

大多数面向对象的编程语言中都具备四个典型特征:抽象、继承、封装以及多态

1) 抽象:就是忽略一个主题中与当前目标无关的方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。

过程抽象是指任何一个明确定义功能的操作都可被使用者看作单个的实体看待,尽管这个操作实际上可能由一系列更低级的操作来完成。数据抽象定义了数据类型和施加于该类型对象上的操作,并限定了对象的值,只能通过使用这些操作修改和观察。

2) 继承:这是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。

派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。这也体现了大自然中一般与特殊的关系。继承性很好地解决了软件的可重用性问题。

3) 封装:就是把过程和数据包围起来,对数据的访问只能通过已定义的接口。面向对象的计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。一旦定义了一个对象的特性,则有必要决定这些特性的可见性,即哪些特性对外部世界是可见的,哪些特性用于表示内部状态。

在这个阶段定义对象的接口。通常,应禁止直接访问一个对象的实际表示,而应通过操作接口访问对象,这称为信息隐藏。封装保证了模块具有较好的独立性,使得程序维护修改较为容易。对应用程序的修改仅限于类的内部,因而可以将应用程序修改带来的影响减少到最低限度。

4) 多态:是指允许不同类的对象对同一消息做出响应。比如同样的复制-粘贴操作,在字处理程序和绘图程序中有不同的效果。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好地解决了应用程序函数同名问题。

9.2 封装机制

简单来说,封装即为在设计类时,将一些属性以及方法隐藏在类的内部。如此在使用该类时,开发者无法直接以【类实例.属性名】的方式或者【类实例.方法名(参数)】的形式调用这些属性或方法,而只能用未隐藏的类方法间接操作这些隐藏的属性或方法。

注意:封装并不是将所有方法和属性隐藏起来,一定要留一些供操作的【接口】。例如操作电脑的鼠标键盘,人们实际使用时并不需要了解计算机内部如何运行。

9.2.1 封装优点

首先,封装保证了类内部数据结构的完整性。由于开发者无法直接看到类内的数据结构,只能使用类允许公开的数据,很好的避免了外部对于内部数据的影响,提高了程序的可维护性。

其次,对一个类实现良好的封装后,开发者只能使用提供的方法进行数据访问。只需要在这些暴露出来的方法中加入适当的控制逻辑即可轻易避免开发者对类中属性或方法的不合理操作。

最后,对类进行良好的封装可以提高代码的复用性。

9.2.2 封装实现

首先要确定面向对象的属性和方法的两种不同【属性】,即 public(公有)以及 private(私有)

public(公有):公有属性的类属性以及类方法,在类的外部、内部以及子类中都可以正常进行访问;

private(私有):私有属性的类属性以及类方法只能在类的内部进行使用。

在python中并没有像其他面向对象的编程语言一样提供了这两种关键字,而是使用下划线来区分:

1)默认情况下,python类中的属性以及方法都是公有的;

2)如果类中的属性以及方法的名称是由双下划线【__】开头且结尾没有下划线,则该属性或方法为私有;

3)如果类中的属性以及方法的名称是由单下划线【_】开头且结尾没有下划线,则该属性或方法也通常被视为私有属性或私有方法;

4)python类中还有以双下划线开头和结尾的类方法(构造函数),这些是python内部定义的用于内部调用。

9.2.3 python封装底层原理

实际上python的封装机制是基于其底层实现时,python改变了【__】开头的类属性和类方法的名称,因此类实例无法直接对其调用。

改变后的【__】开头的类属性和类方法的名称为【_类名__属性(方法)名】。

class FirstDemo():
    def __init__(self, string):
        self.__string = string
    
    @property
    def string(self):
        return self.__string
    
    @string.setter
    def string(self, string):
        self.__string = string
        
    def __display(self):
        print(self.__string)
        
if __name__ == "__main__":
    demo = FirstDemo("测试数据")
    demo.string = "二次测试数据"
    demo.__display()					# 报错,类中不含此名称的方法
    demo._FirstDemo__display()
    
Out:
二次测试数据

在该程序中,属性 string 被设为私有属性,只能通过公有方法 setter 以及 getter 进行操作,实际上只是将名字改为了【_FirstDemo__string】。

9.3 继承机制

继承机制通常用于创建和现有类功能相似的新类,又或者新类只是在子类的基础上添加了一些属性或方法。通过继承机制可以轻易实现类的重复使用。

在python中,实现继承的类称为【子类】,被继承的类称为【父类】、【基类】或【超类】。子类继承父类时,只需要在定义子类时,将父类名称(不限数量)放在子类后的括号中即可。

注意:如果某类没有显式指定继承自哪一个类,则默认继承 object 类。 object 类为python中除它自身外所有类的直接父类或间接父类。

此外【派生】与【继承】的说法指代的都是同一件事,只是从子类角度来说继承于父类,从父类角度来说派生出子类。

class SecondDemo_1():
    def say(self, string):
        self.string = string
        print(self.string)
        
class SecondDemo_2():
    def display(self):
        print("第二父类")
        
class SecondDemo_3(SecondDemo_1, SecondDemo_2):
    pass

if __name__ == "__main__":
    demo = SecondDemo_3()
    demo.say("第一父类")
    demo.display()
    
Out:
第一父类
第二父类

该程序中,虽然子类为空但由于其继承自两个父类,因此实际上子类包含了父类所有的属性和方法,包括私有属性以及私有方法。

9.3.1 多继承

事实上,大部分面向对象的编程语言都只支持单继承,即子类有且仅能有一个父类,而python却和C++一样拥有多继承机制。

多继承机制的问题在于容易使代码逻辑变得复杂混乱,例如当多个父类中包含同名的属性及方法。对于这种情况,python的判定标准为根据子类继承多个父类时这些父类的前后次序决定,即排在前面的父类中的属性和方法会覆盖排在后面的父类中的属性和方法。

class ThirdDemo_1():
    def __init__(self, string):
        self.string = string
        
    def say(self):
        print("第一父类:",self.string)
        
class ThirdDemo_2():
    def __init__(self, string):
        self.string = string
        
    def say(self):
        print("第二父类:",self.string)
        
class ThirdDemo_3(ThirdDemo_1, ThirdDemo_2):
    pass

if __name__ == "__main__":
    demo = ThirdDemo_3("第一父类")
    demo.string = "First"
    demo.say()
    
Out:
第一父类: First

当子类同时拥有两个父类时,第一个父类将第二个父类的同名方法覆盖,因此输出为第一父类。

虽然python在语法上支持多继承,但尽量不要使用。

9.3.2 MRO方法解析顺序

python中的类是支持继承的,一个类中的方法以及属性可能定义在当前类中,也有可能被定义在父类中。针对这种情况,当需要对类中方法和属性进行调用时,需要对当前类以及其父类进行搜索,以确定方法或属性的位置,而搜索的顺序就被称为【方法解析顺序】。

方法解析顺序(Method Resolution Order),简称MRO。对于只支持单继承的编程语言来说,只需要从当前类开始逐个检查父类即可。而对于多继承的编程语言来说,MRO会复杂一些。

实际上,python发展至今共有三种MRO算法:

1)从左到右采用深度优先搜索算法(DFS),也被称为旧式类的MRO;

2)python 2.2 之后,新式类在采用深度优先搜索算法的基础上对其进行了优化;

3)python 2.3 之后,对新式类采用了C3算法。

9.3.3 父类方法重写

子类继承于父类,则子类将拥有父类中的属性以及方法,通常情况下子类会在此基础上拓展一些新的类中属性和方法。

但同时也存在个别的类方法无法直接使用父类的原方法,需要对这部分方法进行修改,这时候就需要父类方法重写。

class FourthDemo_1():
    def say(self):
        print("交通工具都有四个轮子")
        
class FourthDemo_2(FourthDemo_1):
    def say(self):
        print("自行车只有两个轮子")
        
if __name__ == "__main__":
    demo = FourthDemo_2()
    demo.say()
    
Out:
自行车只有两个轮子

由于子类继承于父类,所以子类拥有父类的 say() 方法,但由于需要可以将其进行重写。重写,即覆盖,是指对类中已有方法的内部实现进行修改。那么需要如何调用被重写前的方法呢?

class FourthDemo_1():
    def say(self):
        print("交通工具都有四个轮子")
        
class FourthDemo_2(FourthDemo_1):
    def say(self):
        print("自行车只有两个轮子")
        
if __name__ == "__main__":
    demo = FourthDemo_2()
    FourthDemo_1.say(demo)
    
Out:
交通工具都有四个轮子

通过之前讲过的“未绑定方法”,即使用【类名.方法名】且手动为 self 属性赋值的方式可以实现调用原方法。同时也可以使用 super() 调用父类同名实例方法。

9.3.4 如何使用继承机制

python中有一个内置的类 object ,它是所有类型对象的基类,也是所有没有显示定义父类的类(包括自定义类)的基类。因此在编程过程过程中,如果想实现与某个内置类具有类似行为的类时,最好的办法就是创建这个内置类的子类。

【内置类型子类化】其实就是自定义一个新类,使其继承有类似行为的内置类型,通过重定义这个新类实现指定的功能。

9.3.5 super():调用父类构造方法

python作为一门支持多继承的编程语言,由于构造函数是特殊的实例方法,子类继承的多个父类的构造函数也会按照排列顺序优先选择排在最前面的父类。

class FifthDemo_1():
    def __init__(self, string):
        self.string = string
        
    def say(self):
        print(self.string)
        
class FifthDemo_2():
    def __init__(self, word):
        self.word = word
        
    def printStr(self):
        print(self.word)
        
class FifthDemo_3(FifthDemo_1, FifthDemo_2):
    pass

if __name__ == "__main__":
    demo = FifthDemo_3("第一父类")
    demo.say()

Out:
第一父类
if __name__ == "__main__":
    demo = FifthDemo_3("第一父类")
    demo.printStr()

Out:
AttributeError: 'FifthDemo_3' object has no attribute 'word'

该程序中,子类继承于两个父类,调用构造函数时选择的是第一父类的构造函数,在创建实例的同时需要为其传参。调用第二父类中实例方法出错的原因在于该实例方法需要获取 str 属性,但第一父类的构造函数覆盖了第二父类的构造函数,使得第二父类的构造函数没有被调用。

针对这种情况需要在子类中定义新的构造函数(重写父类的构造函数)。注意:在子类中定义构造函数必须在该函数中调用父类的构造函数。

class FifthDemo_3(FifthDemo_1, FifthDemo_2):
    def __init__(self, string, word):
        super().__init__(string)				# 涉及多继承时,super()只能用于调用第一直接父类的构造方法
        										# super(FifthDemo_3, self).__init__(string)
            									# FifthDemo_1.__init__(self, string)
        FifthDemo_2.__init__(self, word)
        
if __name__ == "__main__":
    demo = FifthDemo_3("第一父类", "第二父类")
    demo.say()
    demo.printStr()
    
Out:
第一父类
第二父类

使用super()的注意事项:

1)尽可能避免使用多继承,可以使用一些设计模式进行替代;

2)super() 的使用在类的层次结构中必须保持一致,即要么全部使用 super() ,要么全不用。混用 super() 和传统调用是一种混乱的写法;

3)如果代码需要同时对 python 2.x 和 python 3.x 进行兼容,在 python 3.x 中应当显示地继承自 object 。在 python 2.x 中,没有指定任何祖先的类都被认定为旧式类(在早期 python 中类并没有共同祖先 object,定义一个类但没有显示指定其祖先则这个类会被解释为旧式类;而在 python 3.x 中,不再保留旧式类的概念,没有继承其他类的类都隐式的继承 object);

4)调用父类时应当提前查看类的层次结构(__mro__ 属性查看有关类的MRO)。

9.4 多态机制

作为一种弱类型语言,python在使用变量时无需指定变量的具体数据类型,这使得同一变量可以被赋值不同的类对象。

多态是指一类事物具有不同的形态,即一个父类拥有各不相同的子类(交通工具类的汽车和自行车)。

要注意区分多态与多态性:多态性是指不同功能的函数具有相同的名称,可以使用一个函数名调用不同功能的函数。

9.4.1 多态的基本条件

类的多态条件需要满足两个前提条件,首先多态一定是发生在子类与父类之间,即类与类之间存在继承机制;其次要求子类存在对父类方法的重写。

class SixthDemo_1():
    def printStr(self):
        print("父类")

class SixthDemo_2(SixthDemo_1):
    def printStr(self):
        print("第一子类")
        
class SixthDemo_3(SixthDemo_1):
    def printStr(self):
        print("第二子类")
        
if __name__ == "__main__":
    demo = SixthDemo_1()
    demo.printStr()
    demo = SixthDemo_2()
    demo.printStr()
    demo = SixthDemo_3()
    demo.printStr()
    
Out:
父类
第一子类
第二子类

该程序中的两个子类皆继承于同一父类,各自对父类的实例方法进行了重写。由于变量a每次代表不同的类实例对象,在执行同名实例方法时调用的也是不同的类的实例方法。

9.4.2 鸭子类型

在程序设计中,鸭子类型是动态类型的一种风格,一个对象的语义不取决于其继承的类而是由当前的方法与属性所构成的集合决定的。

“一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟可以被称为鸭子”

意思是对当前对象的判定在于其实现了什么,而不在乎其本身的类型。

class SeventhDemo_1():
    def printStr(self, instance):
        instance.printStr()
        
class SeventhDemo_2():
    def printStr(self):
        print("父类")
        
class SeventhDemo_3(SeventhDemo_2):
    def printStr(self):
        print("第一子类")
        
class SeventhDemo_4(SeventhDemo_2):
    def printStr(self):
        print("第二子类")
        
if __name__ == "__main__":
    demo = SeventhDemo_1()
    demo.printStr(SeventhDemo_2())
    demo.printStr(SeventhDemo_3())
    demo.printStr(SeventhDemo_4())
    
Out:
父类
第一子类
第二子类

该程序通过给第一个类添加参数 instance,使得在传入不同类的实例时,将会调用实例所对应类的 printStr() 方法。

在鸭子类型中,能否使用某个对象实现功能不再取决于该对象所属的类,而是取决于该对象是否拥有需要的属性或方法,上述程序中此方法为 printStr()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值