X Tutup
>你们仍是属肉体的,因为在你们中间有嫉妒分争,这岂不是属乎肉体,照着世人的样子行吗?...我栽种了,亚波罗浇灌了,惟有神叫他生长。(1 CORINTHIANS 3:3,6) #类(5) ##继承 继承——OOP的三个特征:多态、继承、封装——是类的重要内容。 继承,也是人的贪欲。 在现实生活中,“继承”意味着一个人从另外一个人那里得到了一些什么,“继承”之后,自己就在所继承的方面省力气、不用劳神费心,能轻松得到。比如继承了万贯家产,就一夜之间变成富n代;如果继承了“革命先烈的光荣传统”,就红色? 当然,生活中的继承或许不那么严格,但是编程语言中的继承是有明确规定和稳定的预期结果的。 ###概念 >继承(Inheritance)是面向对象软 件技术当中的一个概念。如果一个类别A“继承自”另一个类别B,就把这个A称为“B的子类别”,而把B称为“A的父类别”,也可以称“B是A的超类”。 >继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。 (源自维基百科) 由上面对继承的表述,可以简单总结出继承的意图或者好处: - 可以实现代码重用,但不是仅仅实现代码重用,有时候根本就没有重用 - 实现属性和方法继承 诚然,以上也不是全部,随着后续学习,对继承的认识会更深刻,例如网友令狐虫持有这样的观点: >从技术上说,OOP里,继承最主要的用途是实现多态。对于多态而言,重要的是接口继承性,属性和行为是否存在继承性,这是不一定的。事实上,大量工程实践表明,重度的行为继承会导致系统过度复杂和臃肿,反而会降低灵活性。因此现在比较提倡的是基于接口的轻度继承理念。这种模型里因为父类(接口类)完全没有代码,因此根本谈不上什么代码复用了。 >在Python里,因为存在Duck Type,接口定义的重要性大大的降低,继承的作用也进一步的被削弱了。 >另外,从逻辑上说,继承的目的也不是为了复用代码,而是为了理顺关系。 他是大牛,或许读者感觉比较高深,没关系,随着你的实践经验的积累,你也能对这个问题有自己独到的见解。 或许你也要问我的观点是什么?我的观点就是:走着瞧!怎么理解?继续向下看,只有你先深入这个问题,才能跳到更高层看这个问题。小马过河的故事还记得吧?只有亲自走入河水中,才知道河水的深浅。 在Python 2 中,我们这样定义新式类: class NewStyle(object): pass 这就是典型的继承。这个类继承了`object`,`object`是所有类的父类。这种定义是从Python 2.2开始的,它解决了以往的类和类型的不统一的问题,自那以后,类就是一种数据类型了。 发展到Python 3,类的定义变为: class NewStyle: pass 不再显示地写出`object`,是因为Python 3中的所有类,都隐式地继承了`object`。 总而言之,`object`就是所有类的父类。 ###单继承 这是只从一个父类那里继承。 >>> class P(object): #Python 3: class P: pass >>> class C(P): pass 寥寥数“键”,就实现了继承。 类`P`是一个通常的类,只不过在Python的两个版本中,定义样式稍微不同罢了。类`C`(注意字母大写)则是定义的一个子类,它用`C(P)`的形式继承了类`P`——称之为父类——虽然父类什么也没有。 子类`C`继承父类`P`的方式就是在类名称后面的括号里面写上父类的名字,不管是Python的哪个版本。既然继承了父类,那么父类的一切都带入到了子类,所以在Python 2中就没有必要重复写`object`了,它已经通过父类`P`被继承到子类`C`了;Python 3中,要显式的写上父类的名字,除了`object`,它不会隐式继承任何其它类。 >>> C.__base__ 还记得类的一个特殊属性吗?由`C.__base__`可以得到类的父类。刚才的操作,就显示出类`C`的父类是`P`。 为了深入理解“继承”的作用,让父类做一点点事情。 >>> class P(object): #Python 3: class P: def __init__(self): print "I am a rich man." #Python 3: print("I am a rich man.") >>> class C(P): pass >>> c = C() I am a rich man. 父类`P`中增加了初始化函数,然后子类`C`继承它。我们已经熟知,当建立实例的时候,首先要执行类中的初始化函数。因为子类`C`继承了父类,就把父类中的初始化函数拿到了子类里面,所以在`c = C()`的时候,执行了父类中定义的初始化函数——这就是继承,而且是从一个父类那里继承来的,所以也称之为单继承。 看一个比较完成的程序示例。 #!/usr/bin/env python # coding=utf-8 class Person(object): #Python 3: class Person: def __init__(self, name): self.name = name def height(self, m): h = dict((["height", m],)) return h def breast(self, n): b = dict((["breast", n],)) return b class Girl(Person): def get_name(self): return self.name if __name__ == "__main__": cang = Girl("canglaoshi") print cang.get_name() #Python 3: print(cang.get_name()),下同,从略 print cang.height(160) print cang.breast(90) 上面这个程序,保存之后运行: canglaoshi {'height': 160} {'breast': 90} 对以上程序进行解释: 首先定义了一个类`Person`,把它作为父类。然后定义了一个子类`Girl`,继承了`Person`。 在子类`Girl`中,只写了一个方法`get_name()`,但是因为是继承了`Person`,那么`Girl`就全部拥有了`Person`中的方法和属性。子类`Girl`的方法`get_name()`中,使用了属性`self.name`,但是在类`Girl`中,并没有什么地方显示创建了这个属性,就是因为继承`Person`类,在父类中有初始化函数。所以,当使用子类创建实例的时候,必须传一个参数`cang = Girl("canglaoshi")`,然后调用实例方法`cang.get_name()`。对于实例方法`cang.height(160)`,也是因着继承的缘故使然。 在上面的程序中,子类`Girl`里面没有与父类`Person`重复的属性和方法,但有时候,会遇到这样的情况。 class Girl(Person): def __init__(self): self.name = "Aoi sola" def get_name(self): return self.name 在子类里面,也写了一个初始化函数,并且定义了一个实例属性`self.name = "Aoi sola"`。在父类中,也有初始化函数。在这种情况下,再次执行程序。 在Python 2中出现异常: TypeError: __init__() takes exactly 1 argument (2 given) Python 3中也有异常: TypeError: __init__() takes 1 positional argument but 2 were given 不管哪个版本中的异常信息,都告诉我们,创建实例的时候,传入的参数个数多了。根源在于,子类`Girl`中的初始化函数,只有一个`self`。因为跟父类中的初始化函数重名,虽然继承了父类,但是将父类中的初始化函数覆盖了,导致父类中的`__init__()`在子类中不再实现。所以,实例化子类,不应该再显式地传参数。 if __name__ == "__main__": cang = Girl() #不在显示地传参数 print cang.get_name() #Python 3: print(cang.get_name()),下同,从略 print cang.height(160) print cang.breast(90) 如此修改之后,再运行,则显示结果: Aoi sola {'height': 160} {'breast': 90} 从结果中不难看出,如果子类中的方法或属性覆盖了父类(即与父类同名),那么就不在继承父类的该方法或者属性。 像这样,子类`Girl`里面有与父类`Person`同样名称的方法和属性,也称之为对父类相应部分的重写。重写之后,父类的相应部分不再被继承到子类,没有重写的部分,在子类中依然被继承,从上面程序可以看出来此结果。 还有一种可能存在,就是重写之后,如果要在子类中继承父类中相应部分,怎么办? ##调用覆盖的方法 承接前面的问题和程序,可以对子类`Gril`做出这样的修改。 class Girl(Person): def __init__(self, name): Person.__init__(self, name) self.real_name = "Aoi sola" def get_name(self): return self.name 请读者注意观察`Girl`的初始化方法,与前面的有所不同。为了能够使用父类的初始化方法,以类方法的方式调用`Person.__init__(self, name)`。另外,在子类的`__init__()`的参数中,要增加相应的参数`name`。这样就回答了前面的问题。 实例化子类,以下面的方式运行程序: if __name__ == "__main__": cang = Girl("canglaoshi") print cang.real_name print cang.get_name() print cang.height(160) print cang.breast(90) 执行结果为: Aoi sola canglaoshi {'height': 160} {'breast': 90} 就这样,使用类方法的方式,将父类中被覆盖的方法再次在子类中实现。 但上述方式有一个问题,如果父类的名称因为某种目前你无法预料的原因修改了,子类中该父类的的名称也要修改,有如果程序比较复杂或者忘记了,就会出现异常。于是乎,就有了更巧妙的方法——`super`。再重写子类。 class Girl(Person): def __init__(self, name): #Person.__init__(self, name) super(Girl, self).__init__(name) self.real_name = "Aoi sola" def get_name(self): return self.name 仅仅修改一处,将`Person.__init__(self, name)`修改为`super(Girl, self).__init__(name)`。执行程序后,显示的结果与以前一样。 关于`super`,有人做了非常深入的研究,推荐读者阅读[《Python’s super() considered super! 》](https://rhettinger.wordpress.com/2011/05/26/super-considered-super/),文中已经探究了`super`的工作过程。读者如果要深入了解,可以阅读这篇文章。 ###多重继承 前面所说的继承,父类都只有一个。但,继承可以来自多个“父”,这就是多重继承。 所谓多重继承,就是指某一个子类的父类,不止一个,而是多个。比如: #!/usr/bin/env python # coding=utf-8 class Person(object): #Python 3: class Person: def eye(self): print "two eyes" #Python 3: print("two eyes"),下同,从略 def breast(self, n): print "The breast is: ",n class Girl(object): #Python 3: class Gril: age = 28 def color(self): print "The girl is white" class HotGirl(Person, Girl): pass if __name__ == "__main__": kong = HotGirl() kong.eye() kong.breast(90) kong.color() print kong.age 在这个程序中,前面有两个类`Person`和`Girl`,然后第三个类`HotGirl`继承了这两个类,注意观察继承方法,就是在类的名字后面的括号中把所继承的两个类的名字写上。但是第三个类中什么方法也没有。 然后实例化类`HotGirl`,既然继承了上面的两个类,那么那两个类的方法就都能够拿过来使用。保存程序,运行一下看看 $ python 20902.py two eyes The breast is: 90 The girl is white 28 值得注意的是,这次在类`Girl`中,有一个`age = 28`,在对HotGirl实例化之后,因为继承的原因,这个类属性也被继承到`HotGirl`中,因此通过实例属性`kong.age`一样能够得到该数据。 由上述两个实例,已经清楚看到了继承的特点,即将父类的方法和属性全部承接到子类中;如果子类重写了父类的方法,就使用子类的该方法,父类的被遮盖。 多重继承的顺序很必要了解。 比如,如果一个子类继承了两个父类,并且两个父类有同样的方法或者属性,那么在实例化子类后,调用那个方法或属性,是属于哪个父类的呢?造一个没有实际意义,纯粹为了解决这个问题的程序: #!/usr/bin/env python # coding=utf-8 class K1(object): #Python 3: class K1: def foo(self): print "K1-foo" #Python 3: print("K1-foo"),下同,从略 class K2(object): #Python 3: class K2: def foo(self): print "K2-foo" def bar(self): print "K2-bar" class J1(K1, K2): pass class J2(K1, K2): def bar(self): print "J2-bar" class C(J1, J2): pass if __name__ == "__main__": print C.__mro__ m = C() m.foo() m.bar() 这段代码,保存后运行: $ python 20904.py (, , , , , ) K1-foo J2-bar 代码中的`print C.__mro__`是要打印出类的继承顺序。从上面清晰看出来了。如果要执行`foo()`方法,首先看`J1`,没有,看`J2`,还没有,看`J1`里面的`K1`,有了,即C==>J1==>J2==>K1;`bar()`也是按照这个顺序,在`J2`中就找到了一个。 这种对继承属性和方法搜索的顺序称之为“广度优先”。 Python 2的新式类,以及Python 3中都是按照此顺序原则搜寻属性和方法的。 但是,在旧式类中,是按照“深度优先”的顺序的。因为后面读者也基本不用旧式类,所以不举例。如果读者愿意,可以自己模仿上面代码,探索旧式类的“深度优先”含义。 导致新式类和Python 3中继承顺序较旧式类有所变化,其原因是mro(Method Resolution Order)算法,读者对此若有兴趣,可以到网上搜索关于这个算法的内容进行了解。 ------ [总目录](./index.md)   |   [上节:类(4)](./238.md)   |   [下节:多态和封装](./211.md) 如果你认为有必要打赏我,请通过支付宝:**qiwsir@126.com**,不胜感激。
X Tutup