zoukankan      html  css  js  c++  java
  • 《深度剖析CPython解释器》19. Python类机制的深度解析(第三部分): 自定义类的底层实现、以及metaclass

    楔子

    Python除了给我提供了很多的类之外,还支持我们定义属于自己的类,那么Python底层是如何做的呢?我们下面就来看看。

    自定义class

    老规矩,如果想知道底层是怎么做的,那么就必须要通过观察字节码来实现。

    class Girl:
    
        name = "夏色祭"
        def __init__(self):
            print("__init__")
    
        def f(self):
            print("f")
    
        def g(self, name):
            self.name = name
            print(self.name)
    
    
    girl = Girl()
    girl.f()
    girl.g("神乐mea")
    """
    __init__
    f
    神乐mea
    """
    

    通过之前对函数机制的分析中,我们知道对于一个包含函数定义的Python源文件,在编译之后会得到一个和源文件对应的PyCodeObject对象,其内部的常量池中存储了函数编译之后的PyCodeObject对象。那么对于包含类的Python源文件,编译之后的结果又是怎么样的呢?

    显然我们可以照葫芦画瓢,根据以前的经验我们可以猜测模块对应的PyCodeObject对象的常量池中肯定存储了类对应的PyCodeObject对象,类对应的PyCodeObject对象的常量池中则存储了__init__、f、g三个函数对应的PyCodeObject对象。然而事实也确实如此。

    在介绍函数的时候,我们看到函数的声明(def语句)和函数的实现代码虽然是一个逻辑整体,但是它们的字节码指令却是分离在两个PyCodeObject对象中的。在类中,同样存在这样的分离现象。声明类的class语句,编译后的字节码指令存储在模块对应的PyCodeObject中,而类的实现、也就是类里面的逻辑,编译后的字节码指令序列则存储在类对应的的PyCodeObject中。所以我们在模块级别中只能找到类,无法直接找到类里面的成员。

    另外还可以看到,类的成员函数和一般的函数相同,也会有这种声明和实现分离的现象。其实也很好理解,就把类和函数想象成变量就行了,类名、函数名就是变量名,而类、函数里面的逻辑想象成值,一个变量对应一个值。

    s = """class Girl:
        name = "夏色祭"
    
        def __init__(self):
            print("__init__")
    
        def f(self):
            print("f")
    
        def g(self, name):
            self.name = name
            print(self.name)"""
    
    
    # 此时的code显然是模块对应的PyCodeObject对象
    code = compile(s, "class", "exec")
    print(code)  # <code object <module> at 0x000001B588101450, file "class", line 1>
    
    # 常量池里面存储了Girl对应的PyCodeObject对象
    print(code.co_consts[0])  # <code object Girl at 0x0000024BB6C7ABE0, file "class", line 1>
    
    # Girl的PyCodeObject对象的常量池里面存储了几个函数的PyCodeObject对象
    # 至于它们的位置我们暂时不需要关心
    print(code.co_consts[0].co_consts[6])  # <code object g at 0x000001FAC40A82F0, file "class", line 10>
    print(code.co_consts[0].co_consts[6].co_varnames)  # ('self', 'name')
    

    class对象的动态元信息

    class对象(class关键字创建的类)的元信息指的就是关于class的信息,比如说class的名称、它所拥有的的属性、方法,该class实例化时要为实例对象申请的内存空间大小等。对于模块中定义的class Girl来说,我们必须知道相应的信息:比如在class Girl中,有一个符号f,这个f对应一个函数;还有一个符号g,这个g也对应了一个函数。有了这些元信息,才能创建class对象,否则我们是没办法创建的。元信息是一个非常重要的概念,比如说Hive,数据的元信息就是存储在MySQL里面的,而在编程语言中,正是通过元信息才实现了反射等动态特性。而在Python中,元信息的概念被发挥的淋漓尽致,因此Python也提供了其他编程语言所不具备的高度灵活的动态特征。

    老规矩,下面还是看一下字节码:

    class Girl:
    
        name = "夏色祭"
        def __init__(self):
            print("__init__")
    
        def f(self):
            print("f")
    
        def g(self, name):
            self.name = name
            print(self.name)
    
    

    这里我们先不涉及调用,只看类的创建。

      1           0 LOAD_BUILD_CLASS
                  2 LOAD_CONST               0 (<code object Girl at 0x0000026FB0B3ABE0, file "class", line 1>)
                  4 LOAD_CONST               1 ('Girl')
                  6 MAKE_FUNCTION            0
                  8 LOAD_CONST               1 ('Girl')
                 10 CALL_FUNCTION            2
                 12 STORE_NAME               0 (Girl)
                 14 LOAD_CONST               2 (None)
                 16 RETURN_VALUE
    
    Disassembly of <code object Girl at 0x0000026FB0B3ABE0, file "class", line 1>:
      1           0 LOAD_NAME                0 (__name__)
                  2 STORE_NAME               1 (__module__)
                  4 LOAD_CONST               0 ('Girl')
                  6 STORE_NAME               2 (__qualname__)
    
      3           8 LOAD_CONST               1 ('夏色祭')
                 10 STORE_NAME               3 (name)
    
      4          12 LOAD_CONST               2 (<code object __init__ at 0x0000026FB0961450, file "class", line 4>)
                 14 LOAD_CONST               3 ('Girl.__init__')
                 16 MAKE_FUNCTION            0
                 18 STORE_NAME               4 (__init__)
    
      7          20 LOAD_CONST               4 (<code object f at 0x0000026FB095AB30, file "class", line 7>)
                 22 LOAD_CONST               5 ('Girl.f')
                 24 MAKE_FUNCTION            0
                 26 STORE_NAME               5 (f)
    
     10          28 LOAD_CONST               6 (<code object g at 0x0000026FB0B472F0, file "class", line 10>)
                 30 LOAD_CONST               7 ('Girl.g')
                 32 MAKE_FUNCTION            0
                 34 STORE_NAME               6 (g)
                 36 LOAD_CONST               8 (None)
                 38 RETURN_VALUE
    
    Disassembly of <code object __init__ at 0x0000026FB0961450, file "class", line 4>:
      5           0 LOAD_GLOBAL              0 (print)
                  2 LOAD_CONST               1 ('__init__')
                  4 CALL_FUNCTION            1
                  6 POP_TOP
                  8 LOAD_CONST               0 (None)
                 10 RETURN_VALUE
    
    Disassembly of <code object f at 0x0000026FB095AB30, file "class", line 7>:
      8           0 LOAD_GLOBAL              0 (print)
                  2 LOAD_CONST               1 ('f')
                  4 CALL_FUNCTION            1
                  6 POP_TOP
                  8 LOAD_CONST               0 (None)
                 10 RETURN_VALUE
    
    Disassembly of <code object g at 0x0000026FB0B472F0, file "class", line 10>:
     11           0 LOAD_FAST                1 (name)
                  2 LOAD_FAST                0 (self)
                  4 STORE_ATTR               0 (name)
    
     12           6 LOAD_GLOBAL              1 (print)
                  8 LOAD_FAST                0 (self)
                 10 LOAD_ATTR                0 (name)
                 12 CALL_FUNCTION            1
                 14 POP_TOP
                 16 LOAD_CONST               0 (None)
                 18 RETURN_VALUE
    
    

    字节码比较长,我们逐行分析,当然很多字节码我们都见过了,因此有的字节码介绍的时候就不会特别详细了。我们仔细观察一下字节码,会发现分为五个部分:模块的字节码、class Girl的字节码、class的三个函数的字节码。

    我们先来看看模块的字节码

      1           0 LOAD_BUILD_CLASS
                  2 LOAD_CONST               0 (<code object Girl at 0x0000026FB0B3ABE0, file "class", line 1>)
                  4 LOAD_CONST               1 ('Girl')
                  6 MAKE_FUNCTION            0
                  8 LOAD_CONST               1 ('Girl')
                 10 CALL_FUNCTION            2
                 12 STORE_NAME               0 (Girl)
                 14 LOAD_CONST               2 (None)
                 16 RETURN_VALUE
    
    • 0 LOAD_BUILD_CLASS: 我们注意到这又是一条我们没见过的新指令,从名字也能看出来这是要构建一个类;
    • 2 LOAD_CONST: 加载Girl对应的PyCodeObject对象;
    • 4 LOAD_CONST: 加载字符串"Girl"
    • 6 MAKE_FUNCTION: 问题来了, 我们看到出现了MAKE_FUNCTION, 不是说要构建类吗? 为什么是MAKE_FUNCTION呢? 别急, 往下看;
    • 8 LOAD_CONST: 再次加载字符串"Girl";
    • 10 CALL_FUNCTION: 你看到了什么?函数调用?是的, 这个CALL_FUNCTION是用来构建类的, 至于怎么构建我们后面会说;
    • 12 STORE_NAME: 将上一步构建好的类使用符号Girl保存;

    我们看一下LOAD_BUILD_CLASS这个指令都干了哪些事情吧。

            case TARGET(LOAD_BUILD_CLASS): {
                _Py_IDENTIFIER(__build_class__);
    
                PyObject *bc;
                if (PyDict_CheckExact(f->f_builtins)) {
                    //从f_builtins里面获取PyId___build_class__
                    bc = _PyDict_GetItemIdWithError(f->f_builtins, &PyId___build_class__);
                    if (bc == NULL) {
                        if (!_PyErr_Occurred(tstate)) {
                            _PyErr_SetString(tstate, PyExc_NameError,
                                             "__build_class__ not found");
                        }
                        goto error;
                    }
                    Py_INCREF(bc);
                }
                else {
                    PyObject *build_class_str = _PyUnicode_FromId(&PyId___build_class__);
                    if (build_class_str == NULL)
                        goto error;
                    bc = PyObject_GetItem(f->f_builtins, build_class_str);
                    if (bc == NULL) {
                        if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError))
                            _PyErr_SetString(tstate, PyExc_NameError,
                                             "__build_class__ not found");
                        goto error;
                    }
                }
                //入栈
                PUSH(bc);
                DISPATCH();
            }
    
    

    LOAD_BUILD_CLASS做的事情很简单,就是从Python的内置函数中取得__build_class__将其入栈,然后下面的几个指令很好理解,但是却出现了一个CALL_FUNCTION,显然它是调用__build_class__创建类的。我们看到它的参数个数是2个,这两个参数分别是:A的PyFunctionObject、字符串"A",因此:

    class A(object):
        pass
    
    
    # 在底层将会被翻译成
    A = __build_class__(<PyFunctionObject A>, "A")
    
    
    # 如果是
    class A(int):
        pass
    
    
    # 在底层将会被翻译成
    A = __build_class__(<PyFunctionObject A>, "A", int)
    

    我们实际操作一下:

    # 在Python中我们可以导入 builtins 来调用__build_class__,也可以直接使用
    import builtins
    
    # 构建一个类, 叫MyInt, 继承自int
    c = builtins.__build_class__(lambda: None, "MyInt", int)
    
    print(c.__name__)  # MyInt
    print(c.__base__)  # <class 'int'>
    
    
    print(c(3) * c(5))  # 15
    

    如果参数类型不正确的话,就会报出如下错误:

    import sys
    import builtins
    
    try:
        builtins.__build_class__()
    except Exception as e:
        exc_type, exc_value, _ = sys.exc_info()
        print(exc_type, exc_value)  # <class 'TypeError'> __build_class__: not enough arguments
    
    try:
        builtins.__build_class__("", "")
    except Exception as e:
        exc_type, exc_value, _ = sys.exc_info()
        print(exc_type, exc_value)  # <class 'TypeError'> __build_class__: func must be a function
    
    try:
        builtins.__build_class__(lambda: 123, 123)
    except Exception as e:
        exc_type, exc_value, _ = sys.exc_info()
        print(exc_type, exc_value)  # <class 'TypeError'> __build_class__: name is not a string
    

    记住这几个报错信息,后面马上就会看到。此外我们也看到,这个函数的一个参数叫func、第二个参数叫name。

    所以现在就明白为什么会出现CALL_FUNCTION这条指令,__build_class__就是用来将一个函数对象变成一个class对象。

    class对象的字节码

      1           0 LOAD_NAME                0 (__name__)
                  2 STORE_NAME               1 (__module__)
                  4 LOAD_CONST               0 ('Girl')
                  6 STORE_NAME               2 (__qualname__)
    
      3           8 LOAD_CONST               1 ('夏色祭')
                 10 STORE_NAME               3 (name)
    
      4          12 LOAD_CONST               2 (<code object __init__ at 0x0000026FB0961450, file "class", line 4>)
                 14 LOAD_CONST               3 ('Girl.__init__')
                 16 MAKE_FUNCTION            0
                 18 STORE_NAME               4 (__init__)
    
      7          20 LOAD_CONST               4 (<code object f at 0x0000026FB095AB30, file "class", line 7>)
                 22 LOAD_CONST               5 ('Girl.f')
                 24 MAKE_FUNCTION            0
                 26 STORE_NAME               5 (f)
    
     10          28 LOAD_CONST               6 (<code object g at 0x0000026FB0B472F0, file "class", line 10>)
                 30 LOAD_CONST               7 ('Girl.g')
                 32 MAKE_FUNCTION            0
                 34 STORE_NAME               6 (g)
                 36 LOAD_CONST               8 (None)
                 38 RETURN_VALUE
    

    对于一个类而言,调用其__module__属性,可以获取所在的模块。所以开始的LOAD_NAME和STORE_NAME是将符号__module__和全局命名空间中符号__name__的值关联了起来,并放入到该类的local名字空间中。

    需要说明的是,我们在介绍函数的时候提过,当时我们说:"函数的局部变量是不可变的,在编译的时候就已经确定了,是以一种静态方式放在了运行时栈前面的那段内存中,并没有放在f_locals中,f_locals其实是一个NULL,我们通过locals()拿到的只是对运行时栈前面的内存的一个拷贝,函数里面的局部变量是通过静态方式来访问的"。但是类则不一样,类是可以动态修改的,可以随时增加属性、方法,这就意味着类是不可能通过静态方式来查找属性的。而事实上也确实如此,类也有一个f_locals,但它指向的就不再是NULL了,而和f_globals一样,也是一个PyDictObject对象。然后是LOAD_CONST,将字符串"Girl"加载进来,和__qualname__组成一个entry存储在Girl的local空间中。

    class Girl:
    
        name = "夏色祭"
        def __init__(self):
            print("__init__")
    
        def f(self):
            print("f")
    
        def g(self, name):
            self.name = name
            print(self.name)
    
    
    print(__name__)  # __main__
    print(Girl.__module__)  # __main__
    print(Girl.__qualname__)  # Girl
    

    所以整体过程就是:先将PyCodeObject构建成函数,再通过__build_class__将函数变成一个类,当然__build_class__结束之后我们的Girl这个类就横空出世了。

    因此剩下的来问题就是__build_class__是如何将一个函数变成类的,想要知道答案,那么只能去源码中一探究竟了。不过在看源码之前,我们还需要了解一样东西:metaclass。

    回顾metaclass

    元类,被誉为是深度的魔法,但是个人觉得有点夸张了。首先元类是做什么的,它是用来控制我们类的生成过程的,默认情况下,我们自定义的类都是由type创建的。但是我们可以手动指定某个类的元类,但是在介绍元类之前,我们还需要看一下Python中的两个特殊的魔法方法:__new__和__init__。

    __new__和__init__

    类在实例化的时候会自动调用__init__,但其实在调用__init__之前会先调用__new__。

    • __new__: 为实例对象申请一片内存;
    • __init__: 为实例对象设置属性;
    class A:
    
        def __new__(cls, *args, **kwargs):
            print("__new__")
    
        def __init__(self):
            print("__init__")
    
    
    A()  # __new__
    

    然而我们看到只有__new__被调用了,__init__则没有。原因就在于__new__中必须将A的实例对象返回,才会执行__init__,并且执行的时候会自动将__new__的返回值作为参数传给self。

    class A:
    
        def __new__(cls, *args, **kwargs):
            print("__new__")
            # 这里的参数cls就表示A这个类本身
            # object.__new__(cls) 便是根据cls创建cls的实例对象
            return object.__new__(cls)
    
        def __init__(self):
            # 然后执行__init__, 里面的self指的就是实例对象
            # 在执行__init__的时候, __new__的返回值会自动作为参数传递给这里的self
            print("__init__")
    
    
    A()  
    """
    __new__
    __init__
    """
    

    所以一个对象是什么,取决于其类型对象的__new__返回了什么。

    class A:
    
        def __new__(cls, *args, **kwargs):
            print("__new__")
            # 这里必须返回A的实例对象, 否则__init__函数是不会执行的
            return 123
    
        def __init__(self):
            print("__init__")
    
    
    a = A()
    print(a + 1)  
    """
    __new__
    124
    """
    

    我们看到A在实例化之后得到的是一个整型,原因就是__new__返回了123。最后一个就是参数问题,首先我们说__new__是创建实例对象的,__init__是为实例对象绑定属性的。

    class A:
    
        def __new__(cls, name, age):
            # __new__里面的参数一定要和__init__是匹配的, 除了第一个参数之外
            return object.__new__(cls)
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
    
    a = A("夏色祭", -1)
    # 我们这里传入了两个参数, 那么: A、"夏色祭"、-1 就会组合起来, 分别传给__new__的 cls、name、age
    # 然后__new__里面返回了一个实例对象
    # 那么: object.__new__(cls)、__new__接收的name、__new__接收的age 会组合起来, 分别传给__init__的 self、name、age
    

    创建类的另一种方式

    创建类的时候可以使用class关键字创建,除了class关键字之外,我们还可以使用type这个古老却又强大的类来创建。

    # type这个类里面可以接收一个参数或者三个参数
    # 如果接收一个参数, 那么表示查看类型; 如果接收三个参数, 那么表示创建一个类
    
    try:
        A = type("A", "")
    except Exception as e:
        print(e)  # type() takes 1 or 3 arguments
    

    告诉我们type要么接收一个参数,要么接收三个参数。显然接收一个参数查看类型不需要再说了,我们看看怎么用来用type创建一个类。

    # type接收的三个参数: 类名、继承的基类、属性
    class A(list):
        name = "夏色祭"
    
    # 上面这个类翻译过来就是
    val = type("A", (list, ), {"name": "夏色祭"})
    print(val)  # <class '__main__.A'>
    print(val.__name__)  # A
    print(val.__base__)  # <class 'list'>
    print(val.name)  # 夏色祭
    

    所以还是很简单的,我们还可以自定义一个类继承自type。

    class MyType(type):
    
        def __new__(mcs, name, bases, attr):
            print(name)
            print(bases)
            print(attr)
    
    
    # 指定metaclass, 表示A这个类由MyType创建
    # 我们说__new__是为实例对象开辟内存的, 那么MyType的实例对象是谁呢? 显然就是这里的A
    # 因为A指定了metaclass为MyType, 所以A的类型就是MyType
    class A(int, object, metaclass=MyType):
        name = "夏色祭"
    """
    A
    (<class 'int'>, <class 'object'>)
    {'__module__': '__main__', '__qualname__': 'A', 'name': '夏色祭'}
    """
    
    # 我们看到一个类在创建的时候会向元类的__new__中传递三个值
    # 分别是类名、继承的基类、类的属性
    
    # 但是此时A并没有被创建出来
    print(A)  # None
    
    """
    我们说__new__一定要将创建的实例对象返回才可以, 这里的MyType是元类
    所以类对象A就等于MyType的实例对象, MyType的__new__就负责为类对象A分配空间
    但是显然我们这里并没有分配, 而且返回的还是一个None, 如果我们返回的是123, 那么print(a)就是123
    """
    

    所以元类和类之间的关系 和 类与实例对象的关系,之间是很相似的,因为完全可以把类对象看成是元类的实例对象。因此A既然指定了metaclass为MyType,表示A这个类由MyType创建,那么MyType的__new__函数返回了什么,A就是什么。

    class MyType(type):
    
        def __new__(mcs, name, bases, attr):
            return "嘿嘿嘿"
    
    
    class A(metaclass=MyType):
        pass
    
    
    print(A + "哟哟哟")  # 嘿嘿嘿哟哟哟
    

    这便是Python语言具备的高度动态特性,那么问题来了,如果我想把A创建出来、像普通的类一样使用的话,该咋办呢?因为默认情况下是由type创建,底层帮你做好了,但是现在是我们手动指定元类,那么一切就需要我们来手动指定了。显然,这里创建还是要依赖于type,只不过需要我们手动指定,而且在手动指定的同时我们还可以增加一些我们自己的操作。

    class MyType(type):
    
        def __new__(mcs, name, bases, attr):
            name = name * 2
            bases = (list,)
            attr.update({"name": "神乐mea", "nickname": "屑女仆"})
    
            # 这里直接交给type即可, 然后type来负责创建
            # 所以super().__new__实际上会调用type.__new__
            # type(name, bases, attr) 等价于 type.__new__(type, name, bases, attr)
            return super().__new__(mcs, name, bases, attr)
            # 但是这里我们将__new__的第一个参数换成了mcs, 也就是这里的MyType
            # 等价于type.__new__(mcs, name, bases, attr)表示将元类设置成MyType
            # 注意: 不能写type(name, bases, attr), 因为这样的话类还是由type创建的
    
    
    class Girl(metaclass=MyType):
        pass
    
    
    # 我们看到类的名字变了, 默认情况下是Girl, 但是我们在创建的时候将name成了个2
    print(Girl.__name__)  # GirlGirl
    
    # 那么显然Girl这里也要继承自list
    print(Girl("你好呀"))  # ['你', '好', '呀']
    
    # 同理Girl还有两个属性
    print(Girl.name, Girl.nickname)  # 神乐mea 屑女仆
    

    我们之前还说过,一个类在没有指定的metaclass的时候,如果它的父类指定了,那么这个类的metaclass等于父类的metaclass。

    class MyType(type):
    
        def __new__(mcs, name, bases, attr):
            name = name * 2
            bases = (list,)
            attr.update({"name": "神乐mea", "nickname": "屑女仆"})
            return super().__new__(mcs, name, bases, attr)
    
    
    class Girl(metaclass=MyType):
        pass
    
    
    class A(Girl):
        pass
    
    
    print(A.__class__)  # <class '__main__.MyType'>
    print(A.__name__)  # AA
    

    我们之前还举了个flask的例子,一种更加优雅的写法。

    class MyType(type):
    
        def __new__(mcs, name, bases, attr):
            return super().__new__(mcs, name, bases, attr)
    
    
    def with_metaclass(meta, bases):
        return meta("tmp", bases, {"gender": "female"})
    
    
    # with_metaclass(MyType, (list,))便会返回一个类
    # 这个类由MyType创建, 并且继承自list
    # 那么Girl再继承这个类, 等价于Girl也是有MyType创建, 并且也会继承自list
    class Girl(with_metaclass(MyType, (list,))):
        pass
    
    print(Girl.__class__)  # <class '__main__.MyType'>
    print(Girl.__bases__)  # (<class '__main__.tmp'>,)
    print(Girl.__mro__)  # (<class '__main__.Girl'>, <class '__main__.tmp'>, <class 'list'>, <class 'object'>)
    
    # 所以with_metaclass(meta, bases)只是为了帮助我们找到元类和继承的类
    # 至于其本身并没有太大的意义, 但我们毕竟继承它了, 就意味着我们也可以找到它的属性
    print(Girl.gender)  # female
    

    注意:我们说创建类的对象是元类,元类要么是type、要么是继承自type的子类。

    class MyType(type):
    
        def __new__(mcs, name, bases, attr):
            return super().__new__(mcs, name, bases, attr)
    
    
    # type直接加括号表示由type创建, 我们需要通过__new__手动指定
    Girl = type.__new__(MyType, "GirlGirlGirl", (list,), {"foo": lambda self, value: value + 123})
    print(Girl.__name__)  # GirlGirlGirl
    
    g = Girl()
    print(g.foo(123))  # 246
    
    
    try:
        type.__new__(int, "A", (object,), {})
    except TypeError as e:
        # 指定为int则报错, 告诉我们int不是type的子类
        # 因为只有两种情况: 要么是type、要么是type的子类
        print(e)  # type.__new__(int): int is not a subtype of type
    

    怎么样,是不是觉得元类很简单呢?其实元类没有什么复杂的。

    再举个例子:

    class MyType(type):
    
        def __new__(mcs, name, bases, attr):
            if "f" in attr:
                attr.pop("f")
            return super().__new__(mcs, name, bases, attr)
    
    
    class Girl(metaclass=MyType):
    
        def f(self):
            return "f"
    
        def g(self):
            return "g"
    
    
    print(Girl().g())  # g
    try:
        print(Girl().f())
    except AttributeError as e:
        print(e)  # 'Girl' object has no attribute 'f'
    
    """
    惊了, 我们看到居然没有f这个属性, 我们明显定义了啊, 原因就是我们在创建类的时候将其pop掉了
    首先创建一个类需要三个元素: 类名、继承的基类、类的一些属性(以字典的形式, 属性名: 属性值)
    
    然后会将这三个元素交给元类进行创建, 但是我们在创建的时候偷偷地将f从attr里面给pop掉了
    因此创建出来的类是没有f这个函数的 
    """ 
    

    元类确实蛮有趣的,而且也没有想象中的那么难,可以多了解一下。

    特殊的魔法函数

    此外我们再来看两个和元类有关的魔法函数:

    __prepared__

    class MyType(type):
    
        @classmethod
        def __prepare__(mcs, name, bases):
            print("__prepared__")
            # 必须返回一个mapping, 至于它是干什么的我们后面说
            return {}
    
        def __new__(mcs, name, bases, attr):
            print("__new__")
            return super().__new__(mcs, name, bases, attr)
    
    
    class Girl(metaclass=MyType):
        pass
    """
    __prepared__
    __new__
    """
    

    我们看到__prepare__会在__new__方法之前被调用,那么它是做什么的呢?答案是添加属性的,我们解释一下。

    class MyType(type):
    
        @classmethod
        def __prepare__(mcs, name, bases):
            return {"name": "夏色祭"}
    
        def __new__(mcs, name, bases, attr):
            return super().__new__(mcs, name, bases, attr)
    
    
    class Girl(metaclass=MyType):
    
        def f(self):
            return "f"
    
        def g(self):
            return "g"
    
    
    print(Girl.name)  # 夏色祭
    
    # 现在你应该知道__prepare__是干什么的了吧, 它接收一个name、一个bases, 返回有个mapping
    # 我们说name、bases、attr会传递给__new__, 但是在__new__之前会先经过__prepared__
    # __prepared__返回一个字典(mapping), 假设叫m吧, 那会将attr和m合并, 相当于执行了attr.update(m)
    # 然后再将 name、bases、attr交给__new__
    

    此外__prepared__这个方法是被classmethod装饰的,另外里面一定要返回一个mapping,否则报错:TypeError: MyType.__prepare__() must return a mapping, not xxx

    __init_subclass__

    它类似于一个钩子函数,在一些简单地场景下可以代替元类。

    class Base:
    
        def __init_subclass__(cls, **kwargs):
            print(cls, kwargs)
    
    
    # 当A被创建的时候, 会触发其父类的__init_subclass__
    class A(Base):
        pass
    """
    <class '__main__.A'> {}
    """
    
    
    class B(Base, name="夏色祭", age=-1):
        pass
    """
    <class '__main__.B'> {'name': '夏色祭', 'age': -1}
    """
    

    所以父类的__init_subclass__里面的cls并不是父类本身,而是继承它的类。kwargs,就是额外设置的一些属性。因此我们可以实现一个属性添加器。

    class Base:
    
        def __init_subclass__(cls, **kwargs):
            for k, v in kwargs.items():
                setattr(cls, k, v)
    
    
    class A(Base, name="夏色祭", age=-1, __str__=lambda self: "__str__" ):
        pass
    
    
    print(A.name, A.age)  # 夏色祭 -1
    print(A())  # __str__
    

    除了属性添加器,我们还可以实现一个属性拦截器。

    class Base:
    
        def __init_subclass__(cls, **kwargs):
            if hasattr(cls, "yoyoyo") and hasattr(cls.yoyoyo, "__code__"):
                raise Exception(f"{cls.__name__}不允许定义'yoyoyo'函数")
    
    
    class A(Base):
        yoyoyo = 123
    
    
    # 由于在创建类的时候就会触发, 所以必须加上try语句
    try:
        class B(Base):
            def yoyoyo(self):
                pass
    except Exception as e:
        print(e)  # B不允许定义'yoyoyo'函数
    

    有了这些元类相关的知识,我们后面在分析源码的时候就会轻松一些。

    源码分析类机制与metaclass

    我们说LOAD_BUILD_CLASS是将一个PyFunctionObject变成一个类,尽管它写在最前面,但实际上是需要将class A对应的PyCodeObject对象包装成一个PyFunctionObject对象之后才能执行。我们说__build_class__是用来将PyFunctionObject变成类的函数,我们来看看它长什么样子。

    //python/bltinmodule.c
    static PyMethodDef builtin_methods[] = {
        {"__build_class__", (PyCFunction)(void(*)(void))builtin___build_class__,
         METH_FASTCALL | METH_KEYWORDS, build_class_doc},
        //...
        //...
    }    
    
    static PyObject *
    builtin___build_class__(PyObject *self, PyObject *const *args, Py_ssize_t nargs,
                            PyObject *kwnames)
    {
        PyObject *func, *name, *bases, *mkw, *meta, *winner, *prep, *ns, *orig_bases;
        PyObject *cls = NULL, *cell = NULL;
        int isclass = 0;   /* initialize to prevent gcc warning */
    	
        //我们说了底层调用的是builtin___build_class__
        //class A: 会被翻译成builtin.__build_class__(PyFunctionObject, "class name")
        //所以这个函数至少需要两个参数
        if (nargs < 2) {
            //参数不足,报错,还记的这个报错信息吗?上面测试过的
            PyErr_SetString(PyExc_TypeError,
                            "__build_class__: not enough arguments");
            return NULL;
        }
        //类对应的PyFunctionObject
        func = args[0];   /* Better be callable */
        if (!PyFunction_Check(func)) {
            //如果不是PyFunctionObject,报错,这个信息有印象吗?
            PyErr_SetString(PyExc_TypeError,
                            "__build_class__: func must be a function");
            return NULL;
        }
        
        //类对应的名字,__build_class__的时候 总要给类起一个名字吧
        name = args[1];
        if (!PyUnicode_Check(name)) {
            //如果不是一个PyUnicodeObject,报错,这个有印象吗?
            PyErr_SetString(PyExc_TypeError,
                            "__build_class__: name is not a string");
            return NULL;
        }
        
        //原始基类
        orig_bases = _PyTuple_FromArray(args + 2, nargs - 2);
        if (orig_bases == NULL)
            return NULL;
    	
        //获取class的基类列表
        bases = update_bases(orig_bases, args + 2, nargs - 2);
        if (bases == NULL) {
            Py_DECREF(orig_bases);
            return NULL;
        }
    	
        if (kwnames == NULL) {
            meta = NULL;
            mkw = NULL;
        }
        else {
            mkw = _PyStack_AsDict(args + nargs, kwnames);
            if (mkw == NULL) {
                Py_DECREF(bases);
                return NULL;
            }
    		
            //这里获取meta
            meta = _PyDict_GetItemIdWithError(mkw, &PyId_metaclass);
            if (meta != NULL) {
                Py_INCREF(meta);
                if (_PyDict_DelItemId(mkw, &PyId_metaclass) < 0) {
                    Py_DECREF(meta);
                    Py_DECREF(mkw);
                    Py_DECREF(bases);
                    return NULL;
                }
                /* metaclass is explicitly given, check if it's indeed a class */
                isclass = PyType_Check(meta);
            }
            else if (PyErr_Occurred()) {
                Py_DECREF(mkw);
                Py_DECREF(bases);
                return NULL;
            }
        }
        //如果meta为NULL,这意味着用户没有指定metaclass
        if (meta == NULL) {
            //然后尝试获取基类,如果没有基类
            if (PyTuple_GET_SIZE(bases) == 0) {
                //指定metaclass为type
                meta = (PyObject *) (&PyType_Type);
            }
            //否则获取第一个继承的基类的metaclass
            else {
                PyObject *base0 = PyTuple_GET_ITEM(bases, 0);//拿到第一个基类
                meta = (PyObject *) (base0->ob_type);//拿到第一个基类的__class__
            }
            Py_INCREF(meta);//meta也是一个类
            isclass = 1;  /* meta is really a class */
        }
    	
        //如果设置了元类, 那么isclass为1, 会执行下面的代码
        if (isclass) {
            //既然已经选择出了元类, 那么这一步是做什么的呢?
            //这一步是为了解决元类冲突的, 假设有两个继承type的元类MyType1和MyType2, 然后Base1的元类是MyType1、Base2的元类是MyType2
            //那么如果class A(Base1, Base2)的话, 就会报错
            //在Python中有一个要求, 假设class A(Base1, Base2, ..., BaseN), Base1的元类叫Type1、BaseN的元类叫TypeN
            //那么必须满足:
            /*
            Type1是Type2的子类或者父类;
            Type1是Type3的子类或者父类;
            Type1是Type4的子类或者父类;
            ....
            Type1是TypeN的子类或者父类;
            */
            //而之所以存在这一限制, 原因就是为了避免属性冲突
            winner = (PyObject *)_PyType_CalculateMetaclass((PyTypeObject *)meta,
                                                            bases);
            if (winner == NULL) {
                Py_DECREF(meta);
                Py_XDECREF(mkw);
                Py_DECREF(bases);
                return NULL;
            }
            if (winner != meta) {
                Py_DECREF(meta);
                meta = winner;
                Py_INCREF(meta);
            }
        }
        /* else: meta is not a class, so we cannot do the metaclass
           calculation, so we will use the explicitly given object as it is */
        
        //寻找__prepare__方法
        if (_PyObject_LookupAttrId(meta, &PyId___prepare__, &prep) < 0) {
            ns = NULL;
        }
        //这个__prepare__方法必须返回一个mapping,如果返回None,那么默认返回一个空字典
        else if (prep == NULL) {
            ns = PyDict_New();
        }
        else {
            //否则将字典返回
            PyObject *pargs[2] = {name, bases};
            //我们看到这里涉及到了一个函数调用, 这个函数应该有印象吧
            ns = _PyObject_FastCallDict(prep, pargs, 2, mkw);
            Py_DECREF(prep);
        }
        if (ns == NULL) {
            Py_DECREF(meta);
            Py_XDECREF(mkw);
            Py_DECREF(bases);
            return NULL;
        }
        if (!PyMapping_Check(ns)) {
            //如果返回的不是一个字典,那么报错,这个错误等信息我们也见过了
            PyErr_Format(PyExc_TypeError,
                         "%.200s.__prepare__() must return a mapping, not %.200s",
                         isclass ? ((PyTypeObject *)meta)->tp_name : "<metaclass>",
                         Py_TYPE(ns)->tp_name);
            goto error;
        }
        //......
    }
    
    

    可以看到,一个简单的类定义,Python底层究竟做了多少事情啊,不过显然这还没完。

    我们前面说,Python虚拟机获得了关于class的属性表(动态元信息),比如所有的方法、属性,所以我们可以说,class的动态元信息包含了class的所有属性。但是对于这个class对象的类型是什么,应该如何创建、要分配多少内存,却没有任何的信息。而在builtin___build_class__中,metaclass正是关于class对象的另一部分元信息,我们称之为静态元信息。在静态元信息中,隐藏着所有的类对象应该如何创建的信息,注意:是所有的类对象。

    从源码中我们可以看到,如果用户指定了metaclass,那么会选择指定的metaclass,如果没有指定,那么会使用第一个继承的基类的__class__作为该class的metaclass。

    对于PyLongObject、PyDictObject这些Python中的实例对象,所有的元信息存储在对应的类对象中(PyLong_Type,PyDict_Type)。但是对于类对象来说,其元信息的静态元信息存储在对应的元类(PyType_Type)中,动态元信息则存储在本身的local名字空间中。但是为什么这么做呢?为什么对于类对象来说,其元信息要游离成两部分呢?都存在metaclass里面不香吗?这是因为,用户在.py文件中可以定义不同的class,这个元信息必须、且只能是动态的,所以它是不适合保存在metaclass中的,因此类对象的创建策略等这些所有class都会共用的元信息,会存储在metaclass里面。

    像Python的内建对象都是Python静态提供的,它们都具备相同的接口集合(底层都是PyTypeObject结构体实例),支持什么操作一开始就定义好了。只不过有的可以用,有的不能用。比如PyLongObject可以使用nb_add,但是PyDictObject不能。而PyDictObject可以使用mp_subscript,但是PyLongObject不可以。尽管如此,但这不影响它们的所有元信息都可以完全存储在类型对象中。但是用户自定义的class对象,接口是动态的,不可能在metaclass中静态指定。

    既然创建了元类,那么下面显然就开始调用了。通过函数 PyObject_Call 调用。

    //Objects/call.c
    PyObject *
    PyObject_Call(PyObject *callable, PyObject *args, PyObject *kwargs)
    {
        //...
        else {
            //调用了tp_call,指向type_call
            call = callable->ob_type->tp_call;
            if (call == NULL) {
                PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable",
                             callable->ob_type->tp_name);
                return NULL;
            }
    
            //......
        }
    }
    
    
    //Objects/typeobject.c
    static PyObject *
    type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
    {
        PyObject *obj;
    	
        if (type->tp_new == NULL) {
            PyErr_Format(PyExc_TypeError,
                         "cannot create '%.100s' instances",
                         type->tp_name);
            return NULL;
        }
        //调用tp_new申请内存
        obj = type->tp_new(type, args, kwds);
        obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL);
        if (obj == NULL)
            return NULL;
    
        //如果是PyType_Type, 那么执行完__new__之后直接返回
        if (type == &PyType_Type &&
            PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 &&
            (kwds == NULL ||
             (PyDict_Check(kwds) && PyDict_GET_SIZE(kwds) == 0)))
            return obj;
    
        //还记得我们之前说过, __new__里面一定要返回类的实例对象, 否则是不会执行__init__函数的
        //从这里我们也看到了, 如果obj的类型不是对应的类、或者其子类, 那么直接返回
        if (!PyType_IsSubtype(Py_TYPE(obj), type))
            return obj;
    	
        //然后获取obj的类型
        type = Py_TYPE(obj);
        //如果存在__init__函数, 那么执行构造函数
        if (type->tp_init != NULL) {
            int res = type->tp_init(obj, args, kwds);
            if (res < 0) {
                assert(PyErr_Occurred());
                Py_DECREF(obj);
                obj = NULL;
            }
            else {
                assert(!PyErr_Occurred());
            }
        }
        //执行完构造函数之后, 再将实例对象返回
        //注意: 执行了__init__说明obj是实例对象, 如果obj是类对象, 那么是不会走到这里来的
        //执行完元类的__new__之后就返回了
        return obj;
    }
    

    tp_new指向type_new,这个type_new是我们创建class对象的第一案发现场。我们看一下type_new的源码,位于 Objects/typeobject.c 中,这个函数的代码比较长,我们会有删减,像那些检测的代码我们就省略掉了。

    static PyObject *
    type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
    {	
        //都是类的一些动态元信息
        PyObject *name, *bases = NULL, *orig_dict, *dict = NULL;
        PyObject *qualname, *slots = NULL, *tmp, *newslots, *cell;
        PyTypeObject *type = NULL, *base, *tmptype, *winner;
        PyHeapTypeObject *et;
        PyMemberDef *mp;
        Py_ssize_t i, nbases, nslots, slotoffset, name_size;
        int j, may_add_dict, may_add_weak, add_dict, add_weak;
        _Py_IDENTIFIER(__qualname__);
        _Py_IDENTIFIER(__slots__);
        _Py_IDENTIFIER(__classcell__);
    	
        //如果metaclass是type的话
        if (metatype == &PyType_Type) {
            //获取位置参数和关键字参数个数
            const Py_ssize_t nargs = PyTuple_GET_SIZE(args);
            const Py_ssize_t nkwds = kwds == NULL ? 0 : PyDict_GET_SIZE(kwds);
    		
            //位置参数为1,关键字参数为0,你想到了什么
            //type(xxx),是不是这个呀
            if (nargs == 1 && nkwds == 0) {
                PyObject *x = PyTuple_GET_ITEM(args, 0);
                Py_INCREF(Py_TYPE(x));
                //这显然是初学Python的时候,就知道的,查看一个变量的类型。
                //获取类型之后直接返回
                return (PyObject *) Py_TYPE(x);
            }
    
            //如果上面的if不满足,会走这里,表示现在不再是查看类型了,而是创建类
            //而这里要求位置参数必须是3个,否则报错。
            //我们知道type查看类型,输入一个参数即可,但是创建类需要3个
            if (nargs != 3) {
                PyErr_SetString(PyExc_TypeError,
                                "type() takes 1 or 3 arguments");
                return NULL;
            }
        }
    
        /* Check arguments: (name, bases, dict) */
        //现在显然是确定参数类型,对于type来说,你传递了三个参数,但是这三个参数是有类型要求的
        //必须是PyUnicodeObject、PyTupleObject、PyDictObject
        if (!PyArg_ParseTuple(args, "UO!O!:type.__new__", &name, &PyTuple_Type,
                              &bases, &PyDict_Type, &orig_dict))
        /*
        type(123, (object, ), {})  # TypeError: type.__new__() argument 1 must be str, not int
        type("xx", [object], {})  # TypeError: type.__new__() argument 2 must be tuple, not list
        type("xx", (object, ), [])  # TypeError: type.__new__() argument 3 must be dict, not list
        */        
            return NULL;
    
        //处理bases为空的情况,另外我们使用class关键字定义类,本质上会转为type定义类的方式
        nbases = PyTuple_GET_SIZE(bases);
        if (nbases == 0) {
            //如果发现我们没有继承基类,那么在Python3中会默认继承object
            base = &PyBaseObject_Type; //base设置为object
            bases = PyTuple_Pack(1, base); //bases设置为(object,)
            if (bases == NULL)
                return NULL;
            nbases = 1;
        }
        else {
            _Py_IDENTIFIER(__mro_entries__);
            //如果我们继承了基类
            //那么循环遍历bases
            for (i = 0; i < nbases; i++) {
                //拿到每一个基类
                tmp = PyTuple_GET_ITEM(bases, i);
                //如果是PyType_Type类型,进行下一次循环
                if (PyType_Check(tmp)) {
                    continue;
                }
                if (_PyObject_LookupAttrId(tmp, &PyId___mro_entries__, &tmp) < 0) {
                    return NULL;
                }
                if (tmp != NULL) {
                    PyErr_SetString(PyExc_TypeError,
                                    "type() doesn't support MRO entry resolution; "
                                    "use types.new_class()");
                    Py_DECREF(tmp);
                    return NULL;
                }
            }
            /* Search the bases for the proper metatype to deal with this: */
            //寻找父类的metaclass, 就是我们之前说的解决元类冲突所采取的策略
            winner = _PyType_CalculateMetaclass(metatype, bases);
            if (winner == NULL) {
                return NULL;
            }
    
            if (winner != metatype) {
                if (winner->tp_new != type_new) /* Pass it to the winner */
                    return winner->tp_new(winner, args, kwds);
                metatype = winner;
            }
    
            /* Calculate best base, and check that all bases are type objects */
            //确定最佳base,存储在PyTypeObject *base中
            base = best_base(bases);
            if (base == NULL) {
                return NULL;
            }
    
            Py_INCREF(bases);
        }
    
        /* Use "goto error" from this point on as we now own the reference to "bases". */
    
        dict = PyDict_Copy(orig_dict);
        if (dict == NULL)
            goto error;
    
        //处理用户定义了__slots__属性的逻辑,一旦程序猿定义了__slots__, 那么类的实例对象就没有属性字典了
        slots = _PyDict_GetItemIdWithError(dict, &PyId___slots__);
        nslots = 0;
        add_dict = 0;
        add_weak = 0;
        may_add_dict = base->tp_dictoffset == 0;
        may_add_weak = base->tp_weaklistoffset == 0 && base->tp_itemsize == 0;
        if (slots == NULL) {
            //.....
        }
        else {
            //.....
        }
    
        /* Allocate the type object */
        //为class对象申请内存
        type = (PyTypeObject *)metatype->tp_alloc(metatype, nslots);
        if (type == NULL)
            goto error;
    
        /* Keep name and slots alive in the extended type object */
        et = (PyHeapTypeObject *)type;
        Py_INCREF(name);
        et->ht_name = name;
        et->ht_slots = slots;
        slots = NULL;
    
        /* 初始化tp_flags */
        type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE |
            Py_TPFLAGS_BASETYPE;
        if (base->tp_flags & Py_TPFLAGS_HAVE_GC)
            type->tp_flags |= Py_TPFLAGS_HAVE_GC;
    
         //设置PyTypeObject中的各个域
        type->tp_as_async = &et->as_async;
        type->tp_as_number = &et->as_number;
        type->tp_as_sequence = &et->as_sequence;
        type->tp_as_mapping = &et->as_mapping;
        type->tp_as_buffer = &et->as_buffer;
        type->tp_name = PyUnicode_AsUTF8AndSize(name, &name_size);
        if (!type->tp_name)
            goto error;
        if (strlen(type->tp_name) != (size_t)name_size) {
            PyErr_SetString(PyExc_ValueError,
                            "type name must not contain null characters");
            goto error;
        }
    
        /* 设置基类和基类列表 */
        type->tp_bases = bases;
        bases = NULL;
        Py_INCREF(base);
        type->tp_base = base;
    
        /* 设置属性表 */
        Py_INCREF(dict);
        type->tp_dict = dict;
    
        //设置__module__
        if (_PyDict_GetItemIdWithError(dict, &PyId___module__) == NULL) {
            if (PyErr_Occurred()) {
                goto error;
            }
            tmp = PyEval_GetGlobals();
            if (tmp != NULL) {
                tmp = _PyDict_GetItemIdWithError(tmp, &PyId___name__);
                if (tmp != NULL) {
                    if (_PyDict_SetItemId(dict, &PyId___module__,
                                          tmp) < 0)
                        goto error;
                }
                else if (PyErr_Occurred()) {
                    goto error;
                }
            }
        }
    
         //设置__qualname__,即"全限定名"
        qualname = _PyDict_GetItemIdWithError(dict, &PyId___qualname__);
        //......
    
        //如果自定义的class中重写了__new__方法,将__new__对应的函数改造为static函数
        tmp = _PyDict_GetItemIdWithError(dict, &PyId___new__);
        if (tmp != NULL && PyFunction_Check(tmp)) {
            tmp = PyStaticMethod_New(tmp);
            if (tmp == NULL)
                goto error;
            if (_PyDict_SetItemId(dict, &PyId___new__, tmp) < 0) {
                Py_DECREF(tmp);
                goto error;
            }
            Py_DECREF(tmp);
        }
        else if (tmp == NULL && PyErr_Occurred()) {
            goto error;
        }
    
        //设置__init_subclass__,如果子类继承了父类,那么会触发父类的__init_subclass__方法
        tmp = _PyDict_GetItemIdWithError(dict, &PyId___init_subclass__);
        if (tmp != NULL && PyFunction_Check(tmp)) {
            tmp = PyClassMethod_New(tmp);
            if (tmp == NULL)
                goto error;
            if (_PyDict_SetItemId(dict, &PyId___init_subclass__, tmp) < 0) {
                Py_DECREF(tmp);
                goto error;
            }
            Py_DECREF(tmp);
        }
        else if (tmp == NULL && PyErr_Occurred()) {
            goto error;
        }
    	
        //设置__class_getitem__,这个是什么?类似于__getitem__
        //__class_getitem__支持通过 类["xxx"] 的方式访问
        tmp = _PyDict_GetItemIdWithError(dict, &PyId___class_getitem__);
        //......
        
        //为class对象对应的instance对象设置内存大小信息 
        type->tp_basicsize = slotoffset;
        type->tp_itemsize = base->tp_itemsize;
        type->tp_members = PyHeapType_GET_MEMBERS(et);
        //......
        
        
        //调用PyType_Ready对class对象进行初始化
        if (PyType_Ready(type) < 0)
            goto error;
    
        /* Put the proper slots in place */
        fixup_slot_dispatchers(type);
    
        if (type->tp_dictoffset) {
            et->ht_cached_keys = _PyDict_NewKeysForClass();
        }
    
        if (set_names(type) < 0)
            goto error;
    
        if (init_subclass(type, kwds) < 0)
            goto error;
    
        Py_DECREF(dict);
        return (PyObject *)type;
    
    error:
        Py_XDECREF(dict);
        Py_XDECREF(bases);
        Py_XDECREF(slots);
        Py_XDECREF(type);
        return NULL;
    }
    

    Python虚拟机首先会将类名、基类列表和属性表从tuple对象中解析出来,然后会基于基类列表及传入的metaclass(参数metatype)确定最佳的metaclass和base。

    随后,python虚拟机会调用metatype->tp_alloc尝试为要创建的类对象分配内存。这里需要注意的是,在PyType_Type中,我们发现tp_alloc是一个NULL,这显然不正常。但是不要忘记,我们之前提到,在Python进行初始化时,会对所有的内建对象通过PyType_Ready进行初始化,在这个初始化过程中,有一项动作就是从基类继承各种操作。由于type.__bases__中的第一个基类是object,所以type会继承object中的tp_alloc操作,即 PyType_GenericAlloc 。对于我们的任意继承自object的class对象来说, PyType_GenericAlloc 将申请metatype->tp_basicsize + metatype->tp_itemsize大小的内存空间。从PyType_Type的定义中我们看到,这个大小实际就是 sizeof(PyHeapTypeObject) + sizeof(PyMemerDef) 。因此在这里应该就明白了PyHeapTypeObject这个老铁到底是干嘛用的了,之前因为偏移量的问题,折腾了不少功夫,甚至让人觉得这有啥用啊,但是现在意识到了,这个老铁是为用户自定义class准备的。

    接下来就是设置class对象的各个域,其中包括了在tp_dict上设置属性表,也就是__dict__。另外注意的是,这里还计算了类对象对应的实例对象所需要的内存大小信息,换言之,我们类创建一个实例对象时,需要为这个实例对象申请多大的内存空间呢?对于任意继承object的class对象来说,这个大小为PyBaseObject_Type->tp_basicsize + 16。其中的16是2 * sizeof(PyObject *)。为什么后面要跟着两个PyObject *的空间,因为这些空间的地址被设置给了 tp_dictoffsettp_weaklistoffset 了呢?这一点将在下一篇博客中进行解析,它是和实例对象的属性字典密切相关的。

    最后,Python虚拟机还会调用PyType_Ready对class定义的类对象(这里简称class对象)进行和内建对象一样的初始化动作,到此class对象才算正式创建完毕。那么内建对象和class对象在内存布局上面有什么区别呢?毕竟都是类对象。

    本质上,无论用户自定义的class对象还是内建对象,在Python虚拟机内部,都可以用一个PyTypeObject来表示。但不同的是,内建对象的PyTypeObject以及与其关联的PyNumberMethods等属性的内存位置都是在编译时确定的,它们在内存中的位置是分离的。而用户自定义的class对象的PyTypeObject和PyNumberMethods等内存位置是连续的,必须在运行时动态分配内存。

    现在我们算是对python中可调用(callable)这个概念有一个感性认识了,在python中可调用这个概念是一个相当通用的概念,不拘泥于对象、大小,只要类型对象定义了tp_call操作,就能进行调用操作。我们已经看到,python中的对象class对象是调用metaclass创建。那么显然,调用class对象就能得到实例对象。

    小结

    这一次我们介绍了自定义的类在底层是如何实现的,但是关于类的知识点还有很多,比如:魔法方法、描述符等等,我们可能还需要两到三篇来进行介绍。

  • 相关阅读:
    模板方法模式
    备忘录模式
    观察者模式
    中介者模式
    迭代器模式
    Char型和string型字符串比较整理
    命令模式
    责任链模式
    代理模式
    dokcer 杂谈
  • 原文地址:https://www.cnblogs.com/traditional/p/13593927.html
Copyright © 2011-2022 走看看