# PyObject

CPython 源码中可以注意到 PyObject 结构的普遍性,当解释器循环处理求值堆栈上的值时,所有这些值都会被视为 PyObject。我们不妨称其为 Python 对象的超类,值实际上从未被声明为 PyObject,但可以将指向任何对象的指针强制转换为 PyObject,即任何对象都可以视为 PyObject 结构,因为所有对象的初始段实际上都是 PyObject 结构。

PyObject 结构体的定义,它由多个字段组成,这些字段必须被全部填上才能将它视为对象

typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
} PyObject;

_PyObject_HEAD_EXTRA 是一个 C 宏,它定义了一个指向之前分配对象的字段和指向下一个对象的字段,这些字段能够形成所有活动对象的隐式双链表。ob_refcnt 字段用于内存管理,而 * ob_type 是指向类型对象的指针,类型决定了对象代表什么、所包含的数据类型以及可以对它执行的操作类型。

以下代码,name 指向一个字符串对象,对象的类型为 str

>>> name = 'obj'
>>> type(name)
<class 'str'>

str 类型字段指向 type 对象,那么 type 对象的 * ob_type 字段指向什么?实际上 type 的 ob_type 指向自身,或者说 type 类型还是 type。

>>> type(type)
<class 'type'>

关于引用计数
CPython 使用引用计数进行内存管理,这是一种相对简单的方法,只要在创建一个对象的新引用时,对象的引用计数就会增加。反之亦然,每当对一个对象的引用消失(如使用 del 删除该引用),引用计数就会减少。当对象引用计数变为 0 时,虚拟机可以将其释放。在虚拟机中,Py_INCREF 和 Py_DECREF 用于增加和减少对象的引用计数。

# 类型对象的字段

Include/Object.h 中定义的_typeobject 结构是所有 Python 类型的基本结构。它定义了许多字段,这些字段大多是指向实现给定类型的某些函数的 C 函数指针。

typedef struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "<module>.<name>" */
    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
    /* Methods to implement standard operations */
    destructor tp_dealloc;
    printfunc tp_print;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
                                    or tp_reserved (Python 3) */
    reprfunc tp_repr;
    /* Method suites for standard classes */
    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;
    /* More standard operations (here for binary compatibility) */
    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;
    /* Functions to access object as input/output buffer */
    PyBufferProcs *tp_as_buffer;
    /* Flags to define presence of optional/expanded features */
    unsigned long tp_flags;
    const char *tp_doc; /* Documentation string */
    /* Assigned meaning in release 2.0 */
    /* call function for all accessible objects */
    traverseproc tp_traverse;
    /* delete references to contained objects */
    inquiry tp_clear;
    /* Assigned meaning in release 2.1 */
    /* rich comparisons */
    richcmpfunc tp_richcompare;
    /* weak reference enabler */
    Py_ssize_t tp_weaklistoffset;
    /* Iterators */
    getiterfunc tp_iter;
    iternextfunc tp_iternext;
    /* Attribute descriptor and subclassing stuff */
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *tp_base;
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    Py_ssize_t tp_dictoffset;
    initproc tp_init;
    allocfunc tp_alloc;
    newfunc tp_new;
    freefunc tp_free; /* Low-level free-memory routine */
    inquiry tp_is_gc; /* For PyObject_IS_GC */
    PyObject *tp_bases;
    PyObject *tp_mro; /* method resolution order */
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;
    destructor tp_del;
    /* Type attribute cache version tag. Added in version 2.6 */
    unsigned int tp_version_tag;
    destructor tp_finalize;
    /* these must be last and never explicitly initialized */
    Py_ssize_t tp_allocs;
    Py_ssize_t tp_frees;
    Py_ssize_t tp_maxalloc;
    struct _typeobject *tp_prev;
    struct _typeobject *tp_next;
} PyTypeObject;

PyObject_VAR_HEAD 字段是 PyObject 字段的扩展,该扩展为具有长度概念的对象添加了一个 ob_size 字段,Python C/API 文档中提供了此类型对象结构中每个字段的详细说明。需要注意的是,结构中每个字段都实现了部分类型行为。这些字段中大多数是可以被称为对象接口或协议,因为它们对应于可以在 Python 对象上调用的函数,但是其实际实现方式取决于类型。例如,tp_hash 字段是给定类型的哈希函数的引用,如果类型实例不能被哈希,则该字段可以不带值(不实现)。在该类型的实力上调用 hash 方法时,将调用 tp_hash 字段中的函数。类型对象包括字段 tp_methods,它引用该类型特有的方法(如该类型支持什么方法调用)。tp_new 是对用来创建该类型实例的函数引用。其中一些字段(如 tp_init)是可选的,并非每种类型都需要运行初始化函数,尤其是对该类型是不可变(如元组)但其它字段(如 tp_new)是必需的。

这些字段中还有用于实现其它 Python 协议的字段,例如:

  1. Number protocol:实现此协议的类型将具有 PyNumberMethods *tp_as_number 字段的实现。该字段是对一组实现数字操作的函数的引用,这意味着该类型将支持在 tp_as_number 集合中包含实现的算术。例如,非数字的 set 类型在此字段中有一个条目,因为它支持一些算术运算,如 -,<= 等。
  2. Sequence protocol:实现此协议的类型将在 PySequenceMethods *tp_as_sequence 字段具有一个值。这意味着该类型将支持部分或全部序列操作,例如 len、in 等。
  3. Mapping protocol:实现该协议的类型会在 PyMappingMethods *tp_as_mapping 字段具有一个值。这将允许使用字典下标语法来设置和访问键 - 值映射,从而将此类型视为 Python 字典。
  4. Iterator protocol:实现此协议的类型将在 getiterfunc tp_iter 以及 iternextfunc tp_iternext 字段具有一个值,从而使该类型的实例能够像 Python 迭代器一样被使用。
  5. Buffer protocol:实现此协议的类型将在 PyBufferProcs *tp_as_buffer 字段具有一个值。这将允许访问该类型的实例作为输入 / 输出缓冲区。

# 类型对象案例研究

# tuple 类型

来看下元组类型,了解是如何填充类型对象的字段

PyTypeObject PyTuple_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "tuple",
    sizeof(PyTupleObject) - sizeof(PyObject *),
    sizeof(PyObject *),
    (destructor)tupledealloc,                   /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    (reprfunc)tuplerepr,                        /* tp_repr */
    0,                                          /* tp_as_number */
    &tuple_as_sequence,                         /* tp_as_sequence */
    &tuple_as_mapping,                          /* tp_as_mapping */
    (hashfunc)tuplehash,                        /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    PyObject_GenericGetAttr,                    /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
        Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TUPLE_SUBCLASS, /* tp_flags */
    tuple_new__doc__,                           /* tp_doc */
    (traverseproc)tupletraverse,                /* tp_traverse */
    0,                                          /* tp_clear */
    tuplerichcompare,                           /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    tuple_iter,                                 /* tp_iter */
    0,                                          /* tp_iternext */
    tuple_methods,                              /* tp_methods */
    0,                                          /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    0,                                          /* tp_init */
    0,                                          /* tp_alloc */
    tuple_new,                                  /* tp_new */
    PyObject_GC_Del,                            /* tp_free */
};
  1. PyObject_VAR_HEAD:已使用类型对象 PyType_Type 作为类型进行初始化。type 对象的类型是 type。查看 PyType_Type 类型对象可发现 PyType_Type 的类型为自身
  2. tp_name:初始化为类型的名称,叫 tuple
  3. tp_basicsize 和 tp_itemsize:引用元组对象和元组对象中包含的项目的大小,并相应地进行填充
  4. tupledealloc:一种内存管理功能,用于在销毁元组对象时处理内存的重新分配
  5. tuplerepr:是以元组实例作为参数调用 repr 函数时调用的函数
  6. tuple_as_sequence:是元组实现的序列方法的集合,比如元组支持的 len 等序列方法
  7. tuple_as_mapping:是元组支持的一组映射方法,在这种情况下,键只能是整数索引
  8. tuplehash:在计算元组对象的哈希值时被调用的函数。当元组被作为字典的 key 使用时能起到作用
  9. PyObject_GenericGetAttr 是引用元组对象的属性时调用的通用函数
  10. tuple_doc:元组对象的文档字符串
  11. tupletraverse:是用于元组对象垃圾回收的遍历函数。垃圾收集器使用此功能来帮助检测引用周期
  12. tuple_iter:在 tuple 对象上调用 iter 函数时将被调用的方法。在这种情况下,将返回完全不同的 tuple_iterator 类型,因此 tp_iternext 方法没有实现
  13. tuple_methods:元组类型的实际方法
  14. tuple_new:用于创建新的元组类型实例的函数
  15. PyObject_GC_Del:是引用内存管理函数的另一个字段

其余具有 0 值的字段保留位空,因此元组的功能不需要它们。以 tp_init 字段为例,元组是 immutable,因此一旦创建它就不能更改,除了 tp_new 引用的函数中发生的事情外,不需要任何初始化,所以该字段保留位空。

# type 类型

所有内置类型以及用户定义的普通类型的元类型(用户可以定义一个新的元类型)。注意在初始化 PyVarObject_HEAD_INIT 中的元组对象时如何使用此类型,在讨论类型时,重要的是区分以 type 位类型的对象和以用户定义类型为类型的对象。在处理对象中的属性引用时,非常重要。

# object 类型

另一个重要的类型是 object 类型,它与 type 类型相似。object 类型是所有用户定义类型的根类型,并提供一些默认值,用于填充用户定义类型的类型字段。与 type 相比,用户定义的类型的行为方式有所不同,object 类型提供的属性解析算法之类的函数与 type 类型所提供的函数有很大不同。

# 创建类型实例

tp_new 字段是 Python 中新类型实例的判断依据,tp_new 字段的文档对填充它的函数进行了足够详细的描述。

一个指向示例创建函数的指针
函数签名为:PyObject *tp_new (PyTypeObject *subtype, PyObject *args, PyObject *kwds);
subtype 参数为被创建对象的类型;args 和 kwds 参数代表传给它的位置(positional)参数和关键字(keyword)参数,注意,subtype 不是必须等同于调用 tp_new 的类型,它可能是它的子类型。
tp_new 函数应该调用 subtype->tp_alloc (subtype, nitems) 为对象分配空间,然后仅执行一些必要的初始化。可以安全地忽略或重复进行的初始化应放在 tp_init 中。一个好的经验法则是:对于不可变类型,所有初始化都应该在 tp_new 中进行,而对于可变类型,大多数初始化应推迟到 tp_init 中进行。
Inheritance
该字段由子类型继承,除非它的字段不是由 tp_base 为 NULL 或 & PyBaseObject_Type 的静态类型继承。

下面以 tuple 为例,元组类型的 tp_new 字段引用了 tuple_new 方法,该方法处理新元组对象的创建。如果要创建一个新的元组对象,请取消引用并调用此函数。

static PyObject *
tuple_new_impl(PyTypeObject *type, PyObject *iterable)
/*[clinic end generated code: output=4546d9f0d469bce7 input=86963bcde633b5a2]*/
{
    if (type != &PyTuple_Type)
        return tuple_subtype_new(type, iterable);
    if (iterable == NULL)
        return PyTuple_New(0);
    else
        return PySequence_Tuple(iterable);
}

忽略创建元组的第一个和第三个条件,看下第二个条件,if (iterable == NULL) return PyTuple_New (0),顺着调用链向下来研究它的工作原理,忽略 PyTuple_New 函数中的优化,创建新元组对象对应的代码是 op = PyObject_GC_NewVar (PyTupleObject, &PyTuple_Type, size);这个调用,该调用为堆上的 PyTuple_Object 结构实例分配内存。这就是内置类型和用户定义类型的内部表示之间的一个明显区别:内置类型的实例(例如元组)为了提高效率实际上是 C 结构体。那么元组对象的 C 结构体看起来像什么呢?代码在 Inlcude/tupleobject.h 中找到它的 typedef

typedef struct {
    PyObject_VAR_HEAD
    PyObject *ob_item[1];
    /* ob_item contains space for 'ob_size' elements.
     * Items must normally not be NULL, except during construction when
     * the tuple is not yet visible outside the function that builds it.
     */
} PyTupleObject;

PyTupleObject 定义具有 PyObject_VAR_HEAD 和 PyObject 指针数组 ob_items 的结构体。与使用 Python 来表达的实例相反,这是非常高效的实现。

对象是方法和数据的集合,这种情况下,PyTupleObject 提供了空间来保存每个元组对象包含的实际数据,因此我们可以在堆上分配 PyTupleObject 的多个实例,但是这些实例都引用单个 PyTuple_Type 类型对象,它提供可以对数据进行操作的方法。

class Test:
    pass

Test 类型是 type 的实例。要创建 Test 类型的实例,要对 Test 类型进行调用 Test ()。type 类型具有一个函数引用 type_call,它填充了 tp_call 字段,并且在 type 实例上使用调用符号时将取消引用。

static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyObject *obj;
    PyThreadState *tstate = _PyThreadState_GET();
#ifdef Py_DEBUG
    /* type_call() must not be called with an exception set,
       because it can clear it (directly or indirectly) and so the
       caller loses its exception */
    assert(!_PyErr_Occurred(tstate));
#endif
    /* Special case: type(x) should return Py_TYPE(x) */
    /* We only want type itself to accept the one-argument form (#27157) */
    if (type == &PyType_Type) {
        assert(args != NULL && PyTuple_Check(args));
        assert(kwds == NULL || PyDict_Check(kwds));
        Py_ssize_t nargs = PyTuple_GET_SIZE(args);
        if (nargs == 1 && (kwds == NULL || !PyDict_GET_SIZE(kwds))) {
            obj = (PyObject *) Py_TYPE(PyTuple_GET_ITEM(args, 0));
            Py_INCREF(obj);
            return obj;
        }
        /* SF bug 475327 -- if that didn't trigger, we need 3
           arguments. But PyArg_ParseTuple in type_new may give
           a msg saying type() needs exactly 3. */
        if (nargs != 3) {
            PyErr_SetString(PyExc_TypeError,
                            "type() takes 1 or 3 arguments");
            return NULL;
        }
    }
    if (type->tp_new == NULL) {
        _PyErr_Format(tstate, PyExc_TypeError,
                      "cannot create '%.100s' instances",
                      type->tp_name);
        return NULL;
    }
    obj = type->tp_new(type, args, kwds);
    obj = _Py_CheckFunctionResult(tstate, (PyObject*)type, obj, NULL);
    if (obj == NULL)
        return NULL;
    /* If the returned object is not an instance of type,
       it won't be initialized. */
    if (!PyType_IsSubtype(Py_TYPE(obj), type))
        return obj;
    type = Py_TYPE(obj);
    if (type->tp_init != NULL) {
        int res = type->tp_init(obj, args, kwds);
        if (res < 0) {
            assert(_PyErr_Occurred(tstate));
            Py_DECREF(obj);
            obj = NULL;
        }
        else {
            assert(!_PyErr_Occurred(tstate));
        }
    }
    return obj;
}

调用 type 对象的实例时,所发生的一切就是取消了对 tp_new 字段的引用,并调用了所引用的任何函数来获取新的实例。如果 tp_init 存在,则在新实例上调用它以执行新实例的初始化。此过程为内置类型提供了解释,因为毕竟它们已经定义了自己的 tp_new 和 tp_init 函数,但是用户定义的类型呢?大多数情况下,用户不会为新类型定义__new__函数(在定义时,在类创建期间进入到 tp_new 字段)。答案也取决于 type_new 函数,该函数填充 type 的 tp_new 字段。在创建用户定义的类型(例如 Test 的情况下)时,type_new 函数检查基类(super types/classes)的存在,如果不存在,则将 PyBaseObject_Type 类型添加为默认基本类型。

static PyObject *
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
{
    // ......
    /* Adjust for empty tuple bases */
    nbases = PyTuple_GET_SIZE(bases);
    if (nbases == 0) {
        base = &PyBaseObject_Type;
        bases = PyTuple_Pack(1, base);
        if (bases == NULL)
            return NULL;
        nbases = 1;
    }
    // ......
}

默认基本类型也在 Objects/typeobject.c 模块中定义,其中包含各个字段的一些默认值。这些默认值中包括 tp_new 和 tp_init 字段的值。这些是解释器为用户定义类调用提供的值。在用户定义类型实现了自己的方法(如__init__,__new__等)的情况下,将调用这些值,而不是 PyBaseObject_Type 类型的值。

如果没有为用户的类定义对象结构,那么对象实例将如何处理?对象属性存放在何处?这与 tp_dictoffset 字段(一个类型对象中的数字字段)有关。实例实际上是作为 PyObject 创建的,但是当实例类型中的偏移量值不为零时,它代表实例属性字典与实例(PyObject)本身之间的偏移量。

如下图,对于 Person 类型的实例,可以通过此偏移量与 PyObject 内存位置相加来计算属性字典在内存中的位置。

用户定义类型的 dictoffset 字段为它本身到它的属性字典之间的偏移量

例如,如果实例 PyObject 的值为 0x10,偏移量为 16,那么属性字典的位置位于 0x10 + 16。

这并不是实例存储其属性的唯一方法,还有其它方式

# 对象和它们的属性

一般情况下,类型和实例使用 dict 存储它们的属性,而在定义了__slots__的时候情况有所不同,在上一节中,可以根据对象的类型在两个位置的其中一个找到对应的 dict。

  1. 对于 type 类型的对象,类型结构体的 tp_dict 字典指向 dict 的指针,该 dict 包含该类型的值、变量和方法。或者说,类型对象结构体的 tp_dict 字典是指向类字典的指针
  2. 对于用户定义类型的实例,该字典(如果存在)位于表示该对象的 PyObject 结构之后。对象类型的 tp_dictoffset 值给出了从对象开始到包含实例属性的实例 dict 的偏移量

描述符协议,它是 Python 属性引用的核心,Descriptor HowTo Guide 十一篇不错的介绍描述符协议的文章。描述符是实现了描述符协议中__get__,__set__或__delete__特殊方法的对象。

descr.__get__(self, obj, type=None) --> value
descr.__set__(self, obj, value) --> None
descr.__delete__(self, obj) --> None

仅实现__get__方法的对象是非数据描述符(non-data descriptors),因此只能在初始化后从它们中读取,而实现__get__和__set__的对象是数据描述符,这意味着此类描述符对象是可写的。

下面代码是一个表示对象属性的描述符示例

class TypedAttribute:
    
    def __init__(self, name, type, default=None):
        self.name = "_" + name
        self.type = type
        self.default = default if default else type()
    def __get__(self, instance, cls):
        return getattr(instance, self.name, self.default)
    def __set__(self,instance,value):
        if not isinstance(value,self.type):
            raise TypeError("Must be a %s" % self.type) 
        setattr(instance,self.name,value)
    
    def __delete__(self,instance):
        raise AttributeError("Can't delete attribute")

TypedAttribute 描述符类对用于表示的类的任何属性强制执行类型检查。需要注意的是,只有在类级别而不是实例级别级别中定义描述符时,描述符才会生效。

class Account:
    name = TypedAttribute("name",str) 
    balance = TypedAttribute("balance",int, 42)
    
    def name_balance_str(self):
        return str(self.name) + str(self.balance)
>> acct = Account()
>> acct.name = "obi"
>> acct.balance = 1234
>> acct.balance
1234
>> acct.name 
obi
# trying to assign a string to number fails
>> acct.balance = '1234'
TypeError: Must be a <type 'int'>

似乎的确只有在类型级别定义此类描述符才有意义,因为如果在实例级别定义,则对该属性的任何赋值都将覆盖该描述符。必须阅读 CPython 源码以了解描述符整体方式。描述符提供了 Python 中的属性,静态方法,类方法和许多其它的功能机制。

为了具体说明描述符的重要性,请考虑用于从用户定义类型的实例 b 解析属性的算法

  1. 在 type (type).__dict__中搜索属性名,如果找到了它并且是一个数据描述符,将返回描述符__get__方法调用的结果。如果名字没有被找到,且将在 type (type) 的 mro 的所有类中以相同方式搜索
  2. 在 type.__dict__中搜索,如果找到,并且是一个描述符,调用__get__的结果,返回
  3. 如果 1 中找到的是一个非数据描述符,则返回__get__的调用结果
  4. 如果 1 中找到的并非一个描述符,则返回这个值

# 在 VM 内部使用描述符进行属性引用的示例

描述符在 Python 中的属性引用里起着非常重要的作用,希望被视为描述符的类型实例都可以填充类型数据结构中的 tp_descr_get 和 tp_descr_set 字段。函数对象是一个很好的例子。

给定类型定义,如上面的 Account 类,Account.name_balance_str 与 acct.name_balance_str,引用 name_balance_str 时,会发生什么?

>> a = Account()
>> a.name_balance_str
<bound method Account.name_balance_str of <__main__.Account object at 
0x102a0ae10>>
>> Account.name_balance_str
<function Account.name_balance_str at 0x102a2b840>

我们引用了相同的属性,但是实例对象的值返回了 bound method,而类对象的值返回了 function,这是因为函数其实也是描述符,函数对象类型的定义

PyTypeObject PyFunction_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "function",
    sizeof(PyFunctionObject),
    0,
    (destructor)func_dealloc,                   /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    (reprfunc)func_repr,                        /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    function_call,                              /* tp_call */
    0,                                          /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,    /* tp_flags */
    func_new__doc__,                            /* tp_doc */
    (traverseproc)func_traverse,                /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    offsetof(PyFunctionObject, func_weakreflist), /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    0,                                          /* tp_methods */
    func_memberlist,                            /* tp_members */
    func_getsetlist,                            /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    func_descr_get,                             /* tp_descr_get */
    0,                                          /* tp_descr_set */
    offsetof(PyFunctionObject, func_dict),      /* tp_dictoffset */
    0,                                          /* tp_init */
    0,                                          /* tp_alloc */
    func_new,                                   /* tp_new */
};

函数对象使用 func_descr_get 函数填充了 tp_descr_get 字段,因此函数类型的实例是非数据描述符

static PyObject *
func_descr_get(PyObject *func, PyObject *obj, PyObject *type)
{
    if (obj == Py_None || obj == NULL) {
        Py_INCREF(func);
        return func;
    }
    return PyMethod_New(func, obj);
}

在类型属性解析或实例属性解析期间调用 func_descr_get。从类型调用时,对 func_descr_get 的调用类似于 local_get (attribute, (PyObject) NULL, (PyObject ) type) 这种,而从用户定义类型的实例的属性引用进行调用时,调用签名为 f (descr, obj, (PyObject *) Py_TYPE (obj))。

观察 func_descr_get 的实现,当实例为 NULL,则函数本身将返回,而当将实例传递给调用时,则会使用该函数和实例创建一个新的方法对象。

当在类中定义方法时,我们将 self 参数用作任何实例方法的第一个参数,因为实际上,实例方法将实例(按惯例称为 self)作为第一个参数。诸如 b.name_balance_str () 之类的调用实际上与 type (b).name_balance_str (b) 相同。之所以能够调用 b.name_balance_str (),是因为 b.name_balance_str 返回的值是一个方法对象(methodobject),该对象是 name_balance_str 的一个簿封装,实例已经绑定到该方法实例。因此我们进行 b.name_balance_str () 的调用时,该方法将绑定实例用作包装函数的参数

/* use borrowed references */
newargs[0] = self; // 实例对象作为第一个参数
/* bpo-37138: since totalargs > 0, it's impossible that args is NULL.
    * We need this, since calling memcpy() with a NULL pointer is
    * undefined behaviour. */
// ......
result = _PyObject_VectorcallTstate(tstate, func,
                                            newargs, nargs+1, kwnames);

在另一个重要的描述符实例中,参考以下代码,从内置类型的实例和用户定义的类型的实例访问__dict__属性的结果

>>> A.__dict__
mappingproxy({'__module__': '__main__', '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None})
>>> i = A()
>>> i.__dict__
{}
>>> A.__dict__['name'] = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment
>>> i.__dict__['name'] = 2
>>> i.__dict__
{'name': 2}

当引用__dict__属性时,两个对象都没有返回普通字典类型。当 type 实例返回支持所有常用字典功能的普通字典映射时,类型对象似乎返回了我们甚至无法分配给它的映射代理。因此,似乎这些对象的属性引用方式有所不同。

>>> type(type.__dict__['__dict__']) # type of A is type
<class 'getset_descriptor'>
>>> type(A.__dict__['__dict__'])
<class 'getset_descriptor'>

可以看到__dict__属性由两个对象的数据描述符表示,所以我们可以得到不同的对象类型的原因。一个有趣的字段是 tpgetset,其中包含了一个 C 结构数组(PyGetSetDef 值),这时将描述符对象插入到类型的__dict__属性中的值的集合,这时类型对象的 tp_dict 字段指向的映射。

static PyGetSetDef type_getsets[] = {
    {"__name__", (getter)type_name, (setter)type_set_name, NULL},
    {"__qualname__", (getter)type_qualname, (setter)type_set_qualname, NULL},
    {"__bases__", (getter)type_get_bases, (setter)type_set_bases, NULL},
    {"__module__", (getter)type_module, (setter)type_set_module, NULL},
    {"__abstractmethods__", (getter)type_abstractmethods,
     (setter)type_set_abstractmethods, NULL},
    {"__dict__",  (getter)type_dict,  NULL, NULL},
    {"__doc__", (getter)type_get_doc, (setter)type_set_doc, NULL},
    {"__text_signature__", (getter)type_get_text_signature, NULL, NULL},
    {0}
};

在类型初始化之后,__dict__属性存在于类型的 tp_dict 字段中

static PyObject *
type_dict(PyTypeObject *type, void *context)
{
    if (type->tp_dict == NULL) {
        Py_RETURN_NONE;
    }
    return PyDictProxy_New(type->tp_dict);
}

tp_getattro 字段指向该函数,该函数是获取任何对象的属性的第一个调用端口。对于 type 对象,它指向 type_getattro 函数。该方法又实现了属性的搜索算法。__dict__属性的类型字典中的描述符所调用的函数是上面的 type_dict 函数,很容易理解,它是包含类型属性的实际字典的字典代理,这也解释了查询类型对象的__dict__属性时返回的是 mappingproxy 类型。

那么,用户定义类型 A 的实例又如何解析__dict__属性呢?A 实际上是 type 类型的对象,因此在 Object/typeobject.c 模块中搜寻以了解创建新的类型实例。

PyType_Type 的 tp_new 字段包含用于创建新类型对象的 type_new 函数。

static PyObject *
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
{
    // 为用户定义 tp_getset 字段
    // ......
    if (type->tp_weaklistoffset && type->tp_dictoffset)
        type->tp_getset = subtype_getsets_full;
    else if (type->tp_weaklistoffset && !type->tp_dictoffset)
        type->tp_getset = subtype_getsets_weakref_only;
    else if (!type->tp_weaklistoffset && type->tp_dictoffset)
        type->tp_getset = subtype_getsets_dict_only;
    else
        type->tp_getset = NULL;
    // ......
}

假设第一个条件为 true,则 tp_getset 字段将填充以下值

static PyGetSetDef subtype_getsets_full[] = {
    {"__dict__", subtype_dict, subtype_setdict,
     PyDoc_STR("dictionary for instance variables (if defined)")},
    {"__weakref__", subtype_getweakref, NULL,
     PyDoc_STR("list of weak references to the object (if defined)")},
    {0}
};

调用 (*tp->tp_getattor)(v, name) 时,将调用包含指向 PyObject_GenericGetAttr 的指针的 tp_getattor 字段。该函数负责为用户定义的类型实现属性搜索算法。对于字典属性,在对象类型的字典中找到描述符,而描述符的__get__函数是上面代码__dict__属性定义的 subtype_dict 函数。

static PyObject *
subtype_dict(PyObject *obj, void *context)
{
    PyTypeObject *base;
    base = get_builtin_base_with_dict(Py_TYPE(obj));
    if (base != NULL) {
        descrgetfunc func;
        PyObject *descr = get_dict_descriptor(base);
        if (descr == NULL) {
            raise_dict_descr_error(obj);
            return NULL;
        }
        func = Py_TYPE(descr)->tp_descr_get;
        if (func == NULL) {
            raise_dict_descr_error(obj);
            return NULL;
        }
        return func(descr, obj, (PyObject *)(Py_TYPE(obj)));
    }
    return PyObject_GenericGetDict(obj, context);
}

当对象实例处于继承层次结构中时,get_builtin_base_with_dict 返回一个值,因此忽略该实例是适当的。

PyObject_GenericGetDict 和实际获取实例字典的关联辅助函数。实际的获取 dict 的函数是_PyObject_GetDictPtr 函数,该函数查询对象的 dictoffset 并将用于计算实例 dict 的地址。在此函数返回空值的情况下,PyObject_GenericGetDict 可以继续向调用函数返回新的字典。

PyObject *
PyObject_GenericGetDict(PyObject *obj, void *context)
{
    PyObject *dict, **dictptr = _PyObject_GetDictPtr(obj);
    if (dictptr == NULL) {
        PyErr_SetString(PyExc_AttributeError,
                        "This object has no __dict__");
        return NULL;
    }
    dict = *dictptr;
    if (dict == NULL) {
        PyTypeObject *tp = Py_TYPE(obj);
        if ((tp->tp_flags & Py_TPFLAGS_HEAPTYPE) && CACHED_KEYS(tp)) {
            dictkeys_incref(CACHED_KEYS(tp));
            *dictptr = dict = new_dict_with_shared_keys(CACHED_KEYS(tp));
        }
        else {
            *dictptr = dict = PyDict_New();
        }
    }
    Py_XINCREF(dict);
    return dict;
}
PyObject **
_PyObject_GetDictPtr(PyObject *obj)
{
    Py_ssize_t dictoffset;
    PyTypeObject *tp = Py_TYPE(obj);
    dictoffset = tp->tp_dictoffset;
    if (dictoffset == 0)
        return NULL;
    if (dictoffset < 0) {
        Py_ssize_t tsize = Py_SIZE(obj);
        if (tsize < 0) {
            tsize = -tsize;
        }
        size_t size = _PyObject_VAR_SIZE(tp, tsize);
        dictoffset += (long)size;
        _PyObject_ASSERT(obj, dictoffset > 0);
        _PyObject_ASSERT(obj, dictoffset % SIZEOF_VOID_P == 0);
    }
    return (PyObject **) ((char *)obj + dictoffset);
}

该解释简要总结了如何根据类型使用描述符来实现自定义属性的访问。在整个 VM 中,对于使用描述符执行属性访问的其它实例,使用上述相同策略。描述符在虚拟机中无处不在,__slots__,静态方法和类方法,属性也只是使用描述符实现的语言功能而已。

# 方法解析顺序(MRO)

在讨论属性引用时,提到了 MRO,这里进行详细的介绍。在 Python 中,类型可以有多重继承关系,因此,当一个类型从多个类继承时,需要一种顺序来搜索方法。如我们在属性参考解析算法中所看到的,在搜索其他非方法属性时,也是使用了称为方法解析顺序(Method Resolution Order,MRO)的该顺序,文档 Python 2.3 Method Resolution Order 是有关 Python 中使用的方法解析算法的出色易读文档。

当一个类型从多个基本类型继承时,Python 使用 C3 算法来构建方法的解析顺序(也成为线性化)。

C1 C2 ... CN 代表一系列类,放在一个列表(list)中:[C1, C2, C3, ..., CN]
头(head)是第一个元素:head = C1
尾巴(tail)是其余元素:tail = C2, ..., CN
C + (C1 C2 ... CN) = C C1 C2 ... CN 代表列表的加和:[C] + [C1, C2, ..., CN]

考虑多重继承层次结构中的类型 C,其中 C 从基本类型 B1,B2,…,BN 继承,则 C 的线性化是 C 加上父项的线性化与 B 的列表合并:L [C (B1 … BN)] = C + merge (L [B1] … L [BN], B1 … BN)。没有父对象的对象类型的线性化是微不足道的 L [object] = object。合并操作是根据以下算法计算的:

取出第一个列表的 head,即 L [B1][0],如果 head 不在任何其它列表的尾部,则将其添加到 C 的线性化中,然后从合并中的列表中将其删除,否则,请查看下一个列表的 head 并采用它,如果它是一个正常的 head 然后重复该操作,直到所有类都被删除,或者找不到正常的 head。这种情况下,不可能构造合并,Python2.3 将拒绝创建类 C 并引发异常。

使用此算法无法线性化某些类型层次结构,在这种情况下,VM 会引发错误,并且不会创建此类层次结构。

# 存在回路,无法创建类
class G(object):
    pass
class A(G):
    pass
                            
class B(G):
    pass
class X(A, B):
    pass
class Y(B, A):
    pass
class Z(X, Y):
    pass
========================================
Traceback (most recent call last):
  File "mro.py", line 16, in <module>
    class Z(X, Y):
TypeError: Cannot create a consistent method resolution order (MRO) for bases A, B

假设我们有一个如图的继承关系,则创建 MRO 的算法将从层次结构的顶部开始依次为 O,A 和 B。O,A 和 B 线性化很简单:

L[O] = O
L[A] = A O
L[B] = B O

X 的线性化可以计算为 L [X] = X + merge (AO, BO, AB)

A 是一个正常的 head,因此将其添加到线性化中:L [X] = X + A + merge (O, BO, B),然后继续计算剩下的。O 不是正常 head,因为它位于 BO 的尾部,所以跳到下一个序列。B 是一个正常的 head,将其添加到线性化中:L [X] = X + A + B + merge (O, O),剩下的就可以计算归并为 O 的 merge (O, O),所得 X 的线性化:L [X] = X A B O。

计算 Y 的线性化:

L[Y] = Y + merge(AO, BO, AB)
     = Y + A + merge(O, BO, B)
     = Y + A + B + merge(O, O)
     = Y A B O

计算 X 和 Y 的线性化后,就可以计算 Z 的线性化:

L[Z] = Z + merge(XABO, YABO, XY)
     = Z + X + merge(ABO, YABO, Y)
     = Z + X + Y + merge(ABO, ABO)
     = Z + X + Y + A + merge(BO, BO)
     = Z + X + Y + A + B + merge(O, O)
     = Z X Y A B O

来源:https://leanpub.com/insidethepythonvirtualmachine