面向对象编程(OOP)是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含数据和操作数据的函数。
面向对象程序设计把计算机程序视为一组对象的集合,每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间的传递。
Python是一种面向对象的语言,对于Python而言,一切皆对象,都有的数据类型都被视为对象,也可以自定义对象。自定义的数据类型就是面向对象中的类的概念。
12.1 类 和 对象
类是设计图
对象是根据图纸造成的实物(如飞机)
也就是类是对象的定义,对应着一类具有共同特征的实物的抽象内容,对象是类的实例,也就是那些实物
1、类的定义与组成
类一般有三部分组成:
- 类名
- 类的属性
- 类的方法(函数)
(1)关于类的名字使用大驼峰法则来命名:
每个单词首字母全部大写 : HelloWorld
(2)类和实例的属性
实例属性:
- self.实例属性
- 通过 实例名.属性 名来访问实例属性
类属性(静态属性):不带self
- 可变的类属性(list、dict、set)可以通过实例来进行修改:实例名.属性名 重新赋值,这样也会修改类属性;
- 不可变得类属性(number、tuple、str)就只能通过 类名.属性名 来进行修改,用实例名.属性名只能修改该实例的属性值而不是修改类的属性值
(3)类的方法
类的方法其实就是函数,通过实例名.方法名()进行调用,注意一般要求方法的第一个参数都应该是self,指向实例化的对象,实质上就是实例对象在类内进行操作的分身,
class HelloWorld: #在Python3中默认继承object类
eye = 2 #类内属性,静态字段静态属性
def __init__(self,name,age): #类里面函数称为方法,第一个参数规范是self,self指向实例化对象,也就是对象在类里面统一称为self
self.name = name #实例属性
self.age = age
print('init——',self)
def hello(self):
return 'HelloWorld'
#类的实例化
hw = HelloWorld('hw','18') #init是初始化函数(构造函数),在实例化的同时调用,所以要传入相应的参数
print(hw)
从图片可以看到,实例化的对象和self在相同的内存中,说明两者是一个对象,所以self指向的是实例化的对象,也就是类的外部是实例化对象,类的内部其实是self在运作。
图片中我们看到对于不同类型的类属性的修改操作的特点,前面已经说过,这里不再赘述。
_ _ init_ _方法是一个比较特殊的方法,是Python中的构造方法,在实例化对象的时候调用,需要注意的是,一个类中可以有多个构造方法,但是最后一个构造方法会覆盖前面的,也就是只有最后一个会生效。
2、类的私有变量
其实对于Python来说没有私有这种说法,无私有,仅仅是一种约定而已,告诉其他看你程序的人别访问这个。私有的可能是属性也可能是方法。
关于私有变量的定义:
- _ 以单个下划线开头定义的变量会被继承,且只能允许其本身与子类进行访问,通过 对象名._变量名 就可以访问了。
- _ _ 以双下划线开头定义的变量不会被继承,相对于单个下划线就更私有了,不能通过 对象名 . _ _ 变量名访问了。这是需要 对象名._ 类名_ _ 属性名。注意对象名后面有点,类名后面没点。
- 所以我们可以看到,对于Python来说,所谓的私有其实并不是真正的私有,因为即便变量是私有的,依然有办法进行访问,这跟Java里面的private不同。这只是Python工程师间的约定,只要这样写就是不允许访问的,即便有方法可以访问的到。
而且我们可以在类内定义get、set方法来获取或者修改我们的私有变量,或者用来检查是否我们传入的参数是有效的,如果不符合要求就按照不满足要求的逻辑执行。
12.2 封装 、继承 、多态
1、封装
将类内的属性和行为以方法的形式进行封装,类外使用时就直接对象.方法()来进行调用,而且我们不需要知道函数内部的实现细节(包括函数传哪几个参数啊我们都不用管,我们调用的时候只需要调用不需要传参就好,因为传啥什么的已经在类内就封装好了);反之我们要是没有进行封装,我们在调用函数的时候,要传几个参数就得传几个参数。
也就是函数调用需要的那几个参数,类内是有的,那我们可以在类内的函数里把这些数据封装起来,然后直接调用就可以了。
2、继承
(1)单继承
我们可以通过继承机制来实现代码的重用。
子类可以继承获得父类的所有非私有的属性和方法。也就是在子类中不用在定义这些就可以直接使用啦。
在子类中可以重写父类的方法,也就是在子类中定义和父类相同的方法,这样就会覆盖掉父类的该方法,这就是方法的重写。
那重写以后我还能调用原来父类的方法吗?
答案是可以的:
super().eat() 这样还不用考虑原来父类方法的参数。
super()只能调用父类的方法或者属性,不能调用父类的父类的,如果有多个父类的话,要将想要访问的那个类的类名作为super的参数:super(类名,self).方法名 ,这样来调用指定类的父类的方法(注意这里返回的是这里的类名的父类的方法哦)。
B的父类是A,所以图片中最后返回的是A的fx函数运行结果,打印的是A。
(2)多继承
多继承是Python特有的,Java就只有单继承。
多继承就是一个类可以继承多个父类,可以获得多个父类的非私有的方法和属性,当该类调用一个方法的时候,先查找该类中是否含有此方法,如果不存在该方法的话就从左到右挨个去继承的多个父类中查找,在哪个父类中先找到就返回哪个父类中的该方法。
从上面两张图可以看出来,调用的时候是先在该类中进行查找,找不到的话就从左到右在父类中进行查找,也就是先广度再深度优先的原则:也就是先查第一个父类,然后查完该父类的整个一支,也就是第一个父类没有找到就去这个父类的父类中找,一直找到最上面的父类,如果第一个父类的这一支都没有找到再找第二个父类的一支,依次这样进行查找。
注意在向上找着找着发现有一个结点是和后面父类的一支共同的结点,就去查找下一个父类的一支,因为这个过程总的来说其实是广度优先。
如上图一个继承关系,当我们调用一个函数的时候,查找流程应该是:D——C——B——E——F——A
如果F没有继承A,那查找顺序就是:D——C——B——A——E——F
如果你实在想不通那就用下面语句进行继承树的打印看看吧,
对象.__class__.mro() #打印继承树(列表)
类.__bases__ #打印这个类的父类(元组)