Pocket Readings

个人阅读清单记录博客

0%

《深度剖析CPython解释器》19. Python类机制的深度解析(第三部分): 自定义类的底层实现、以及metaclass - 古明地盆 - 博客园

Python除了给我提供了很多的类之外,还支持我们定义属于自己的类,那么Python底层是如何做的呢?我们下面就来看看。 老规矩,如果想知道底层是怎么做的,那么就必须要通过观察字节码来实现。



Tags:



via Pocket https://ift.tt/2LVUP6l original site



February 16, 2021 at 10:14PM

Comments


from: github-actions[bot] on: 3/8/2021

《深度剖析CPython解释器》19. Python类机制的深度解析(第三部分): 自定义类的底层实现、以及metaclass - 古明地盆 - 博客园

《深度剖析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对象就能得到实例对象。

小结

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

posted @ 2020-09-01 00:58 古明地盆 阅读(179) 评论(1) 编辑 收藏