Python面向对象基础篇之继承

在Java和C#这类面向对象的语言中,一个类只允许有一个父类。而在Python中,可以有多个父类。继承,顾名思义,就是子类默认获得父类中的所有成员。当子类中不包含与父类同名的成员时,Python会自动去父类中查找并使用父类的成员;当子类中的成员与父类中的成员名称相同时,优先使用子类的成员。这种覆盖父类成员属性的特性被称之为“重写”或“复写”

什么是继承

继承可以简单的理解为,将父类中的所有成员copy一份到子类中,子类和父类如果有相同的成员名,优先使用子类的代码。

1
2
3
4
5
6
7
class A:
def show(self):
print('show')

class B(A):
def get(self):
print('get')

上面的代码中,我让B类去继承了A类,也就是说A类是B类的父类。现在B类和A类中没有名字相同的成员,所以可以简单的看做是:

1
2
3
4
5
6
class B:
def show(self):
print('show')

def get(self):
print('get')

接下来执行如下代码

1
2
3
4
5
b = B()
b.show()

------------
show

通过上面的额代码可以验证,在B类中,并没有show方法,但是B类继承了A类,相当于继承了A类所有的代码,所以B类的对象可以使用A类的成员方法。

重写的特性

上面我们说到,只有在子类中与父类的成员方法名字不冲突的时候,才会将父类中的成员方法copy到子类中使用(继承到子类)那么,假如子类和父类的某些方法名字冲突了会怎样呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A:
def show(self):
print('A show')

class B(A):
def get(self):
print('get')

def show(self):
print('B show')

b = B()
b.show()

------------
B show

在父类中调用子类的成员属性或方法

  • 实例一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class A:
def show(self):
print('A show')

def func(self):
print(self.name)

def func2(self):
self.get()


class B(A):
def __init__(self, name):
self.name = name

def get(self):
print('get')

def show(self):
print('B show')


b = B('polarsnow')
b.func()
b.func2()

猜一猜会有什么样的结果?会打印出polarsnowget吗?

按照之前的说法,我们可以把上面的继承看成下面这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class B:
def __init__(self, name):
self.name = name

def func(self):
print(self.name)

def func2(self):
self.get()

def get(self):
print('get')

def show(self):
print('B show')

b = B('polarsnow')
b.func()

这样看答案就清楚了吧,肯定是会打印polarsnowget

这里只是举个例子,实际使用中,不推荐在父类中去调用子类的属性,因为有的时候,你不能确定所有继承这个父类的子类都拥有这个成员属性。

  • 实例二
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A:
def f1(self):
self.f2()

def f2(self):
print('A f2')

class B(A):
def f3(self):
self.f1()

def f2(self):
print('B f2')

b = B()
b.f3()

创建了B类的对象b,然后执行了对象b的f3方法,在f3方法中又执行了f1方法,f1方法在B类中没有,但是B类继承了A类的代码,(注意这里是继承了A类的成员,可以理解为copy过来的成员,而不是去A类中找相应的成员)执行了f1方法后,又调用了f2方法,思考,这个f2是调用了A类中的f2还是B类中的f2?

根据继承的原理,我们把上面的代码看做如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A:
def f1(self):
self.f2()

def f2(self):
print('A f2')

class B(A):
def f1(self):
self.f2()

def f3(self):
self.f1()

def f2(self):
print('B f2')

b = B()
b.f3()

将父类中有而子类中没有的成员copy到子类中,子类中有而父类中也有的优先使用子类的成员(重写父类成员)所以A类中只有f1这个成员方法被copy到了B类中。

当B类的对象b调用f3方法时,调用本类中的f1方法(继承过来的成员方法),接着在f1方法中又调用了本类中已经存在的f2方法。所以最后调用的是B类中的f2方法。

如果执行以下命令,执行的f2是哪里的f2?

1
2
a = A()
a.f1()

当然会执行本类中的f2方法,也就是A类中的f2方法

总结:由于Python类中self隐式传递对象自己本身的特性,可以看出,如果调用了子类中不存在的成员,那么去父类中查找的时候,self变量仍然等于原对象自己本身,也就是说,继承的每次查找,都会返回到最底层的(调用对象的那一层)类中去找,如果找不到,才会去父类中查找。(记住每次寻找失败都回到原点)

多继承

  • 一个类同时继承两个类的情况下,会优先去左边的父类中查找,如果没有则去右边的类中查找

  • 一个类同时继承两个类,左边的类又继承了一个类的情况下,会优先从最左边的类开始找,如果没有,则继续向上查找,如果上面的类还有单继承,就继续向上查找,直到找到没有继承的类为止

  • 如果多继承出现了如下的继承关系,那么左边的类将找到没有继承或公共继承的类为止,然后就切到了第二个类中继续向上查找,公共继承的类会在最右边的类中被找到

查看Python源码的技巧

在Python的源码中,我们看到好多的pass,却不知道真正的函数体或方法体写在了哪里,那么看完Python的多继承,将完全解决这个问题!

1
2
3
4
import socketserver

s = socketserver.ThreadingTCPServer()
s.serve_forever()

