第十二讲
一、封装
封装是指将数据与具体操作的实现代码放在某个对象内部,使这些代码的实现细节不被外界发现,外界只能通过接口使用该对象,而不能通过任何形式修改对象内部实现,正式由于封装机制,程序在使用某一对象时不需要关心该对象的数据结构细节及实现操作的方法。
看图理解一下:
我们在给实例化对象传递一些属性的时候,就是封装的一个过程:
二、继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类。继承机制的作用是实现了代码的复用,多个类公用的代码部分可以只在一个类中提供,而其他类也只需要继承这个类即可。
我们来回顾下,新式类的建立写法为:
class Demo(object):
pass
其实这里的object
是一个父类名,该类里面封装有很多方法,新建立的Demo类是可以调用其中的方法的,我们举个简单的例子来说明一下:
class Father(object):
def run(self):
pass
def eat(self):
pass
class Son(Father):
pass
Tom = Son()
Tom.run()
Tom.eat()
我们来看一下上面的例子,Father是父类,也叫基类,Son是子类,也叫派生类。在建立Son类的时候,括号里面写的是Father,如此一来就可以继承Father类里面的方法了,在实例化对象后我们可以使用,而不需要再在Son类中编辑一遍。
在python3中继承机制是有优先级的,一般来讲,子类在调用某个方法或变量的时候,首先在自己内部(最深)查找,如果没有找到,则开始根据继承机制在父类里面找,一层一层往上寻。例如下图中的Son类,就会现在自己内部查找方法,如果没有再去Father类寻找,如果Father类里面也没有就去GrandFather类里面寻找。
继承在使用python开发软件的时候经常使用,一般来说都是借助已有的框架来实现自己想要的功能,但是在具体操作中我们想要实现的确切功能有时候和父类的某一个方法不一致,或者说我们想要添加一些其它的功能,在保证不对源代码实现修改的前提下,我们就需要一些相关的操作来满足我们的需要,比如说我们重写一下某一方法,如:
class Father(object):
def run(self):
print('Father likes running')
class Son(Father):
def run(self):
print('Son likes running')
虽然Son类继承了Father类的方法,但是针对于run这个内部方法来说,输出的内容是不同的,因而在调用Son类内部的run方法的时候就会优先采用该类的内部方法,以上的操作就相当于我们重写了run这个方法。
当然,有的时候我们仅仅想针对于某个方法多添加一些功能(保留原方法的功能),但是不会选择去修改原函数,这个时候我们会使用super()函数或采取内部添加父类名的方式来增加一些功能,例如:
class Father(object):
def run(self):
print('Father likes running')
class Son(Father):
def run(self):
print('Son likes running')
super(Son,self).run() # super函数可以传入参数,格式为super(cls,self),cls为子类名,self为调用对象
Tom = Son()
Tom.run()
'''
输出结果为:
Son likes running
Father likes running
'''
以上输出结果表明,我们在执行完Son内部的run方法时,先是执行了我们想添加的新功能,即打印输入’Son likes running’,然后再执行了父类的原功能,即打印输出’Father likes running’。
需要注意的是,如果继承的方法需要额外参数的输入,那么run后面需要跟上参数。如:
class Father(object):
def test(self,a):
print(a)
class Son(Father):
def test(self,b,a):
print(b)
super().test(a)
A = Son()
A.test(1,2)
---------------------
结果:
1
2
super函数还可以简写:
class Father(object):
def run(self):
print('Father likes running')
class Son(Father):
def run(self):
print('Son likes running')
super().run() # 参数是可以省略的
Tom = Son()
Tom.run()
'''
输出结果为:
Son likes running
Father likes running
'''
我们也可以不采取super函数,利用父类名调用的方式:
class Father(object):
def run(self):
print('Father likes running')
class Son(Father):
def run(self):
print('Son likes running')
Father.run(self) # 这就是使用父类名直接调用的方式,需要注意的是一定要写入self,表明调用对象
Tom = Son()
Tom.run()
'''
输出结果为:
Son likes running
Father likes running
'''
注意:
- _ init _()方法同样是可以继承的,遵循深度优先继承机制;
- 私有属性和私有方法不可以被继承。
三、多继承
多继承,即子类有多个父类,并且具有它们的特征。这里我们主要关注的是继承顺序问题,因为有时候子类继承的某个方法在多个父类里面都有,那优先继承谁呢?有这么几个原则:
- 左侧优先;
- 左侧一条路走到黑;
- 左侧优先,根最后执行。
挨个来解释一下应用场景,第一个是左侧有限比如一个子类同时继承了多个父类:
class Father1(object):
def run(self):
pass
class Father2(object):
def run(self):
pass
class Son(Father1,Father2):
pass
Tom = Son()
Tom.run()
上面的子类Son继承了Father1、Father2两个父类,并且两个父类中都有run()这个方法,那么就会优先继承左边的,即Father1的run()方法。这是相对来说比较简单的情形。
如果说我们有多层继承关系,比如下图:
那么在调用sleep()方法的时候左侧一条路走到黑,即继承的是GrandFather类里面的sleep()方法。
另外一种情况如下图:
这种属于有一个共同的父类GrandFather,即“根”,当遇到这种情绪的时候,子类Son调用sleep方法就会优先调用Father1的sleep()方法,根放到最后执行。
后来的补充:
这里引一个案例:
class A(object):
def __init__(self):
print('A')
class C(A):
def __init__(self):
print('B')
super().__init__()
class B(A):
def __init__(self):
print('C')
super().__init__()
class D(B,C):
def __init__(self):
print('D')
super().__init__()
d = D()
-----------------------
结果:
D
C
B
A
以上结果是基于对菱形继承的演绎,首先是打印输出D,然后继承父类B的初始化方法,打印输出C,在这里出现了转折点,我们可以有这样一个理解,程序会对整体进行一个继承关系的判断,然后执行具体代码,因而在这里打印输出C之后,不会继续执行B类构造方法中的super继承,而是先去执行D类继承的C类构造方法,打印输出B。这里严格遵循这继承的顺序,我们可以通过打印输出print(D.__mro__)
来查看继承的顺序。
四、多态
在这里只简单的介绍,有个概念即可,在后期课程会详细讲解。
多态的概念是应用于Java和C#这一类强类类型语言中,而Python崇尚的是“鸭子类型”。动态语言调用实例方法时不检查类型,只要方法存在,参数正确,就可以调用,这就是动态语言的“鸭子类型”,它不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做鸭子。所谓的多态,定义时的类型和运行时的类型不一样,此时就成为多态。
这里查阅了一篇较为简洁的多态介绍,可以看一下