面向对象的程序设计都三个主要的特征:封装,继承,多态,这个也是类里面的重要内容,这三个特征我会从今天开始依次开始写,今天咱们先来看第一个:「封装」,这一部分我会分两次来写,接下来进入正题。bash
对于「继承」的概念,咱们先来看在《维基百科》中的定义:函数
继承(Inheritance)是面向对象软件技术当中的一个概念。若是一个类别 A “继承”自另外一个类别 B,就把这个 A 称为 “B 的子类别”,而把 B 称为 “A 的父类别”,也能够称为 “B 是 A 的超类”。学习
「继承」可使得子类具备父类的各类属性和方法,而不须要再去重复编写相同的代码。在令子类继承父类的同时,能够从新去定义某些属性,并能够重写某些方法,简单点来讲就是覆盖掉父类的属性和方法,使其拥有和父类不一样的功能。此外,还能够为子类追加新的属性和方法,这都是些常见的作法。ui
因此由上面的描述咱们能够知道使用继承的好处:一是能够实现代码的重用,但又不只仅是代码重用;再者就是能够实现属性和方法的继承。固然了这个并非所有,随着咱们以后的学习,相信你对继承会有更深层次的了解。spa
若是你以前学过 Java 或者其它 OOP 语言的话,可能会对继承有不同的理解。其实在 Python 里,由于存在**「鸭子类型」(duck typing)** ,使得接口定义的重要性大大的下降,从而继承的做用也被进一步的削弱了。至于什么是鸭子类型,由于如今还有几个没有讲,因此这个我会在以后的文章单独拿出来讲。debug
再补充一点,若是你看过我前面的文章,你可能看到过我过全部的类都是 object 的子类,可是这里并无显式的把 object 写出来,而是用隐式的方法继承了 object。总而言之,object 就是全部类的父类。设计
所谓的单继承,就是只从一个父类那里继承。code
>>> class A:
... pass
...
>>> class B(A):
... pass
...
复制代码
上面的例子,类 A 是一个普通的类,类 B 则是定义的一个子类,它用 B(A) 的形式继承了类 A(父类),虽然这个父类里什么也没有。cdn
子类 B 继承父类 A 的方式就是在类名后面的括号里写上父类的类名。既然是继承了父类,那么根据咱们上面说的,父类的一切都带到了子类里。上面也说了,在 Python3 里全部的类都是 object 的子类,可是不用写出 object,可是对于继承其它的类就不能隐藏了,必需要显式的写上父类的类名。对象
还记得咱们前面的文章中写过的能够获得类的父类的那个特殊函数吗?咱们在这里能够用一下:
>>> A.__base__
<class 'object'>
>>> B.__base__
<class '__main__.A'>
复制代码
为了深刻的了解一下「继承」的做用,咱们下面让父类作点儿事情:
>>> class A:
... def __init__(self):
... print('my name is rocky')
...
>>> class B(A):
... pass
...
>>> b = B()
my name is rocky
复制代码
父类 A 中增长了初始化函数,而后子类 B 继承了它。咱们已经知道,当创建实例的时候,首先要执行类中的初始化函数,由于子类 B 继承了父类,就把父类中的初始化函数拿到了子类里面,因此在 b = B() 的时候,执行了父类中定义的初始化函数,这就是继承,并且是从一个父类那里继承来的,因此称为单继承。
下面咱们来看一个相对完整一些的例子:
class Person:
def __init__(self,name):
self.name = name
def height(self,m):
h = dict((['height',m],))
return h
def sex(self,se):
sex1 = se
return sex1
class Boy(Person):
def get_name(self):
return self.name
if __name__ == "__main__":
lee = Boy('rocky')
print(lee.get_name())
print(lee.height(170))
print(lee.sex('男'))
复制代码
运行上面的代码,所得的结果以下:
rocky
{'height': 170}
男
复制代码
首先咱们定义了一个 Person 类,而后定义了一个子类 Boy。
在子类 Boy 中只写了一个方法 get_name(),可是由于继承了 Person,那么 Boy 就拥有了 Person 中的所有方法和属性,在子类 Boy 的方法 get_name() 中,使用了属性 self.name,可是在类 Boy 中并无建立这个属性,只是由于其继承了 Person,在父类中有初始化函数。因此在使用子类建立实例的时候,必需要传一个参数,而后再调用方法。对于实例方法 lee.height(170) 也是由于继承的缘故。
在上面的程序中,子类 Boy 并无和父类有重复的属性和方法,但有时候会出现下面的这种状况:
class Boy(Person):
def __init__(self):
self.name = 'snow'
def get_name(self):
return self.name
复制代码
在子类里面也有一个初始化函数,而且定义了一个实例属性 self.name = 'snow',而在父类中也有初始化函数,在这种状况下运行之后会出现什么结果呢?你先猜一下,猜完了继续往下看:
TypeError: __init__() takes 1 positional argument but 2 were given
复制代码
嚯,居然报错了。报错的消息是建立实例的时候,传入的参数个数多了。这个的根源在于,子类 Boy 中的初始化函数只有一个 self,但由于跟父类的初始化函数重名,虽然继承了父类,可是将父类中的初始化函数覆盖掉了,因此如今的实例化子类不该该再显式的传参数。
if __name__ == "__main__":
lee = Boy()
print(lee.get_name())
print(lee.height(170))
print(lee.sex('男'))
复制代码
如此修改之后再运行,显示的结果以下:
snow
{'height': 170}
男
复制代码
从结果中咱们不难看出来,若是子类中的属性或者方法与父类同名,那么就再也不继承父类的该属性或方法。
咱们昨天说过,若是子类里有和父类一样名称的方法和属性,那么父类相应的部分再也不被继承到子类。那么若是有这么一种状况,若是子类还想继续使用父类的该方法,那么该怎么办?咱们能够对子类作以下修改,例子仍是昨天的那个例子:
class Boy(Person):
def __init__(self,name):
Person.__init__(self,name)
self.real_name = 'snow'
def get_name(self):
return self.name
复制代码
请仔细观察 Boy 的初始化方法,与昨天的例子有所不一样,为了能继续在子类中使用父类的初始化方法,以类方法的方式调用 Person.init(self,name),此外在子类的初始化方法的参数中,要增长相应的参数 name。
接下来让咱们实例化子类:
if __name__ == "__main__":
lee = Boy('rocky')
print(lee.real_name)
print(lee.get_name())
复制代码
此刻的运行结果估计你也能猜出来:
snow
rocky
复制代码
这样使父类中被覆盖的方法能再次在子类中使用。可是上述的方式有一个问题,那就是若是父类的名称由于某种玄学的方式被改变了,子类所对应的父类的类名也要改,若是你忘记了改,就会出现异常,程序少还能够,程序若是特别多的话,你可能会忘记一两个地方,而后就苦逼的 debug 了。
你想咱们号称简洁优雅的 Python 怎么可能会容忍这么费事的东西存在呢,因而 Python 创了一个很是巧妙的方法 -- super
!
class Boy(Person):
def __init__(self,name):
#Person.__init__(self,name)
super(Boy,self).__init__(name)
self.real_name = 'snow'
def get_name(self):
return self.name
复制代码
在上面的代码中咱们只修改了一处,运行程序后显示的结果和之前同样,固然关于 super 还有一些别的用法,在这不作深究,感兴趣的能够自行 Google。
昨天的文章中咱们学了单继承,所谓单继承就是只从一个父类那继承,推展开来,多重继承就是继承能够来自多个父类,即一个子类的父类不止一个。请看下面的例子:
class Person:
def hands(self):
print('two hands')
def hair(self,color):
print('The hair color is: ',color)
class Boy:
age = 23
def name(self):
print('The boy name is rocky')
class Children(Person,Boy):
pass
if __name__ == "__main__":
lee = Children()
lee.hands()
lee.hair('black')
lee.name()
print(lee.age)
复制代码
在上面的程序中,前面有两个类分别是 Person 和 Boy,而后第三个类是 Children,它继承了前面的两个类,继承的方法和单继承差很少,也是在类名后面的括号里把要继承的两个类的类名写上。接着我实例化了类 Children,既然它继承了上面的两个类,那么上面两个父类的方法就能够拿来用。
运行一下程序,结果以下所示:
two hands
The hair color is: black
The boy name is rocky
23
复制代码
由上面的几个例子咱们已经能很清楚的知道继承的特色,接下来咱们还有必要了解一下多重继承的顺序问题。在这里有一个小问题:若是一个子类继承了两个父类,而且两个父类中都有相同的属性或者方法,那么实例化以后的子类所调用的该属性或方法是属于哪一个父类的呢?下面就让咱们来试一下:
class P1:
def name(self):
print('name is P1')
class P2:
def name(self):
print('name is P2')
def age(self):
print('P2 age is 23')
class Q1(P1,P2):
pass
class Q2(P1,P2):
def age(self):
print('Q2 age is 110')
class A(Q1,Q2):
pass
if __name__ == "__main__":
print(A.__mro__)
cla = A()
cla.age()
cla.name()
复制代码
上述程序的运行结果以下:
(<class '__main__.A'>, <class '__main__.Q1'>, <class '__main__.Q2'>, <class '__main__.P1'>, <class '__main__.P2'>, <class 'object'>)
Q2 age is 110
name is P1
复制代码
其中 print(A.mro) 能够打印出类的继承顺序。若是要执行 age() 方法,首先要在 Q1 类中查看有没有这种方法,若是没有,再看 Q2 类,若是仍是没有的话再看 Q1 类所继承的 P1 类,找到后就执行该方法,一样 name() 也是按照这样的顺序,在 Q2 类中就找到了该方法。
上述的这种对继承属性和方法的搜索叫作「广度优先搜索」,至于为何是这样,感兴趣的能够去 Google 一下。
更多内容,欢迎关注公众号「Python空间」,期待和你的交流。