单继承
我们在对象模型中,通过 Dog 与 Sleuth 类来讲解类继承关系。为了进一步讨论类继承机制,我们进一步扩充:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Animal:
def run(self):
print('running')
class Dog(Animal):
def yelp(self):
print('woof')
def play(self):
print('playing')
class Sleuth(Dog):
def yelp(self):
print('WOOF!')
def hunt(self):
print('hunting')
|
通过引入 Animal 类,我们得到一条包含 3 个类的继承链,继承链结束语 object 基类型对象:
现在,实例化一个 Sleuth 对象,它可以调用自己定义的方法,例如 hunt :
1
2
3
|
>>> s = Sleuth()
>>> s.hunt()
hunting
|
当然了,由于 Sleuth 类继承于 Dog 类,因此 Sleuth 对象也可以调用 Dog 定义的方法,例如 play :
Sleuth 类通过 Dog 类间接继承于 Animal 类,因此它也可以调用 Animal 定义的方法:
如果子类对父类中的方法不满意,还可以进行方法重写。猎犬吠声与普通狗有所不同,我们可以为 Sleuth 类重写 yelp 方法,以大写突出吠声的威武雄壮。这样一来,Sleuth 实例对象将执行 Sleuth 类中定义的 yelp 方法版本:
那么,Python 虚拟机内部是如何实现继承机制的呢?我们接着到字节码中寻找秘密。
对以上例子进行编译,我们可以得到这样的字节码:
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
|
1 0 LOAD_BUILD_CLASS
2 LOAD_CONST 0 (<code object Animal at 0x109b90810, file "", line 1>)
4 LOAD_CONST 1 ('Animal')
6 MAKE_FUNCTION 0
8 LOAD_CONST 1 ('Animal')
10 CALL_FUNCTION 2
12 STORE_NAME 0 (Animal)
5 14 LOAD_BUILD_CLASS
16 LOAD_CONST 2 (<code object Dog at 0x109bd1c90, file "", line 5>)
18 LOAD_CONST 3 ('Dog')
20 MAKE_FUNCTION 0
22 LOAD_CONST 3 ('Dog')
24 LOAD_NAME 0 (Animal)
26 CALL_FUNCTION 3
28 STORE_NAME 1 (Dog)
11 30 LOAD_BUILD_CLASS
32 LOAD_CONST 4 (<code object Sleuth at 0x109bd1a50, file "", line 11>)
34 LOAD_CONST 5 ('Sleuth')
36 MAKE_FUNCTION 0
38 LOAD_CONST 5 ('Sleuth')
40 LOAD_NAME 1 (Dog)
42 CALL_FUNCTION 3
44 STORE_NAME 2 (Sleuth)
46 LOAD_CONST 6 (None)
48 RETURN_VALUE
|
由上一小节,我们知道 LOAD_BUILD_CLASS 字节码用于加载 build_class 函数,它创建类对象,接口如下:
1
2
3
4
5
6
7
|
>>> help(__build_class__)
Help on built-in function __build_class__ in module builtins:
__build_class__(...)
__build_class__(func, name, *bases, metaclass=None, **kwds) -> class
Internal helper function used by the class statement.
|
build_class 相关参数如下:
- func ,用于初始化类属性空间的可调动对象,由类代码块生成;
- name ,类名;
- bases ,基类,可以为多个;
由此可见,创建子类时,需要将父类作为 bases 参数传给 build_class 函数。
创建 Animal 类时,由于没有显式指定继承关系,因此没有给 build_class 函数传递任何基类:
1
2
3
4
5
6
7
|
1 0 LOAD_BUILD_CLASS
2 LOAD_CONST 0 (<code object Animal at 0x109b90810, file "", line 1>)
4 LOAD_CONST 1 ('Animal')
6 MAKE_FUNCTION 0
8 LOAD_CONST 1 ('Animal')
10 CALL_FUNCTION 2
12 STORE_NAME 0 (Animal)
|
这时, build_class 函数将默认以 object 为基类创建 Animal 对象。换句话讲,如果自定义类没有显式指定继承关系,将默认继承于 object ,这就是继承链中 object 的由来。
当我们创建 Dog 类时,由于代码中明确指定了从 Animal 继承,偏移量为 24 的那条字节码将 Animal 类加载到运行栈并传给 build_class 函数:
1
2
3
4
5
6
7
8
|
5 14 LOAD_BUILD_CLASS
16 LOAD_CONST 2 (<code object Dog at 0x109bd1c90, file "", line 5>)
18 LOAD_CONST 3 ('Dog')
20 MAKE_FUNCTION 0
22 LOAD_CONST 3 ('Dog')
24 LOAD_NAME 0 (Animal)
26 CALL_FUNCTION 3
28 STORE_NAME 1 (Dog)
|
结合对象模型中的知识可知: build_class 函数将基类保存于 PyTypeObject 类型对象的 tp_base 字段中。
通过 tp_base 字段,子类与父类被串在一起,形成一条继承链:
那么,在多继承的场景下,python 内部又是如何保存继承关系的呢?类的属性查找机制又隐藏着什么秘密呢?点击“阅读原文”,获取更多详情!
【Python源码剖析】系列文章首发于公众号【小菜学编程】,敬请关注: