类继承机制与属性查找

单继承

我们在对象模型中,通过 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

1
2
>>> s.play()
playing

Sleuth 类通过 Dog 类间接继承于 Animal 类,因此它也可以调用 Animal 定义的方法:

1
2
>>> s.run()
running

如果子类对父类中的方法不满意,还可以进行方法重写。猎犬吠声与普通狗有所不同,我们可以为 Sleuth 类重写 yelp 方法,以大写突出吠声的威武雄壮。这样一来,Sleuth 实例对象将执行 Sleuth 类中定义的 yelp 方法版本:

1
2
>>> s.yelp()
WOOF!

那么,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源码剖析】系列文章首发于公众号【小菜学编程】,敬请关注:

【Python源码剖析】系列文章首发于公众号【小菜学编程】,敬请关注: