|
| 1 | +>你们仍是属肉体的,因为在你们中间有嫉妒分争,这岂不是属乎肉体,照着世人的样子行吗?...我栽种了,亚波罗浇灌了,惟有神叫他生长。(1 CORINTHIANS 3:3,6) |
| 2 | +
|
| 3 | +#类(4) |
| 4 | + |
| 5 | +本节介绍类中一个非常重要的东西——继承,其实也没有那么重要,只是听起来似乎有点让初学者晕头转向,然后就感觉它属于很高级的东西,真是情况如何?学了之后你自然有感受。 |
| 6 | + |
| 7 | +在现实生活中,“继承”意味着一个人从另外一个人那里得到了一些什么,比如“继承革命先烈的光荣传统”、“某人继承他老爹的万贯家产”等。总之,“继承”之后,自己就在所继承的方面省力气、不用劳神费心,能轻松得到,比如继承了万贯家产,自己就一夜之间变成富豪。如果继承了“革命先烈的光荣传统”,自己是不是一下就变成革命者呢? |
| 8 | + |
| 9 | +当然,生活中的继承或许不那么严格,但是编程语言中的继承是有明确规定和稳定的预期结果的。 |
| 10 | + |
| 11 | +>继承(Inheritance)是面向对象软 件技术当中的一个概念。如果一个类别A“继承自”另一个类别B,就把这个A称为“B的子类别”,而把B称为“A的父类别”,也可以称“B是A的超类”。 |
| 12 | +
|
| 13 | +>继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。 (源自维基百科) |
| 14 | +
|
| 15 | +由上面对继承的表述,可以简单总结出继承的意图或者好处: |
| 16 | + |
| 17 | +- 可以实现代码重用,但不是仅仅实现代码重用,有时候根本就没有重用 |
| 18 | +- 实现属性和方法继承 |
| 19 | + |
| 20 | +诚然,以上也不是全部,随着后续学习,对继承的认识会更深刻。好友令狐虫曾经这样总结继承: |
| 21 | + |
| 22 | +>从技术上说,OOP里,继承最主要的用途是实现多态。对于多态而言,重要的是接口继承性,属性和行为是否存在继承性,这是不一定的。事实上,大量工程实践表明,重度的行为继承会导致系统过度复杂和臃肿,反而会降低灵活性。因此现在比较提倡的是基于接口的轻度继承理念。这种模型里因为父类(接口类)完全没有代码,因此根本谈不上什么代码复用了。 |
| 23 | +
|
| 24 | +>在Python里,因为存在Duck Type,接口定义的重要性大大的降低,继承的作用也进一步的被削弱了。 |
| 25 | +
|
| 26 | +>另外,从逻辑上说,继承的目的也不是为了复用代码,而是为了理顺关系。 |
| 27 | +
|
| 28 | +他是大牛,或许读者感觉比较高深,没关系,随着你的实践经验的积累,你也能对这个问题有自己独到的见解。 |
| 29 | + |
| 30 | +或许你也要问我的观点是什么?我的观点就是:走着瞧!怎么理解?继续向下看,只有你先深入这个问题,才能跳到更高层看这个问题。小马过河的故事还记得吧?只有亲自走入河水中,才知道河水的深浅。 |
| 31 | + |
| 32 | +对于python中的继承,前面一直在使用,那就是我们写的类都是新式类,所有新式类都是继承自object类。不要忘记,新式类的一种写法: |
| 33 | + |
| 34 | + class NewStyle(object): |
| 35 | + pass |
| 36 | + |
| 37 | +这就是典型的继承。 |
| 38 | + |
| 39 | +##基本概念 |
| 40 | + |
| 41 | + #!/usr/bin/env python |
| 42 | + # coding=utf-8 |
| 43 | + |
| 44 | + __metaclass__ = type |
| 45 | + |
| 46 | + class Person: |
| 47 | + def speak(self): |
| 48 | + print "I love you." |
| 49 | + |
| 50 | + def setHeight(self, n): |
| 51 | + self.length = n |
| 52 | + |
| 53 | + def breast(self, n): |
| 54 | + print "My breast is: ",n |
| 55 | + |
| 56 | + class Girl(Person): |
| 57 | + def setHeight(self): |
| 58 | + print "The height is:1.70m ." |
| 59 | + |
| 60 | + if __name__ == "__main__": |
| 61 | + cang = Girl() |
| 62 | + cang.setHeight() |
| 63 | + cang.speak() |
| 64 | + cang.breast(90) |
| 65 | + |
| 66 | +上面这个程序,保存之后运行: |
| 67 | + |
| 68 | + $ python 20901.py |
| 69 | + The height is:1.70m . |
| 70 | + I love you. |
| 71 | + My breast is: 90 |
| 72 | + |
| 73 | +对以上程序进行解释,从中体会继承的概念和方法。 |
| 74 | + |
| 75 | +首先定义了一个类Person,在这个类中定义了三个方法。注意,没有定义初始化函数,初始化函数在类中不是必不可少的。 |
| 76 | + |
| 77 | +然后又定义了一个类Girl,这个类的名字后面的括号中,是上一个类的名字,这就意味着Girl继承了Person,Girl是Person的子类,Person是Girl的父类。 |
| 78 | + |
| 79 | +既然是继承了Person,那么Girl就全部拥有了Person中的方法和属性(上面的例子虽然没有列出属性)。但是,如果Girl里面有一个和Person同样名称的方法,那么就把Person中的同一个方法遮盖住了,显示的是Girl中的方法,这叫做方法的**重写**。 |
| 80 | + |
| 81 | +实例化类Girl之后,执行实例方法`cang.setHeight()`,由于在类Girl中重写了setHeight方法,那么Person中的那个方法就不显作用了,在这个实例方法中执行的是类Girl中的方法。 |
| 82 | + |
| 83 | +虽然在类Girl中没有看到speak方法,但是因为它继承了Person,所以`cang.speak()`就执行类Person中的方法。同理`cang.breast(90)`,它们就好像是在类Girl里面已经写了这两个方法一样。既然继承了,就是我的了。 |
| 84 | + |
| 85 | +##多重继承 |
| 86 | + |
| 87 | +所谓多重继承,就是只某一个类的父类,不止一个,而是多个。比如: |
| 88 | + |
| 89 | + #!/usr/bin/env python |
| 90 | + # coding=utf-8 |
| 91 | + |
| 92 | + __metaclass__ = type |
| 93 | + |
| 94 | + class Person: |
| 95 | + def eye(self): |
| 96 | + print "two eyes" |
| 97 | + |
| 98 | + def breast(self, n): |
| 99 | + print "The breast is: ",n |
| 100 | + |
| 101 | + class Girl: |
| 102 | + age = 28 |
| 103 | + def color(self): |
| 104 | + print "The girl is white" |
| 105 | + |
| 106 | + class HotGirl(Person, Girl): |
| 107 | + pass |
| 108 | + |
| 109 | + if __name__ == "__main__": |
| 110 | + kong = HotGirl() |
| 111 | + kong.eye() |
| 112 | + kong.breast(90) |
| 113 | + kong.color() |
| 114 | + print kong.age |
| 115 | + |
| 116 | +在这个程序中,前面有两个类:Person和Girl,然后第三个类HotGirl继承了这两个类,注意观察继承方法,就是在类的名字后面的括号中把所继承的两个类的名字写上。但是第三个类中什么方法也没有。 |
| 117 | + |
| 118 | +然后实例化类HotGirl,既然继承了上面的两个类,那么那两个类的方法就都能够拿过来使用。保存程序,运行一下看看 |
| 119 | + |
| 120 | + $ python 20902.py |
| 121 | + two eyes |
| 122 | + The breast is: 90 |
| 123 | + The girl is white |
| 124 | + 28 |
| 125 | + |
| 126 | +值得注意的是,这次在类Girl中,有一个`age = 28`,在对HotGirl实例化之后,因为继承的原因,这个类属性也被继承到HotGirl中,因此通过实例属性`kong.age`一样能够得到该数据。 |
| 127 | + |
| 128 | +由上述两个实例,已经清楚看到了继承的特点,即将父类的方法和属性全部承接到子类中;如果子类重写了父类的方法,就使用子类的该方法,父类的被遮盖。 |
| 129 | + |
| 130 | +##super函数 |
| 131 | + |
| 132 | +对于初始化函数的继承,跟一般方法的继承,还有点不同。可以看下面的例子: |
| 133 | + |
| 134 | + #!/usr/bin/env python |
| 135 | + # coding=utf-8 |
| 136 | + |
| 137 | + __metaclass__ = type |
| 138 | + |
| 139 | + class Person: |
| 140 | + def __init__(self): |
| 141 | + self.height = 160 |
| 142 | + |
| 143 | + def about(self, name): |
| 144 | + print "{} is about {}".format(name, self.height) |
| 145 | + |
| 146 | + class Girl(Person): |
| 147 | + def __init__(self): |
| 148 | + self.breast = 90 |
| 149 | + |
| 150 | + def about(self, name): |
| 151 | + print "{} is a hot girl, she is about {}, and her breast is {}".format(name, self.height, self.breast) |
| 152 | + |
| 153 | + if __name__ == "__main__": |
| 154 | + cang = Girl() |
| 155 | + cang.about("canglaoshi") |
| 156 | + |
| 157 | +在上面这段程序中,类Girl继承了类Person。在类Girl中,初始化设置了`self.breast = 90`,由于继承了Person,按照前面的经验,Person的初始化函数中的`self.height = 160`也应该被Girl所继承过来。然后在重写的about方法中,就是用`self.height`。 |
| 158 | + |
| 159 | +实例化类Girl,并执行`cang.about("canglaoshi")`,试图打印出一句话`canglaoshi is a hot girl, she is about 160, and her bereast is 90`。保存程序,运行之: |
| 160 | + |
| 161 | + $ python 20903.py |
| 162 | + Traceback (most recent call last): |
| 163 | + File "20903.py", line 22, in <module> |
| 164 | + cang.about("canglaoshi") |
| 165 | + File "20903.py", line 18, in about |
| 166 | + print "{} is a hot girl, she is about {}, and her breast is {}".format(name, self.height, self.breast) |
| 167 | + AttributeError: 'Girl' object has no attribute 'height' |
| 168 | + |
| 169 | +报错! |
| 170 | + |
| 171 | +程序员有一句名言:不求最好,但求报错。报错不是坏事,是我们长经验的时候,是在告诉我们,那么做不对。 |
| 172 | + |
| 173 | +重要的是看报错信息。就是我们要打印的那句话出问题了,报错信息显示`self.height`是不存在的。也就是说类Girl没有从Person中继承过来这个属性。 |
| 174 | + |
| 175 | +原因是什么?仔细观察类Girl,会发现,除了刚才强调的about方法重写了,`__init__`方法,也被重写了。不要认为它的名字模样奇怪,就不把它看做类中的方法(函数),它跟类Person中的`__init__`重名了,也同样是重写了那个初始化函数。 |
| 176 | + |
| 177 | +这就提出了一个问题。因为在子类中重写了某个方法之后,父类中同样的方法被遮盖了。那么如何再把父类的该方法调出来使用呢?纵然被遮盖了,应该还是存在的,不要浪费了呀。 |
| 178 | + |
| 179 | +python中有这样一种方法,这种方式是被提倡的方法:super函数。 |
| 180 | + |
| 181 | + #!/usr/bin/env python |
| 182 | + # coding=utf-8 |
| 183 | + |
| 184 | + __metaclass__ = type |
| 185 | + |
| 186 | + class Person: |
| 187 | + def __init__(self): |
| 188 | + self.height = 160 |
| 189 | + |
| 190 | + def about(self, name): |
| 191 | + print "{} is about {}".format(name, self.height) |
| 192 | + |
| 193 | + class Girl(Person): |
| 194 | + def __init__(self): |
| 195 | + super(Girl, self).__init__() |
| 196 | + self.breast = 90 |
| 197 | + |
| 198 | + def about(self, name): |
| 199 | + print "{} is a hot girl, she is about {}, and her breast is {}".format(name, self.height, self.breast) |
| 200 | + super(Girl, self).about(name) |
| 201 | + |
| 202 | + if __name__ == "__main__": |
| 203 | + cang = Girl() |
| 204 | + cang.about("canglaoshi") |
| 205 | + |
| 206 | +在子类中,`__init__`方法重写了,为了调用父类同方法,使用`super(Girl, self).__init__()`的方式。super函数的参数,第一个是当前子类的类名字,第二个是self,然后是点号,点号后面是所要调用的父类的方法。同样在子类重写的about方法中,也可以调用父类的about方法。 |
| 207 | + |
| 208 | +执行结果: |
| 209 | + |
| 210 | + $ python 20903.py |
| 211 | + canglaoshi is a hot girl, she is about 160, and her breast is 90 |
| 212 | + canglaoshi is about 160 |
| 213 | + |
| 214 | +最后要提醒注意:super函数仅仅适用于新式类。当然,你一定是使用的新式类。“喜新厌旧”是程序员的嗜好。 |
| 215 | + |
| 216 | +------ |
| 217 | + |
| 218 | +[总目录](./index.md) | [上节:类(3)](./208.md) | [下节:类(5)](./210.md) |
| 219 | + |
| 220 | +如果你认为有必要打赏我,请通过支付宝: **[email protected]**,不胜感激。 |
0 commit comments