Skip to content

Commit 6ba24b9

Browse files
committed
p3
1 parent 2c03aa9 commit 6ba24b9

File tree

5 files changed

+274
-104
lines changed

5 files changed

+274
-104
lines changed

209.md

Lines changed: 214 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
>你们仍是属肉体的,因为在你们中间有嫉妒分争,这岂不是属乎肉体,照着世人的样子行吗?...我栽种了,亚波罗浇灌了,惟有神叫他生长。(1 CORINTHIANS 3:3,6)
22
3-
#类(4)
3+
#类(5)
44

5-
本节介绍类中一个非常重要的东西——继承,其实也没有那么重要,只是听起来似乎有点让初学者晕头转向,然后就感觉它属于很高级的东西,真是情况如何?学了之后你自然有感受。
5+
##继承
66

7-
在现实生活中,“继承”意味着一个人从另外一个人那里得到了一些什么,比如“继承革命先烈的光荣传统”、“某人继承他老爹的万贯家产”等。总之,“继承”之后,自己就在所继承的方面省力气、不用劳神费心,能轻松得到,比如继承了万贯家产,自己就一夜之间变成富豪。如果继承了“革命先烈的光荣传统”,自己是不是一下就变成革命者呢?
7+
继承——OOP的三个特征:多态、继承、封装——是类的重要内容。
8+
9+
继承,也是人的贪欲。
10+
11+
在现实生活中,“继承”意味着一个人从另外一个人那里得到了一些什么,“继承”之后,自己就在所继承的方面省力气、不用劳神费心,能轻松得到。比如继承了万贯家产,就一夜之间变成富n代;如果继承了“革命先烈的光荣传统”,就红色?
812

913
当然,生活中的继承或许不那么严格,但是编程语言中的继承是有明确规定和稳定的预期结果的。
1014

15+
###概念
16+
1117
>继承(Inheritance)是面向对象软 件技术当中的一个概念。如果一个类别A“继承自”另一个类别B,就把这个A称为“B的子类别”,而把B称为“A的父类别”,也可以称“B是A的超类”。
1218
1319
>继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。 (源自维基百科)
@@ -17,7 +23,7 @@
1723
- 可以实现代码重用,但不是仅仅实现代码重用,有时候根本就没有重用
1824
- 实现属性和方法继承
1925

20-
诚然,以上也不是全部,随着后续学习,对继承的认识会更深刻。好友令狐虫曾经这样总结继承
26+
诚然,以上也不是全部,随着后续学习,对继承的认识会更深刻,例如网友令狐虫持有这样的观点
2127

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

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

32-
对于python中的继承,前面一直在使用,那就是我们写的类都是新式类,所有新式类都是继承自object类。不要忘记,新式类的一种写法
38+
在Python 2 中,我们这样定义新式类
3339

3440
class NewStyle(object):
3541
pass
3642
37-
这就是典型的继承。
43+
这就是典型的继承。这个类继承了`object``object`是所有类的父类。这种定义是从Python 2.2开始的,它解决了以往的类和类型的不统一的问题,自那以后,类就是一种数据类型了。
3844

39-
##基本概念
45+
发展到Python 3,类的定义变为:
4046

41-
#!/usr/bin/env python
42-
# coding=utf-8
47+
class NewStyle:
48+
pass
49+
50+
不再显示地写出`object`,是因为Python 3中的所有类,都隐式地继承了`object`
4351

44-
__metaclass__ = type
52+
总而言之,`object`就是所有类的父类。
4553

46-
class Person:
47-
def speak(self):
48-
print "I love you."
54+
###单继承
4955

50-
def setHeight(self):
51-
print "The height is: 1.60m ."
56+
这是只从一个父类那里继承。
5257

53-
def breast(self, n):
54-
print "My breast is: ",n
58+
>>> class P(object): #Python 3: class P:
59+
pass
5560

56-
class Girl(Person):
57-
def setHeight(self):
58-
print "The height is:1.70m ."
61+
>>> class C(P):
62+
pass
5963

60-
if __name__ == "__main__":
61-
cang = Girl()
62-
cang.setHeight()
63-
cang.speak()
64-
cang.breast(90)
64+
寥寥数“键”,就实现了继承。
6565

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`——称之为父类——虽然父类什么也没有。
7467

75-
首先定义了一个类Person,在这个类中定义了三个方法。注意,没有定义初始化函数,初始化函数在类中不是必不可少的
68+
子类`C`继承父类`P`的方式就是在类名称后面的括号里面写上父类的名字,不管是Python的哪个版本。既然继承了父类,那么父类的一切都带入到了子类,所以在Python 2中就没有必要重复写`object`了,它已经通过父类`P`被继承到子类`C`了;Python 3中,要显式的写上父类的名字,除了`object`,它不会隐式继承任何其它类
7669

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`
8074

81-
实例化类Girl之后,执行实例方法`cang.setHeight()`,由于在类Girl中重写了setHeight方法,那么Person中的那个方法就不显作用了,在这个实例方法中执行的是类Girl中的方法
75+
为了深入理解“继承”的作用,让父类做一点点事情
8276

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.")
8480

85-
##多重继承
81+
>>> class C(P):
82+
pass
83+
84+
>>> c = C()
85+
I am a rich man.
8686

87-
所谓多重继承,就是指某一个类的父类,不止一个,而是多个。比如:
87+
父类`P`中增加了初始化函数,然后子类`C`继承它。我们已经熟知,当建立实例的时候,首先要执行类中的初始化函数。因为子类`C`继承了父类,就把父类中的初始化函数拿到了子类里面,所以在`c = C()`的时候,执行了父类中定义的初始化函数——这就是继承,而且是从一个父类那里继承来的,所以也称之为单继承。
8888

89+
看一个比较完成的程序示例。
90+
8991
#!/usr/bin/env python
9092
# coding=utf-8
9193

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
97101

98102
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
105105

106-
class HotGirl(Person, Girl):
107-
pass
106+
class Girl(Person):
107+
def get_name(self):
108+
return self.name
108109

109110
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)
115115

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

118-
然后实例化类HotGirl,既然继承了上面的两个类,那么那两个类的方法就都能够拿过来使用。保存程序,运行一下看看
118+
canglaoshi
119+
{'height': 160}
120+
{'breast': 90}
119121

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+
对以上程序进行解释:
127123

128-
由上述两个实例,已经清楚看到了继承的特点,即将父类的方法和属性全部承接到子类中;如果子类重写了父类的方法,就使用子类的该方法,父类的被遮盖
124+
首先定义了一个类`Person`,把它作为父类。然后定义了一个子类`Girl`,继承了`Person`
129125

130-
##多重继承的顺序
126+
在子类`Girl`中,只写了一个方法`get_name()`,但是因为是继承了`Person`,那么`Girl`就全部拥有了`Person`中的方法和属性。子类`Girl`的方法`get_name()`中,使用了属性`self.name`,但是在类`Girl`中,并没有什么地方显示创建了这个属性,就是因为继承`Person`类,在父类中有初始化函数。所以,当使用子类创建实例的时候,必须传一个参数`cang = Girl("canglaoshi")`,然后调用实例方法`cang.get_name()`。对于实例方法`cang.height(160)`,也是因着继承的缘故使然。
131127

132-
多重继承的顺序很必要了解。比如,如果一个子类继承了两个父类,并且两个父类有同样的方法或者属性,那么在实例化子类后,调用那个方法或属性,是属于哪个父类的呢?造一个没有实际意义,纯粹为了解决这个问题的程序:
128+
在上面的程序中,子类`Gril`里面没有与父类`Person`重复的属性和方法,但有时候,会遇到这样的情况。
133129

134-
#!/usr/bin/env python
135-
# coding=utf-8
130+
class Girl(Person):
131+
def __init__(self):
132+
self.name = "Aoi sola"
136133

137-
class K1(object):
138-
def foo(self):
139-
print "K1-foo"
134+
def get_name(self):
135+
return self.name
140136

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"`。在父类中,也有初始化函数。在这种情况下,再次执行程序。
146138

147-
class J1(K1, K2):
148-
pass
139+
在Python 2中出现异常:
149140

150-
class J2(K1, K2):
151-
def bar(self):
152-
print "J2-bar"
141+
TypeError: __init__() takes exactly 1 argument (2 given)
153142

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__()`在子类中不再实现。所以,实例化子类,不应该再显式地传参数。
156148

157149
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)
162154

163-
这段代码,保存后运行
155+
如此修改之后,再运行,则显示结果
164156

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}
169160

170-
代码中的`print C.__mro__`是要打印出类的继承顺序。从上面清晰看出来了。如果要执行foo()方法,首先看J1,没有,看J2,还没有,看J1里面的K1,有了,即C==>J1==>J2==>K1;bar()也是按照这个顺序,在J2中就找到了一个。
161+
从结果中不难看出,如果子类中的方法或属性覆盖了父类(即与父类同名),那么就不在继承父类的该方法或者属性。
162+
163+
像这样,子类`Girl`里面有与父类`Person`同样名称的方法和属性,也称之为对父类相应部分的重写。重写之后,父类的相应部分不再被继承到子类,没有重写的部分,在子类中依然被继承,从上面程序可以看出来此结果。
171164

172-
这种对继承属性和方法搜索的顺序称之为“广度优先”。
165+
还有一种可能存在,就是重写之后,如果要在子类中继承父类中相应部分,怎么办?
173166

174-
新式类用以及python3.x中都是按照此顺序原则搜寻属性和方法的。
167+
##super函数
175168

176-
但是,在旧式类中,是按照“深度优先”的顺序的。因为后面读者也基本不用旧式类,所以不举例。如果读者愿意,可以自己模仿上面代码,探索旧式类的“深度优先”含义
169+
承接前面的问题和程序,可以对子类`Gril`做出这样的修改
177170

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}
179196

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

@@ -261,6 +278,101 @@ python中有这样一种方法,这种方式是被提倡的方法:super函数
261278

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

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+
264376
------
265377

266378
[总目录](./index.md)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[上节:类(3)](./208.md)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[下节:类(5)](./210.md)

0 commit comments

Comments
 (0)