首页 > 编程语言 >《深度剖析CPython解释器》19. Python类机制的深度解析(第三部分): 自定义类的底层实现、以及metaclass

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

时间:2023-06-06 10:56:18浏览次数:51  
标签:__ 自定义 CPython 深度 new NULL type class name

https://www.cnblogs.com/traditional/p/13593927.html


楔子

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对象就能得到实例对象。

小结

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

标签:__,自定义,CPython,深度,new,NULL,type,class,name
From: https://www.cnblogs.com/tomato-haha/p/17459901.html

相关文章

  • SpringMVC里通过ResponseBodyAdvice接口实现统一自定义返回逻辑
    这个org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice接口。publicinterfaceResponseBodyAdvice<T>{//返回true代表走自定义逻辑booleansupports(MethodParameterreturnType,Class<?extendsHttpMessageConverter<?>>converte......
  • 8. 自定义映射resultMap
    ‍在Mybatis中,resultType和resultMap都用于定义查询结果的映射关系。它们的使用场景如下:resultTyperesultType用于指定返回结果的数据类型,通常用于返回简单类型的结果以及返回vo或dto等自定义类型的结果。例如:‍<selectid="findUserById"parameterType="int"r......
  • Vue自定义指令-让你的业务开发更简单
    1、使用场景在日常开发中,我们会将重复代码抽象为一个函数或者组件,然后在需要时调用或者引入。但是,对于某些功能,这种方法可能不够优雅或者不够灵活。例如,我们可能需要在DOM元素上添加一些自定义属性或者绑定一些事件,这些操作可能难以通过函数或组件来实现。这时,自定义指令就派上用......
  • qt 中的自定义pushbutton
       ......
  • WPF自学入门(四)WPF路由事件之自定义路由事件
      在上一篇博文中写到了内置路由事件,其实除了内置的路由事件,我们也可以进行自定义路由事件。接下来我们一起来看一下WPF中的自定义路由事件怎么进行创建吧。创建自定义路由事件分为3个步骤:1、声明并注册路由事件。2、利用CLR事件包装路由事件(封装路由事件)。3、创建可以激发路由......
  • win10,vs2015深度学习目标检测YOLOV5+deepsort C++多目标跟踪代码实现,源码注释,拿来即
    int8,FP16等选择,而且拿来即用,自己再win10安装上驱动可以立即使用,不用在自己配置,支持答疑。自己辛苦整理的,求大佬打赏一顿饭钱。苦苦苦、平时比较比忙,自己后期会继续发布真实场景项目;欢迎下载。优点:1、架构清晰,yolov5和sort是分开单独写的,可以随意拆解拼接,都是对外接口。2、支持答疑......
  • 二、Spring Reactive Security自定义登录页
    添加配置类:@ConfigurationpublicclassMyReactiveSecurityConfig{@BeanpublicReactiveUserDetailsServicereactiveUserDetailsService(){UserDetailsuser=User.withUsername("user").password("12345")......
  • Python生成器深度解析:构建强大的数据处理管道
    前言生成器是Python的一种核心特性,允许我们在请求新元素时再生成这些元素,而不是在开始时就生成所有元素。它在处理大规模数据集、实现节省内存的算法和构建复杂的迭代器模式等多种情况下都有着广泛的应用。在本篇文章中,我们将从理论和实践两方面来探索Python生成器的深度用法。生......
  • Python生成器深度解析:构建强大的数据处理管道
    前言生成器是Python的一种核心特性,允许我们在请求新元素时再生成这些元素,而不是在开始时就生成所有元素。它在处理大规模数据集、实现节省内存的算法和构建复杂的迭代器模式等多种情况下都有着广泛的应用。在本篇文章中,我们将从理论和实践两方面来探索Python生成器的深度用法。......
  • 武汉星起航:深度剖析亚马逊广告推广,是否有助于卖家提升销量
    亚马逊作为全球领先的电商平台,提供了一系列广告推广工具,帮助卖家提高产品曝光度、增加销量并促进品牌增长。武汉星起航将对亚马逊广告推广进行分析,探讨其优势和效果。增强产品曝光度:亚马逊广告推广可以显著提高产品在平台上的曝光度。通过在搜索结果页面、商品详情页和相关产品页上......