4.4 继承

继承是非常重要的,因为继承让我们能够延续以前的东西,比如“龙生龙、凤生凤、老鼠的儿子会打洞”是基因继承结果,除了生物方面的继承,在现实生活中,“继承”意味着一个人从另外一个人那里得到了一些什么,比如继承革命先烈的光荣传统等。总之,“继承”之后,自己就在所继承的方面省力气,不用劳神费心就能轻松得到。

但是,高级编程语言中的“继承”,跟通常理解的继承会有所不同。“继承”在高级编程语言中是一个非常重要的概念。虽然不用继承一样能够编写程序,但是,当我们追求程序的更高阶层时,继承的作用就显现出来了。

继承(Inheritance)是面向对象软件技术当中的一个概念。如果一个类别A“继承”自另一个类别B,就把这个A称为“B的子类别”,而把B称为“A的父类别”,也可以称“B是A的超类”。

继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。(源自维基百科)

由上面对继承的表述,简单总结出继承的意图或者好处:

(1)可以实现代码重用,但不是仅仅实现代码重用,有时候根本就没有重用。

(2)实现属性和方法继承。

诚然,以上也不是全部,随着后续学习,对继承的认识会更深刻。好友“令狐虫”曾经这样总结继承:

从技术上说,OOP里继承最主要的用途是实现多态。对于多态而言,重要的是接口继承性,属性和行为是否存在继承性,这是不一定的。事实上,大量工程实践表明,重度的行为继承会导致系统过度复杂和臃肿,反而会降低灵活性。因此现在比较提倡的是基于接口的轻度继承理念。这种模型里因为父类(接口类)完全没有代码,因此根本谈不上什么代码复用。

在Python里,因为存在Duck Type,接口定义的重要性大大降低,继承的作用也进一步被削弱了。

另外,从逻辑上说,继承的目的也不是为了复用代码,而是为了理顺关系。

或许读者感觉比较高深,没关系,随着你对实践经验的积累,也能对这个问题有自己独到的见解。

或许你也要问我的观点是什么,我的观点就是:走着瞧!怎么理解?继续向下看,只有你先深入这个问题,才能跳到更高层看这个问题。小马过河的故事还记得吧?只有亲自走入河水中,才知道河水的深浅。

对于Python中的继承,前面一直在使用,那就是我们写的类都是新式类,所有新式类都是继承自object类。不要忘记,新式类的一种写法:

  1. class NewStyle(object):
  2. pass

这就是典型的继承。

4.4.1 基本概念

在编辑器中把这些代码敲出来。

  1. #!/usr/bin/env python
  2. # coding=utf-8
  3.  
  4. __metaclass__ = type
  5.  
  6. class Person:
  7. def speak(self):
  8. print "I love you."
  9.  
  10. def setHeight(self):
  11. print "The height is: 1.60m."
  12.  
  13. def breast(self, n):
  14. print "My breast is: ",n
  15.  
  16. class Girl(Person):
  17. def setHeight(self):
  18. print "The height is:1.70m ."
  19.  
  20. if __name__ == "__main__":
  21. cang = Girl()
  22. cang.setHeight()
  23. cang.speak()
  24. cang.breast(90)

上面这个程序,保存之后运行:

  1. $ python 20901.py
  2. The height is:1.70m .
  3. I love you.
  4. My breast is: 90

对以上程序进行解释,从中体会继承的概念和方法。

首先定义了一个类Person,在这个类中定义了三个方法。注意,没有定义初始化函数,初始化函数在类中不是必须的。

然后又定义了一个类girl,这个类的名字后面的括号中是上一个类的名字,这就意味着girl继承了Person,girl是Person的子类,Person是girl的父类。

既然是继承了Person,那么girl就拥有了Person中的全部方法和属性(上面的例子没有列出属性)。但是,如果girl里面有一个和Person同样名称的方法,那么就把Person中的同一个方法遮盖住了,显示的是girl中的方法,这叫作方法的重写。

实例化类girl之后,执行实例方法cang.setHeight(),由于在类girl中重写了setHeight方法,那么Person中的那个方法就不显作用了,在这个实例方法中执行的是类girl中的setHeight方法。

虽然在类girl中没有看到speak方法,但是因为它继承了Person,所以cang.speak()就执行类Person中的方法。同理cang.breast(90),它们就好像是在类girl里面已经写了这两个方法一样。

4.4.2 多重继承

所谓多重继承就是指某一个类所继承的父类,不止一个,而是多个。比如:

  1. #!/usr/bin/env python
  2. # coding=utf-8
  3.  
  4. __metaclass__ = type
  5.  
  6. class Person:
  7. def eye(self):
  8. print "two eyes"
  9.  
  10. def breast(self, n):
  11. print "The breast is: ",n
  12.  
  13. class Girl:
  14. age = 28
  15. def color(self):
  16. print "The girl is white"
  17.  
  18. class HotGirl(Person, Girl):
  19. pass
  20.  
  21. if __name__ == "__main__":
  22. kong = HotGirl()
  23. kong.eye()
  24. kong.breast(90)
  25. kong.color()
  26. print kong.age

在这个程序中,前面有两个类:Person和girl,然后第三个类HotGirl继承了这两个类,注意观察继承方法,就是在类的名字后面的括号中把所继承的两个类的名字写上。但是第三个类中什么方法也没有。

然后实例化类HotGirl,既然继承了上面的两个类,那么那两个类的方法就都能够拿过来使用。保存程序,运行一下看看:

  1. $ python 20902.py
  2. two eyes
  3. The breast is: 90
  4. The girl is white
  5. 28

值得注意的是,在类girl中,有一个age=28,在对HotGirl实例化之后,因为继承的原因,这个类属性也被继承到HotGirl中,因此通过实例属性kong.age一样能够得到该数据。

所谓继承,听起来玄乎,实际上没有那么复杂,核心特征是将父类的方法和属性全部承接到子类中;如果子类重写了父类的方法,就使用子类的该方法,父类的方法被遮盖。

4.4.3 多重继承的顺序

学习多重继承的顺序很有必要。比如,如果一个类继承了两个父类,并且两个父类有同样的方法或者属性,那么在实例化子类后,调用哪个父类的方法和属性呢?编造一个没有实际意义,纯粹为了回答这个问题的程序,体会一下多重继承的顺序。

  1. #!/usr/bin/env python
  2. # coding=utf-8
  3.  
  4. class K1(object):
  5. def foo(self):
  6. print "K1-foo"
  7.  
  8. class K2(object):
  9. def foo(self):
  10. print "K2-foo"
  11. def bar(self):
  12. print "K2-bar"
  13.  
  14. class J1(K1, K2):
  15. pass
  16.  
  17. class J2(K1, K2):
  18. def bar(self):
  19. print "J2-bar"
  20.  
  21. class C(J1, J2):
  22. pass
  23.  
  24. if __name__ == "__main__":
  25. print C.__mro__
  26. m = C()
  27. m.foo()
  28. m.bar()

这段代码,保存后运行:

  1. $ python 20904.py
  2. (<class '__main__.C'>, <class '__main__.J1'>, <class '__main__.J2'>, <class '__main__.K1'>, <class '__main__.K2'>, <type 'object'>)
  3. K1-foo
  4. J2-bar

代码中的print C.mro是要打印出类的继承顺序。如果要执行foo()方法,首先看J1,没有,看J2,还没有,看J1里面的K1,有了就执行,即C==>J1==>J2==>K1;bar()也是按照这个顺序,在J2中就找到了一个。

这种对继承属性和方法搜索的顺序称之为“广度优先”。

在新式类中,以及python3.x的类中,都是按照“广度优先”原则搜寻属性和方法的。

但是,在旧式类中是按照“深度优先”的顺序的。因为旧式类很少被用到,所以不举例。读者可以自己模仿上面代码,探索旧式类的“深度优先”含义。

4.4.4 super函数

初始化函数的继承跟一般方法的继承还有点不同,可以看下面的例子:

  1. #!/usr/bin/env python
  2. # coding=utf-8
  3.  
  4. __metaclass__ = type
  5.  
  6. class Person:
  7. def __init__(self):
  8. self.height = 160
  9.  
  10. def about(self, name):
  11. print "{} is about {}".format(name, self.height)
  12.  
  13. class Girl(Person):
  14. def __init__(self):
  15. self.breast = 90
  16.  
  17. def about(self, name):
  18. print "{} is a hot girl, she is about {}, and her breast is {}".format(name, self.height, self.breast)
  19.  
  20. if __name__ == "__main__":
  21. cang = Girl()
  22. cang.about("canglaoshi")

在上面这段程序中,类girl继承了类Person。在类girl中,初始化设置了self.breast=90,由于继承了Person,按照前面的经验,Person的初始化函数中的self.height=160也应该被Girl所继承过来。然后在重写的about方法中调用self.height。

实例化类girl,并执行cang.about("canglaoshi"),试图打印出一句话canglaoshi is a hot girl,she is about 160,and her bereast is 90。

保存程序,运行之,看看结果是否如所愿。

  1. $ python 20903.py
  2. Traceback (most recent call last):
  3. File "20903.py", line 22, in <module>
  4. cang.about("canglaoshi")
  5. File "20903.py", line 18, in about
  6. print "{} is a hot girl, she is about {}, and her breast is {}".format(name, self.height, self.breast)
  7. AttributeError: 'Girl' object has no attribute 'height'

报错!

程序员有一句名言:不求最好,但求报错。

报错不是坏事,而是我们长经验的时候,是在告诉我们,那么做不对。

重要的是看报错信息,就是我们要打印的那句话出问题了,报错信息显示self.height是不存在的,也就是说类girl没有从Person中继承过来这个属性。

原因是什么?仔细观察类girl会发现,除了刚才强调的about方法重写了,init方法也被重写了。不要觉得它的名字模样奇怪,就不把它看作类中的方法(函数),它跟类Person中的init重名了,同样是重写了那个初始化函数,而girl中的init中根本就没有关于self.height的任何信息。

这就提出了一个问题:因为在子类中重写了某个方法之后,父类中同样的方法被遮盖了,那么如何再把父类的该方法调出来使用呢?纵然被遮盖了,应该还是存在的,不要浪费了呀。

Python中有这样一种被提倡的方法:super函数。

  1. #!/usr/bin/env python
  2. # coding=utf-8
  3.  
  4. __metaclass__ = type
  5.  
  6. class Person:
  7. def __init__(self):
  8. self.height = 160
  9.  
  10. def about(self, name):
  11. print "{} is about {}".format(name, self.height)
  12.  
  13. class Girl(Person):
  14. def __init__(self):
  15. super(Girl, self).__init__()
  16. self.breast = 90
  17.  
  18. def about(self, name):
  19. print "{} is a hot girl, she is about {}, and her breast is {}".format(name, self.height, self.breast)
  20. super(Girl, self).about(name)
  21.  
  22. if __name__ == "__main__":
  23. cang = Girl()
  24. cang.about("canglaoshi")

在子类中,init方法重写了,为了调用父类同方法,使用super(Girl,self).init()的方式。super函数的参数,第一个是当前子类的类名字,第二个是self,然后是点号,点号后面是所要调用的父类的方法。同样在子类重写的about方法中,也可以调用父类的about方法。

执行结果:

  1. $ python 20903.py
  2. canglaoshi is a hot girl, she is about 160, and her breast is 90
  3. canglaoshi is about 160

最后要注意:super函数仅仅适用于新式类。当然,你使用的一定是新式类,因为“喜新厌旧”是程序员的嗜好。

如果你用的是Python3.x,则使用super函数的形式稍微不同。怎么个不同呢?请你认真阅读下面的“拓展阅读”中的资料(不要责备我不说,我是在告诉你一种非常好的学习方法——看别人提出的问题和解答,因为那些问题也是你的问题)。