|
1 | 1 | >你们仍是属肉体的,因为在你们中间有嫉妒分争,这岂不是属乎肉体,照着世人的样子行吗?...我栽种了,亚波罗浇灌了,惟有神叫他生长。(1 CORINTHIANS 3:3,6)
|
2 | 2 |
|
3 |
| -#类(4) |
| 3 | +#类(5) |
4 | 4 |
|
5 |
| -本节介绍类中一个非常重要的东西——继承,其实也没有那么重要,只是听起来似乎有点让初学者晕头转向,然后就感觉它属于很高级的东西,真是情况如何?学了之后你自然有感受。 |
| 5 | +##继承 |
6 | 6 |
|
7 |
| -在现实生活中,“继承”意味着一个人从另外一个人那里得到了一些什么,比如“继承革命先烈的光荣传统”、“某人继承他老爹的万贯家产”等。总之,“继承”之后,自己就在所继承的方面省力气、不用劳神费心,能轻松得到,比如继承了万贯家产,自己就一夜之间变成富豪。如果继承了“革命先烈的光荣传统”,自己是不是一下就变成革命者呢? |
| 7 | +继承——OOP的三个特征:多态、继承、封装——是类的重要内容。 |
| 8 | + |
| 9 | +继承,也是人的贪欲。 |
| 10 | + |
| 11 | +在现实生活中,“继承”意味着一个人从另外一个人那里得到了一些什么,“继承”之后,自己就在所继承的方面省力气、不用劳神费心,能轻松得到。比如继承了万贯家产,就一夜之间变成富n代;如果继承了“革命先烈的光荣传统”,就红色? |
8 | 12 |
|
9 | 13 | 当然,生活中的继承或许不那么严格,但是编程语言中的继承是有明确规定和稳定的预期结果的。
|
10 | 14 |
|
| 15 | +###概念 |
| 16 | + |
11 | 17 | >继承(Inheritance)是面向对象软 件技术当中的一个概念。如果一个类别A“继承自”另一个类别B,就把这个A称为“B的子类别”,而把B称为“A的父类别”,也可以称“B是A的超类”。
|
12 | 18 |
|
13 | 19 | >继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。 (源自维基百科)
|
|
17 | 23 | - 可以实现代码重用,但不是仅仅实现代码重用,有时候根本就没有重用
|
18 | 24 | - 实现属性和方法继承
|
19 | 25 |
|
20 |
| -诚然,以上也不是全部,随着后续学习,对继承的认识会更深刻。好友令狐虫曾经这样总结继承: |
| 26 | +诚然,以上也不是全部,随着后续学习,对继承的认识会更深刻,例如网友令狐虫持有这样的观点: |
21 | 27 |
|
22 | 28 | >从技术上说,OOP里,继承最主要的用途是实现多态。对于多态而言,重要的是接口继承性,属性和行为是否存在继承性,这是不一定的。事实上,大量工程实践表明,重度的行为继承会导致系统过度复杂和臃肿,反而会降低灵活性。因此现在比较提倡的是基于接口的轻度继承理念。这种模型里因为父类(接口类)完全没有代码,因此根本谈不上什么代码复用了。
|
23 | 29 |
|
|
29 | 35 |
|
30 | 36 | 或许你也要问我的观点是什么?我的观点就是:走着瞧!怎么理解?继续向下看,只有你先深入这个问题,才能跳到更高层看这个问题。小马过河的故事还记得吧?只有亲自走入河水中,才知道河水的深浅。
|
31 | 37 |
|
32 |
| -对于python中的继承,前面一直在使用,那就是我们写的类都是新式类,所有新式类都是继承自object类。不要忘记,新式类的一种写法: |
| 38 | +在Python 2 中,我们这样定义新式类: |
33 | 39 |
|
34 | 40 | class NewStyle(object):
|
35 | 41 | pass
|
36 | 42 |
|
37 |
| -这就是典型的继承。 |
| 43 | +这就是典型的继承。这个类继承了`object`,`object`是所有类的父类。这种定义是从Python 2.2开始的,它解决了以往的类和类型的不统一的问题,自那以后,类就是一种数据类型了。 |
38 | 44 |
|
39 |
| -##基本概念 |
| 45 | +发展到Python 3,类的定义变为: |
40 | 46 |
|
41 |
| - #!/usr/bin/env python |
42 |
| - # coding=utf-8 |
| 47 | + class NewStyle: |
| 48 | + pass |
| 49 | + |
| 50 | +不再显示地写出`object`,是因为Python 3中的所有类,都隐式地继承了`object`。 |
43 | 51 |
|
44 |
| - __metaclass__ = type |
| 52 | +总而言之,`object`就是所有类的父类。 |
45 | 53 |
|
46 |
| - class Person: |
47 |
| - def speak(self): |
48 |
| - print "I love you." |
| 54 | +###单继承 |
49 | 55 |
|
50 |
| - def setHeight(self): |
51 |
| - print "The height is: 1.60m ." |
| 56 | +这是只从一个父类那里继承。 |
52 | 57 |
|
53 |
| - def breast(self, n): |
54 |
| - print "My breast is: ",n |
| 58 | + >>> class P(object): #Python 3: class P: |
| 59 | + pass |
55 | 60 |
|
56 |
| - class Girl(Person): |
57 |
| - def setHeight(self): |
58 |
| - print "The height is:1.70m ." |
| 61 | + >>> class C(P): |
| 62 | + pass |
59 | 63 |
|
60 |
| - if __name__ == "__main__": |
61 |
| - cang = Girl() |
62 |
| - cang.setHeight() |
63 |
| - cang.speak() |
64 |
| - cang.breast(90) |
| 64 | +寥寥数“键”,就实现了继承。 |
65 | 65 |
|
66 |
| -上面这个程序,保存之后运行: |
67 |
| - |
68 |
| - $ python 20901.py |
69 |
| - The height is:1.70m . |
70 |
| - I love you. |
71 |
| - My breast is: 90 |
72 |
| - |
73 |
| -对以上程序进行解释,从中体会继承的概念和方法。 |
| 66 | +类`P`是一个通常的类,只不过在Python的两个版本中,定义样式稍微不同罢了。类`C`(注意字母大写)则是定义的一个子类,它用`C(P)`的形式继承了类`P`——称之为父类——虽然父类什么也没有。 |
74 | 67 |
|
75 |
| -首先定义了一个类Person,在这个类中定义了三个方法。注意,没有定义初始化函数,初始化函数在类中不是必不可少的。 |
| 68 | +子类`C`继承父类`P`的方式就是在类名称后面的括号里面写上父类的名字,不管是Python的哪个版本。既然继承了父类,那么父类的一切都带入到了子类,所以在Python 2中就没有必要重复写`object`了,它已经通过父类`P`被继承到子类`C`了;Python 3中,要显式的写上父类的名字,除了`object`,它不会隐式继承任何其它类。 |
76 | 69 |
|
77 |
| -然后又定义了一个类Girl,这个类的名字后面的括号中,是上一个类的名字,这就意味着Girl继承了Person,Girl是Person的子类,Person是Girl的父类。 |
78 |
| - |
79 |
| -既然是继承了Person,那么Girl就全部拥有了Person中的方法和属性(上面的例子虽然没有列出属性)。但是,如果Girl里面有一个和Person同样名称的方法,那么就把Person中的同一个方法遮盖住了,显示的是Girl中的方法,这叫做方法的**重写**。 |
| 70 | + >>> C.__base__ |
| 71 | + <class '__main__.P'> |
| 72 | + |
| 73 | +还记得类的一个特殊属性吗?由`C.__base__`可以得到类的父类。刚才的操作,就显示出类`C`的父类是`P`。 |
80 | 74 |
|
81 |
| -实例化类Girl之后,执行实例方法`cang.setHeight()`,由于在类Girl中重写了setHeight方法,那么Person中的那个方法就不显作用了,在这个实例方法中执行的是类Girl中的方法。 |
| 75 | +为了深入理解“继承”的作用,让父类做一点点事情。 |
82 | 76 |
|
83 |
| -虽然在类Girl中没有看到speak方法,但是因为它继承了Person,所以`cang.speak()`就执行类Person中的方法。同理`cang.breast(90)`,它们就好像是在类Girl里面已经写了这两个方法一样。既然继承了,就是我的了。 |
| 77 | + >>> class P(object): #Python 3: class P: |
| 78 | + def __init__(self): |
| 79 | + print "I am a rich man." #Python 3: print("I am a rich man.") |
84 | 80 |
|
85 |
| -##多重继承 |
| 81 | + >>> class C(P): |
| 82 | + pass |
| 83 | + |
| 84 | + >>> c = C() |
| 85 | + I am a rich man. |
86 | 86 |
|
87 |
| -所谓多重继承,就是指某一个类的父类,不止一个,而是多个。比如: |
| 87 | +父类`P`中增加了初始化函数,然后子类`C`继承它。我们已经熟知,当建立实例的时候,首先要执行类中的初始化函数。因为子类`C`继承了父类,就把父类中的初始化函数拿到了子类里面,所以在`c = C()`的时候,执行了父类中定义的初始化函数——这就是继承,而且是从一个父类那里继承来的,所以也称之为单继承。 |
88 | 88 |
|
| 89 | +看一个比较完成的程序示例。 |
| 90 | + |
89 | 91 | #!/usr/bin/env python
|
90 | 92 | # coding=utf-8
|
91 | 93 |
|
92 |
| - __metaclass__ = type |
93 |
| - |
94 |
| - class Person: |
95 |
| - def eye(self): |
96 |
| - print "two eyes" |
| 94 | + class Person(object): #Python 3: class Person: |
| 95 | + def __init__(self, name): |
| 96 | + self.name = name |
| 97 | + |
| 98 | + def height(self, m): |
| 99 | + h = dict((["height", m],)) |
| 100 | + return h |
97 | 101 |
|
98 | 102 | 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" |
| 103 | + b = dict((["breast", n],)) |
| 104 | + return b |
105 | 105 |
|
106 |
| - class HotGirl(Person, Girl): |
107 |
| - pass |
| 106 | + class Girl(Person): |
| 107 | + def get_name(self): |
| 108 | + return self.name |
108 | 109 |
|
109 | 110 | if __name__ == "__main__":
|
110 |
| - kong = HotGirl() |
111 |
| - kong.eye() |
112 |
| - kong.breast(90) |
113 |
| - kong.color() |
114 |
| - print kong.age |
| 111 | + cang = Girl("canglaoshi") |
| 112 | + print cang.get_name() #Python 3: print(cang.get_name()),下同,从略 |
| 113 | + print cang.height(160) |
| 114 | + print cang.breast(90) |
115 | 115 |
|
116 |
| -在这个程序中,前面有两个类:Person和Girl,然后第三个类HotGirl继承了这两个类,注意观察继承方法,就是在类的名字后面的括号中把所继承的两个类的名字写上。但是第三个类中什么方法也没有。 |
| 116 | +上面这个程序,保存之后运行: |
117 | 117 |
|
118 |
| -然后实例化类HotGirl,既然继承了上面的两个类,那么那两个类的方法就都能够拿过来使用。保存程序,运行一下看看 |
| 118 | + canglaoshi |
| 119 | + {'height': 160} |
| 120 | + {'breast': 90} |
119 | 121 |
|
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`一样能够得到该数据。 |
| 122 | +对以上程序进行解释: |
127 | 123 |
|
128 |
| -由上述两个实例,已经清楚看到了继承的特点,即将父类的方法和属性全部承接到子类中;如果子类重写了父类的方法,就使用子类的该方法,父类的被遮盖。 |
| 124 | +首先定义了一个类`Person`,把它作为父类。然后定义了一个子类`Girl`,继承了`Person`。 |
129 | 125 |
|
130 |
| -##多重继承的顺序 |
| 126 | +在子类`Girl`中,只写了一个方法`get_name()`,但是因为是继承了`Person`,那么`Girl`就全部拥有了`Person`中的方法和属性。子类`Girl`的方法`get_name()`中,使用了属性`self.name`,但是在类`Girl`中,并没有什么地方显示创建了这个属性,就是因为继承`Person`类,在父类中有初始化函数。所以,当使用子类创建实例的时候,必须传一个参数`cang = Girl("canglaoshi")`,然后调用实例方法`cang.get_name()`。对于实例方法`cang.height(160)`,也是因着继承的缘故使然。 |
131 | 127 |
|
132 |
| -多重继承的顺序很必要了解。比如,如果一个子类继承了两个父类,并且两个父类有同样的方法或者属性,那么在实例化子类后,调用那个方法或属性,是属于哪个父类的呢?造一个没有实际意义,纯粹为了解决这个问题的程序: |
| 128 | +在上面的程序中,子类`Gril`里面没有与父类`Person`重复的属性和方法,但有时候,会遇到这样的情况。 |
133 | 129 |
|
134 |
| - #!/usr/bin/env python |
135 |
| - # coding=utf-8 |
| 130 | + class Girl(Person): |
| 131 | + def __init__(self): |
| 132 | + self.name = "Aoi sola" |
136 | 133 |
|
137 |
| - class K1(object): |
138 |
| - def foo(self): |
139 |
| - print "K1-foo" |
| 134 | + def get_name(self): |
| 135 | + return self.name |
140 | 136 |
|
141 |
| - class K2(object): |
142 |
| - def foo(self): |
143 |
| - print "K2-foo" |
144 |
| - def bar(self): |
145 |
| - print "K2-bar" |
| 137 | +在子类里面,也写了一个初始化函数,并且定义了一个实例属性`self.name = "Aoi sola"`。在父类中,也有初始化函数。在这种情况下,再次执行程序。 |
146 | 138 |
|
147 |
| - class J1(K1, K2): |
148 |
| - pass |
| 139 | +在Python 2中出现异常: |
149 | 140 |
|
150 |
| - class J2(K1, K2): |
151 |
| - def bar(self): |
152 |
| - print "J2-bar" |
| 141 | + TypeError: __init__() takes exactly 1 argument (2 given) |
153 | 142 |
|
154 |
| - class C(J1, J2): |
155 |
| - pass |
| 143 | +Python 3中也有异常: |
| 144 | + |
| 145 | + TypeError: __init__() takes 1 positional argument but 2 were given |
| 146 | + |
| 147 | +不管哪个版本中的异常信息,都告诉我们,创建实例的时候,传入的参数个数多了。根源在于,子类`Girl`中的初始化函数,只有一个`self`。因为跟父类中的初始化函数重名,虽然继承了父类,但是将父类中的初始化函数覆盖了,导致父类中的`__init__()`在子类中不再实现。所以,实例化子类,不应该再显式地传参数。 |
156 | 148 |
|
157 | 149 | if __name__ == "__main__":
|
158 |
| - print C.__mro__ |
159 |
| - m = C() |
160 |
| - m.foo() |
161 |
| - m.bar() |
| 150 | + cang = Girl() #不在显示地传参数 |
| 151 | + print cang.get_name() #Python 3: print(cang.get_name()),下同,从略 |
| 152 | + print cang.height(160) |
| 153 | + print cang.breast(90) |
162 | 154 |
|
163 |
| -这段代码,保存后运行: |
| 155 | +如此修改之后,再运行,则显示结果: |
164 | 156 |
|
165 |
| - $ python 20904.py |
166 |
| - (<class '__main__.C'>, <class '__main__.J1'>, <class '__main__.J2'>, <class '__main__.K1'>, <class '__main__.K2'>, <type 'object'>) |
167 |
| - K1-foo |
168 |
| - J2-bar |
| 157 | + Aoi sola |
| 158 | + {'height': 160} |
| 159 | + {'breast': 90} |
169 | 160 |
|
170 |
| -代码中的`print C.__mro__`是要打印出类的继承顺序。从上面清晰看出来了。如果要执行foo()方法,首先看J1,没有,看J2,还没有,看J1里面的K1,有了,即C==>J1==>J2==>K1;bar()也是按照这个顺序,在J2中就找到了一个。 |
| 161 | +从结果中不难看出,如果子类中的方法或属性覆盖了父类(即与父类同名),那么就不在继承父类的该方法或者属性。 |
| 162 | + |
| 163 | +像这样,子类`Girl`里面有与父类`Person`同样名称的方法和属性,也称之为对父类相应部分的重写。重写之后,父类的相应部分不再被继承到子类,没有重写的部分,在子类中依然被继承,从上面程序可以看出来此结果。 |
171 | 164 |
|
172 |
| -这种对继承属性和方法搜索的顺序称之为“广度优先”。 |
| 165 | +还有一种可能存在,就是重写之后,如果要在子类中继承父类中相应部分,怎么办? |
173 | 166 |
|
174 |
| -新式类用以及python3.x中都是按照此顺序原则搜寻属性和方法的。 |
| 167 | +##super函数 |
175 | 168 |
|
176 |
| -但是,在旧式类中,是按照“深度优先”的顺序的。因为后面读者也基本不用旧式类,所以不举例。如果读者愿意,可以自己模仿上面代码,探索旧式类的“深度优先”含义。 |
| 169 | +承接前面的问题和程序,可以对子类`Gril`做出这样的修改。 |
177 | 170 |
|
178 |
| -##super函数 |
| 171 | + class Girl(Person): |
| 172 | + def __init__(self, name): |
| 173 | + Person.__init__(self, name) |
| 174 | + self.real_name = "Aoi sola" |
| 175 | + |
| 176 | + def get_name(self): |
| 177 | + return self.name |
| 178 | + |
| 179 | +请读者注意观察`Girl`的初始化方法,与前面的有所不同。为了能够继续继承父类的初始化方法,以类方法的方式将父类的初始化函数再次调用`Person.__init__(self, name)`,同时,在子类的`__init__()`的参数中,要增加相应的参数`name`。这样就回答了前面的问题。 |
| 180 | + |
| 181 | +在实例化子类的时候,就以下面的方式进行: |
| 182 | + |
| 183 | + if __name__ == "__main__": |
| 184 | + cang = Girl("canglaoshi") |
| 185 | + print cang.real_name |
| 186 | + print cang.get_name() |
| 187 | + print cang.height(160) |
| 188 | + print cang.breast(90) |
| 189 | + |
| 190 | +执行结果为: |
| 191 | + |
| 192 | + Aoi sola |
| 193 | + canglaoshi |
| 194 | + {'height': 160} |
| 195 | + {'breast': 90} |
179 | 196 |
|
180 | 197 | 对于初始化函数的继承,跟一般方法的继承,还有点不同。可以看下面的例子:
|
181 | 198 |
|
@@ -261,6 +278,101 @@ python中有这样一种方法,这种方式是被提倡的方法:super函数
|
261 | 278 |
|
262 | 279 | 最后要提醒注意:super函数仅仅适用于新式类。当然,你一定是使用的新式类。“喜新厌旧”是程序员的嗜好。
|
263 | 280 |
|
| 281 | + |
| 282 | +##多重继承 |
| 283 | + |
| 284 | +所谓多重继承,就是指某一个类的父类,不止一个,而是多个。比如: |
| 285 | + |
| 286 | + #!/usr/bin/env python |
| 287 | + # coding=utf-8 |
| 288 | + |
| 289 | + __metaclass__ = type |
| 290 | + |
| 291 | + class Person: |
| 292 | + def eye(self): |
| 293 | + print "two eyes" |
| 294 | + |
| 295 | + def breast(self, n): |
| 296 | + print "The breast is: ",n |
| 297 | + |
| 298 | + class Girl: |
| 299 | + age = 28 |
| 300 | + def color(self): |
| 301 | + print "The girl is white" |
| 302 | + |
| 303 | + class HotGirl(Person, Girl): |
| 304 | + pass |
| 305 | + |
| 306 | + if __name__ == "__main__": |
| 307 | + kong = HotGirl() |
| 308 | + kong.eye() |
| 309 | + kong.breast(90) |
| 310 | + kong.color() |
| 311 | + print kong.age |
| 312 | + |
| 313 | +在这个程序中,前面有两个类:Person和Girl,然后第三个类HotGirl继承了这两个类,注意观察继承方法,就是在类的名字后面的括号中把所继承的两个类的名字写上。但是第三个类中什么方法也没有。 |
| 314 | + |
| 315 | +然后实例化类HotGirl,既然继承了上面的两个类,那么那两个类的方法就都能够拿过来使用。保存程序,运行一下看看 |
| 316 | + |
| 317 | + $ python 20902.py |
| 318 | + two eyes |
| 319 | + The breast is: 90 |
| 320 | + The girl is white |
| 321 | + 28 |
| 322 | + |
| 323 | +值得注意的是,这次在类Girl中,有一个`age = 28`,在对HotGirl实例化之后,因为继承的原因,这个类属性也被继承到HotGirl中,因此通过实例属性`kong.age`一样能够得到该数据。 |
| 324 | + |
| 325 | +由上述两个实例,已经清楚看到了继承的特点,即将父类的方法和属性全部承接到子类中;如果子类重写了父类的方法,就使用子类的该方法,父类的被遮盖。 |
| 326 | + |
| 327 | +##多重继承的顺序 |
| 328 | + |
| 329 | +多重继承的顺序很必要了解。比如,如果一个子类继承了两个父类,并且两个父类有同样的方法或者属性,那么在实例化子类后,调用那个方法或属性,是属于哪个父类的呢?造一个没有实际意义,纯粹为了解决这个问题的程序: |
| 330 | + |
| 331 | + #!/usr/bin/env python |
| 332 | + # coding=utf-8 |
| 333 | + |
| 334 | + class K1(object): |
| 335 | + def foo(self): |
| 336 | + print "K1-foo" |
| 337 | + |
| 338 | + class K2(object): |
| 339 | + def foo(self): |
| 340 | + print "K2-foo" |
| 341 | + def bar(self): |
| 342 | + print "K2-bar" |
| 343 | + |
| 344 | + class J1(K1, K2): |
| 345 | + pass |
| 346 | + |
| 347 | + class J2(K1, K2): |
| 348 | + def bar(self): |
| 349 | + print "J2-bar" |
| 350 | + |
| 351 | + class C(J1, J2): |
| 352 | + pass |
| 353 | + |
| 354 | + if __name__ == "__main__": |
| 355 | + print C.__mro__ |
| 356 | + m = C() |
| 357 | + m.foo() |
| 358 | + m.bar() |
| 359 | + |
| 360 | +这段代码,保存后运行: |
| 361 | + |
| 362 | + $ python 20904.py |
| 363 | + (<class '__main__.C'>, <class '__main__.J1'>, <class '__main__.J2'>, <class '__main__.K1'>, <class '__main__.K2'>, <type 'object'>) |
| 364 | + K1-foo |
| 365 | + J2-bar |
| 366 | + |
| 367 | +代码中的`print C.__mro__`是要打印出类的继承顺序。从上面清晰看出来了。如果要执行foo()方法,首先看J1,没有,看J2,还没有,看J1里面的K1,有了,即C==>J1==>J2==>K1;bar()也是按照这个顺序,在J2中就找到了一个。 |
| 368 | + |
| 369 | +这种对继承属性和方法搜索的顺序称之为“广度优先”。 |
| 370 | + |
| 371 | +新式类用以及python3.x中都是按照此顺序原则搜寻属性和方法的。 |
| 372 | + |
| 373 | +但是,在旧式类中,是按照“深度优先”的顺序的。因为后面读者也基本不用旧式类,所以不举例。如果读者愿意,可以自己模仿上面代码,探索旧式类的“深度优先”含义。 |
| 374 | + |
| 375 | + |
264 | 376 | ------
|
265 | 377 |
|
266 | 378 | [总目录](./index.md) | [上节:类(3)](./208.md) | [下节:类(5)](./210.md)
|
|
0 commit comments