socketserver.ThreadingTCPServer()创建了一个对象,而创建对象时需要执行__init__方法,我们来看看源码中有没有提供这个init方法

1
2
# 源码
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

从上面的源码中可以看出,这个类直接被pass掉了。根据上面的多继承的知识,本类中没有init方法,我们需要去他的父类中去查找,首先去左边第一个父类中去查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 源码
class ThreadingMixIn:
"""Mix-in class to handle each request in a new thread."""

# Decides how threads will act upon termination of the
# main process
daemon_threads = False

def process_request_thread(self, request, client_address):
"""Same as in BaseServer but as a thread.

In addition, exception handling is done here.

"""
try:
self.finish_request(request, client_address)
self.shutdown_request(request)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)

def process_request(self, request, client_address):
"""Start a new thread to process the request."""
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address))
t.daemon = self.daemon_threads
t.start()

这边也没有init方法,这个时候由于这个类不再有继承关系,所以需要回到原点查找第二个父类中有没有init方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 源码
class TCPServer(BaseServer):
...
def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
"""Constructor. May be extended, do not override."""
BaseServer.__init__(self, server_address, RequestHandlerClass)
self.socket = socket.socket(self.address_family,
self.socket_type)
if bind_and_activate:
try:
self.server_bind()
self.server_activate()
except:
self.server_close()
raise

...

哎~ 在这里找到了init方法,那么最开始创建的对象的代码的init方法,就会使用这里的代码啦!

至此,s = socketserver.TreadingTCPServer()这一行代码就执行完毕啦。

接下来需要执行这个对象的server.forver()方法

接着又回到这个类中查找

1
2
# 源码
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

没有,从左边的父类找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 源码
class ThreadingMixIn:
"""Mix-in class to handle each request in a new thread."""

# Decides how threads will act upon termination of the
# main process
daemon_threads = False

def process_request_thread(self, request, client_address):
"""Same as in BaseServer but as a thread.

In addition, exception handling is done here.

"""
try:
self.finish_request(request, client_address)
self.shutdown_request(request)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)

def process_request(self, request, client_address):
"""Start a new thread to process the request."""
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address))
t.daemon = self.daemon_threads
t.start()

左边没有,去右边查找

1
2
3
# 源码
class TCPServer(BaseServer):
...

右边也没有找到,但是右边的父类中,还有一个继承,继续向上查找!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 源码
class BaseServer:
...
def serve_forever(self, poll_interval=0.5):
"""Handle one request at a time until shutdown.

Polls for shutdown every poll_interval seconds. Ignores
self.timeout. If you need to do periodic tasks, do them in
another thread.
"""
self.__is_shut_down.clear()
try:
# XXX: Consider using another file descriptor or connecting to the
# socket to wake this up instead of polling. Polling reduces our
# responsiveness to a shutdown request and wastes cpu at all other
# times.
with _ServerSelector() as selector:
selector.register(self, selectors.EVENT_READ)

while not self.__shutdown_request:
ready = selector.select(poll_interval)
if ready:
self._handle_request_noblock()

self.service_actions()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()
...

啊哈,原来在这里找到了它!!!但是这里面又调用了self._handle_request_noblock()这个方法,根据之前的继承原理,这里的self指的是调用对象的地址,所以需要重新回到socketserver.ThreadingTCPServer()开始查找

1
2
# 源码
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

没有,找左边

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 源码
class ThreadingMixIn:
"""Mix-in class to handle each request in a new thread."""

# Decides how threads will act upon termination of the
# main process
daemon_threads = False

def process_request_thread(self, request, client_address):
"""Same as in BaseServer but as a thread.

In addition, exception handling is done here.

"""
try:
self.finish_request(request, client_address)
self.shutdown_request(request)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)

def process_request(self, request, client_address):
"""Start a new thread to process the request."""
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address))
t.daemon = self.daemon_threads
t.start()

左边没有找右边

1
2
3
# 源码
class TCPServer(BaseServer):
...

右边也没有,继续向上找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 源码
class BaseServer:
...
def _handle_request_noblock(self):
"""Handle one request, without blocking.

I assume that selector.select() has returned that the socket is
readable before this function was called, so there should be no risk of
blocking in get_request().
"""
try:
request, client_address = self.get_request()
except OSError:
return
if self.verify_request(request, client_address):
try:
self.process_request(request, client_address)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)
...

找到了!但是这里面又执行了self.process_request(),有self,仍需要回到最开始的地方查找

1
2
# 源码
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

没有,找左边

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 源码
class ThreadingMixIn:
"""Mix-in class to handle each request in a new thread."""

# Decides how threads will act upon termination of the
# main process
daemon_threads = False

def process_request_thread(self, request, client_address):
"""Same as in BaseServer but as a thread.

In addition, exception handling is done here.

"""
try:
self.finish_request(request, client_address)
self.shutdown_request(request)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)

def process_request(self, request, client_address):
"""Start a new thread to process the request."""
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address))
t.daemon = self.daemon_threads
t.start()

找到啦!!!


这就是socketserver的源码阅读方法,也适用于所有源码的阅读方法,再次强调,遇到self一定要回到原点再次寻找