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

小結

這一次我們介紹了自定義的類在底層是如何實現的,但是關於類的知識點還有很多,比如:魔法方法、描述符等等,我們可能還需要兩到三篇來進行介紹。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM