解剖 Python 类

1,212 阅读22分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

前言

最近想为 SQLAlchemy 封装一套类似 Django ORM 的 Model Manager,于是捡起了「流畅的 Python」开始看被我遗留的「元编程」部分。在阅读的过程中,我慢慢发现自己并没有像想象的那样对 Python 类了如指掌,在很多概念的划分上我都是模棱两可的。因此特地总结这样一篇文章,希望能够由浅至深对 Python 类进行一次全面解剖手术。

注:本文所讨论的内容仅适用于 Python3。 注:本文针对源码的讨论仅适用于 CPython。 注:本文写作时参考 CPython 代码版本:3.9。 注:本文写作时参考 Python 官方文档版本:3.10.4。

概念

在开始解剖 Python 类之前需要先正确的理解一些概念。

在 Python 中有一句影响深远的话:一切皆对象。请不要被误导,此处的「对象」指的是一个抽象的概念,而 Python 内部为了能够清楚的表达「对象」这个概念将其分为了两个部分:object and type,即对象和类型。由这两部分组成的能够表达某类抽象「对象」的东西在 Python 中被称为 class,即类。

注:后文中凡是有括号的「对象」表示的都是一个抽象概念,而无括号的对象表示的则是 Python 中的 object。

另外还有一组概念要提前理解:

  • 当在 IDE 或文本编辑器中通过 class XX 的语法定义好某个类后,我们得到的只是一个
  • 一旦这个模块被导入后,Python 解释器就会生成相应的类对象
  • 一旦这个类在导入时或运行时被实例化了,Python 解释器就会生成相应的实例对象

注:关于这几个概念在本章的后续会有更详细的解释,而更深入的解析会在对应名字的章节上。

我们在使用 Python 语法编写程序时之所以能够一上来就定义一些复杂的类、生成复杂的类对象和实例对象,是因为 Python 在出厂时为我们包装好了各种基于类型和对象生成的类和相应类对象与实例对象(有些是内置的工具使用 C 实现的,拥有更好的执行效率)。

对象 | object

object

类型 | type

type

类 | class

Classes provide a means of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made. Each class instance can have attributes attached to it for maintaining its state. Class instances can also have methods (defined by its class) for modifying its state.

类把数据与功能绑定在一起。创建新类就是创建新的对象类型,从而创建该类型的新实例。类实例支持维持自身状态的属性,还支持(由类定义的)修改自身状态的方法。

——《Python 官方文档 - Python 教程 - 9. 类》

注:可以仔细品读一下官方文档的这段话,这段话已经将类的绝大多数秘密展示出来了。

导入时和运行时 | import & run

导入时:import 某个模块时此模块所处的状态; 运行时:调用某个模块时模块所处的状态;

注:在《流畅的 Python》这本书的 21.3 和 21.4 两个章节里有两个实例能够帮助更好地理解这两个概念。

类对象 | class object

类对象用于维护类的基本信息。

类对象支持两种操作:属性引用和实例化。

属性引用使用 Python 中所有属性引用所使用的标准语法:obj.name。有效的属性名称是类对象被创建时存在于类命名空间中的所有名称。

类的实例化使用函数表达法。可以把类对象视为是返回该类的一个新实例的不带参数的函数。 实例化操作(“调用”类对象)会创建一个空对象。 许多类喜欢创建带有特定初始状态的自定义实例。 为此类定义可能包含一个名为 __init__() 的特殊方法。 当一个类定义了 __init__() 方法时,类的实例化操作会自动为新创建的类实例发起调用 __init__()。 当然,__init__() 方法还可以有额外参数以实现更高灵活性。 在这种情况下,提供给类实例化运算符的参数将被传递给 __init__()。

——《Python 官方文档 - Python 教程 - 9. 类》

实例对象 | instance object

实例对象用于维护实例的基本信息。

实例对象所能理解的唯一操作是属性引用。 有两种有效的属性名称:数据属性和方法。

数据属性 对应于 Smalltalk 中的“实例变量”,以及 C++ 中的“数据成员”。 数据属性不需要声明;像局部变量一样,它们将在第一次被赋值时产生。

另一类实例属性引用称为 方法。 方法是“从属于”对象的函数。在 Python 中,方法这个术语并不是类实例所特有的:其他对象也可以有方法。 例如,列表对象具有 append, insert, remove, sort 等方法。

——《Python 官方文档 - Python 教程 - 9. 类》

注:请从此刻开始有意识的思考「实例对象的属性和类对象的属性之间的优先级关系」 注:有关类对象和实例对象的对比会在后文详细展开。 注:关于类对象和实例对象部分 网易云课堂-Python 微专业 的面向对象基础里有比较详细的讲解。 注:针对上一条,如果你搞不到资源的话可以看后面我自己的总结,内容大差不差。

实例 | instance

实例,代表的是类对象与其实例对象或类型与对应类之间的关系。

type,即类型是实例关系的顶点,type 的类型还是 type,object 的类型也是 type。

继承 | subclass

继承,代表的是父对象与子对象或父类型与子类型之间的关系。

object,即对象是继承关系的顶点,object 没有更上一层的对象了,而 type 的父对象是 object。

注:关于实例和继承的详细过程也会在后文详展开。

变量和属性 | variable & attributes

变量,指的是在进行 Python 编码的过程中为某个具体对象赋予的名称。

属性也是变量,但属性不会单独出现。我们在称呼一个变量为属性的时候一般会称其为某某对象的属性。当然在一些常见场景中,为了方便称呼会省略定语「某某对象的」,但省略不代表没有。

函数和方法 | function & method

Python 官方文档的 types 文档 中有这么几个类型:

types.FunctionType
types.LambdaType

The type of user-defined functions and functions created by lambda expressions.

# ---

types.MethodType

The type of methods of user-defined class instances.

# ---

types.BuiltinFunctionType
types.BuiltinMethodType

The type of built-in functions like len() or sys.exit(), and methods of built-in classes. (Here, the term “built-in” means “written in C”.)

大概可以理解为: 用户直接定义的函数或通过 lambda 生成的函数称为函数,是 FunctionType 类型,也就是函数; 用户通过实例引用的函数称为方法,是 MethodType 类型,也就是方法; 所有内建的函数(方法)称为内建函数(方法),是 BuiltinFunctionType,也就是内建函数(方法);

其中 FunctionType 和 LambdaType 是等效的,BuiltinFunctionType 和 BuiltinMethodType 是等效的。

注:关于函数和方法的详细区别,以及类中函数转化为方法的过程会在后文展开。

可调用对象 | callable

可以通过函数调用的方式(例如 foo())进行调用的对象。

对象:万物的起源

我刚开始接触 Python 时看的各种学习资料都在强调一点:在 Python 中,一切皆是对象。直到我整理这篇文章之前我对这句话的理解都仅限于字面意思。最近,正好借着工作需要的由头我收集了一些相关的资料(比较重要的我都放在了 参考 中)横向地了解了一下。下面就正式开始,好好地扒一扒 Python 中的「一切接对象」到底是什么意思以及隐藏在这句话背后的 Python 类究竟是如何构造的。

官方文档

从 Python 官方文档来看,object 关键字对应的是一个可调用对象,下面是官方文档对 object 的描述:

Return a new featureless object. object is a base for all classes. It has methods that are common to all instances of Python classes. This function does not accept any arguments. object does not have a __dict__, so you can’t assign arbitrary attributes to an instance of the object class.

返回一个不带特征的新对象。object 是所有类的基类。它带有所有 Python 类实例均通用的方法。本函数不接受任何参数。 由于 object 没有 __dict__,因此无法将任意属性赋给 object 的实例。

——《Python 官方文档 - 内置函数》

注:这里有个细节可以关注一下,虽然 object 被放在内置函数章节,但是却被标记为了 class。

Cpython

PyObject & PyVarObject | 对象基石

Cpython 中 Python 所有类型都由结构体 PyObject 扩展而来,而那些指向可变大小 Python 对象的指针都可以被转换为 PyVarObject,具体如下:

// 注:为了阅读体验,在书写格式上有所调整
// cpython-root/Include/object.h

#ifdef Py_TRACE_REFS
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA            \
    struct _object *_ob_next;           \
    struct _object *_ob_prev;

#define _PyObject_EXTRA_INIT 0, 0,

#else

#define _PyObject_HEAD_EXTRA
#define _PyObject_EXTRA_INIT

#endif

...

/* Nothing is actually declared to be a PyObject, but every pointer to
 * a Python object can be cast to a PyObject*.  This is inheritance built
 * by hand.  Similarly every pointer to a variable-size Python object can,
 * in addition, be cast to PyVarObject*.
 * 
 * 实际上没有任何东西被声明为 PyObject,
 * 但是每一个指向 Python 对象的指针都可以被转换为 PyObject。 
 * 这是通过手动强制建立的继承关系。 
 * 同样地,每个指向可变大小的 Python 对象的指针都可以被转换为 PyVarObject
 */
typedef struct _object {
    // 代表了两个 PyObject* 双向链表的指针,用于把堆上的所有对象链接起来,
    // 只会在开启了 Py_TRACE_REFS 宏的时候进行构造,方便调试;
    _PyObject_HEAD_EXTRA

    // 引用计数,用于垃圾回收;
    Py_ssize_t ob_refcnt;

    // 指针,指向对象的类型对象,用来标识对象属于的类型,并存储类型的元数据;
    PyTypeObject *ob_type;
} PyObject;

...

typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

PyObject 和 PyVarObject 一般是作为头部被包含在一个变量结构体中的(可以近似理解为继承),根据该变量大小是否固定来选择使用哪一种。PyObject 和 PyVarObject 是 Cpython 的基石,Python 中的 object 和 type 都是由这两个结构体拓展而来。

PyBaseObject_Type | object 的实现

// 注:为了阅读体验,在书写格式上有所调整
// cpython-root/Object/typeobject.c

PyTypeObject PyBaseObject_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "object",                                   /* tp_name */
    sizeof(PyObject),                           /* tp_basicsize */
    0,                                          /* tp_itemsize */
    object_dealloc,                             /* tp_dealloc */
    0,                                          /* tp_vectorcall_offset */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_as_async */
    object_repr,                                /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    (hashfunc)_Py_HashPointer,                  /* tp_hash */
    0,                                          /* tp_call */
    object_str,                                 /* tp_str */
    PyObject_GenericGetAttr,                    /* tp_getattro */
    PyObject_GenericSetAttr,                    /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   /* tp_flags */
    object_doc,                                 /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    object_richcompare,                         /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    object_methods,                             /* tp_methods */
    0,                                          /* tp_members */
    object_getsets,                             /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    object_init,                                /* tp_init */
    PyType_GenericAlloc,                        /* tp_alloc */
    object_new,                                 /* tp_new */
    PyObject_Del,                               /* tp_free */
};

注:本人没有系统性的学习过 C/C++ 所以在后文的表述中难免有些外行。

仅从当前展示的代码还比较难理解 CPython 中 Python 类的实现思路,需配合后文中类型的 CPython 源码分析才能更全面的理解 Python 中那些底层的特性是为何表现出来的。

object 分析

type(object) == type

从代码中不难发现 PyBaseObject_Type 是结构体 PyTypeObject 的一个实现。而且结构体的第一行 PyVarObject_HEAD_INIT(&PyType_Type, 0) 也印证了 type(object) == type(PyType_Type 是 type 的实现)。

object.__base__ == None

从源码中可以找到,tp_base 对应的值是 0。

最开始我是这么认为的,但后面当我想用同样的道理证明 tpye.__base__ == object 的时候却发现 PyType_Type 的 tp_base 也是 0。后面我在 古明地觉:《源码探秘 CPython》3. type 和 object 的恩怨纠葛 一文中看到了半句解释:

因为 Python 的动态性,显然不可能在定义的时候就将所有成员属性都设置好、然后解释器一启动就会得到我们平时使用的类型对象。 目前看到的类型对象是一个半成品,有一部分成员属性是在解释器启动之后再进行动态完善的。

我简单的翻看了一下后续的文章没有找到实际完善的地方,后续找到解释再进行补充吧。

object 里有什么

dir(object)
['__class__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']

# 除此之外,还有:
object.__bases__        # ()
object.__qualname__     # 'object'
object.__subclasses__() # 太多了...
object.mro()            # [object]

object.a = 1

object.a = 1

# TypeError: can't set attributes of built-in/extension type 'object'

a = object()
a.a = 1

# AttributeError: 'object' object has no attribute 'a'

a.__init__ = 1
AttributeError: 'object' object attribute '__init__' is read-only

类型:类的定义(描述)

官方文档

从 Python 官方文档来看,type 关键字和 object 关键字一样对应的是一个可调用对象,下面是官方文档对 type 的描述:

With one argument, return the type of an object. The return value is a type object and generally the same object as returned by object.class.

The isinstance() built-in function is recommended for testing the type of an object, because it takes subclasses into account.

With three arguments, return a new type object. This is essentially a dynamic form of the class statement. The name string is the class name and becomes the name attribute. The bases tuple contains the base classes and becomes the bases attribute; if empty, object, the ultimate base of all classes, is added. The dict dictionary contains attribute and method definitions for the class body; it may be copied or wrapped before becoming the dict attribute.

传入一个参数时,返回 object 的类型。 返回值是一个 type 对象,通常与 object.class 所返回的对象相同。

推荐使用 isinstance() 内置函数来检测对象的类型,因为它会考虑子类的情况。

传入三个参数时,返回一个新的 type 对象。 这在本质上是 class 语句的一种动态形式,name 字符串即类名并会成为 name 属性;bases 元组包含基类并会成为 bases 属性;如果为空则会添加所有类的终极基类 object。 dict 字典包含类主体的属性和方法定义;它在成为 dict 属性之前可能会被拷贝或包装。

——《Python 官方文档 - 内置函数》

CPython

PyTypeObject | 类型基石

// 注:为了阅读体验,在书写格式上有所调整
// cpython-root/Include/object.h

#define PyObject_VAR_HEAD      PyVarObject ob_base;

/* PyTypeObject structure is defined in cpython/object.h.
   In Py_LIMITED_API, PyTypeObject is an opaque structure. */
typedef struct _typeobject PyTypeObject;


// cpython-root/Include/cpython/object.h

typedef struct {
    lenfunc sq_length;
    binaryfunc sq_concat;
    ssizeargfunc sq_repeat;
    ssizeargfunc sq_item;
    void *was_sq_slice;
    ssizeobjargproc sq_ass_item;
    void *was_sq_ass_slice;
    objobjproc sq_contains;

    binaryfunc sq_inplace_concat;
    ssizeargfunc sq_inplace_repeat;
} PySequenceMethods;

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; // 析构
    Py_ssize_t tp_vectorcall_offset;
    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; // __repr__

    /* Method suites for standard classes */

    PyNumberMethods *tp_as_number; // 函数簇,封装了对应类型的操作,可以参考前文给出的 PySequenceMethods
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    /* More standard operations (here for binary compatibility) */

    hashfunc tp_hash; // __hash__
    ternaryfunc tp_call; // __call__
    reprfunc tp_str; // __str__
    getattrofunc tp_getattro; // __getattr__
    setattrofunc tp_setattro; // __setattr__

    /* Functions to access object as input/output buffer */
    PyBufferProcs *tp_as_buffer; // 类似前文的 PyNumberMethods

    /* Flags to define presence of optional/expanded features */
    unsigned long tp_flags; // 由各种标志组成的位掩码

    const char *tp_doc; /* Documentation string | __doc__ */

    /* 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; // regular methods
    struct PyMemberDef *tp_members; // regular data members
    struct PyGetSetDef *tp_getset;  // computed attribute
    struct _typeobject *tp_base;    // 基类(父类)
    PyObject *tp_dict; // __dict__
    descrgetfunc tp_descr_get; // 描述符相关
    descrsetfunc tp_descr_set; // 描述符相关
    Py_ssize_t tp_dictoffset;  // 与 __slots__ 有关
    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;
    vectorcallfunc tp_vectorcall;
};

通过分析 PyTypeObject 我们可以发现一些 Python 底层有趣的实现:

在 PyTypeObject 中有这样几个值

PyAsyncMethods *tp_as_async PyNumberMethods *tp_as_number; PySequenceMethods *tp_as_sequence; PyMappingMethods *tp_as_mapping; PyBufferProcs *tp_as_buffer;

这些值的类型分别对应到了同名的结构体上,而这些结构体中定义了相关类型的操作方法,正是由于这样的结构使得 Python 可以支持鸭子类型。

注:这里描述的并非鸭子类型的原理,仅仅是通过源码进行一些延伸。

除此之外,如果想了解更多 PyTypeObject 内部变量的具体意义可以访问 Python 官方文档-C API-Type Objects 进行查看。

PyType_Type | type 实现

PyTypeObject PyType_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "type",                                     /* tp_name */
    sizeof(PyHeapTypeObject),                   /* tp_basicsize */
    sizeof(PyMemberDef),                        /* tp_itemsize */
    (destructor)type_dealloc,                   /* tp_dealloc */
    offsetof(PyTypeObject, tp_vectorcall),      /* tp_vectorcall_offset */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_as_async */
    (reprfunc)type_repr,                        /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    (ternaryfunc)type_call,                     /* tp_call */
    0,                                          /* tp_str */
    (getattrofunc)type_getattro,                /* tp_getattro */
    (setattrofunc)type_setattro,                /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
    Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TYPE_SUBCLASS |
    Py_TPFLAGS_HAVE_VECTORCALL,                 /* tp_flags */
    type_doc,                                   /* tp_doc */
    (traverseproc)type_traverse,                /* tp_traverse */
    (inquiry)type_clear,                        /* tp_clear */
    0,                                          /* tp_richcompare */
    offsetof(PyTypeObject, tp_weaklist),        /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    type_methods,                               /* tp_methods */
    type_members,                               /* tp_members */
    type_getsets,                               /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    offsetof(PyTypeObject, tp_dict),            /* tp_dictoffset */
    type_init,                                  /* tp_init */
    0,                                          /* tp_alloc */
    type_new,                                   /* tp_new */
    PyObject_GC_Del,                            /* tp_free */
    (inquiry)type_is_gc,                        /* tp_is_gc */
};

type 分析

type(type) == type

从源码中可以发现 PyType_Type 也是结构体 PyTypeObject 的实现。同样结构体的第一行 PyVarObject_HEAD_INIT(&PyType_Type, 0) 印证了 type(type) == type

甚至:type(type(type)) == type,无论嵌套多少层都是成立的。

type.__base__ == object

原因待补充。

type 里有什么

dir(type)
['__abstractmethods__',
 '__base__',
 '__bases__',
 '__basicsize__',
 '__call__',
 '__class__',
 '__delattr__',
 '__dict__',
 '__dictoffset__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__flags__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__instancecheck__',
 '__itemsize__',
 '__le__',
 '__lt__',
 '__module__',
 '__mro__',
 '__name__',
 '__ne__',
 '__new__',
 '__prepare__',
 '__qualname__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasscheck__',
 '__subclasses__',
 '__subclasshook__',
 '__text_signature__',
 '__weakrefoffset__',
 'mro']

小结:对象与类型

至此,CPython 中用于实现 Python 类的几个比较重要的结构体和实现都被展示出来了:

CPython 结构关系

后文基本上都是基于 Python 的内容了,不会再设计 CPython 源码的列举。

类型实例(类对象):类的生成

根据前文的内容我们已然得知:类是由类型实例化得到的。

手写类的生成

class Foo:
    def __init__(self, name):
        self.name = name

def custom_init(self, name):
    self.name = name

Bar = type("Bar", (object, ), {"__init__": custom_init})

a = Foo("foo")
b = Bar("bar")

通过上面的代码我们能得到两个类对象:Foo 和 Bar,其中 Foo 的定义方式是在 Python 编码过程中常用的形式,而 Bar 的定义方式则更能体现「类是有类型实例化得到的」。

class Foo(metaclass=type):
    def __init__(self, name):
        self.name = name

当有很多属性和方法需要定义时,使用 Bar 的定义方式会显得很不方便。但在通过对 Foo 的定义方式进行改造后,同样能够帮助我们清楚地看清类对象生成过程。

class FooType(type):
    def __new__(cls, name, bases, attrs):
        return type.__new__(cls, name, bases, attrs)

class Foo(metaclass=FooTyoe):
    def __init__(self, name):
        self.name = name

为了更清楚的理解类型(元类)的工作过程,我封装了一个 FooType 类型,并让 Foo 类对象使用这个类型。

类对象

当我们使用编辑器在文件中定义好类的主体后,就可以使用 Python 解释器加载相关文件(模块)了,在 Python 解释器加载了相关文件(模块)后,定义好的类主体会被用来生成相应的类对象。

# test.py
class Foo:
    a = 1
    
    def __init__(self, b=None):
        self.b = b
    
    @staticmethod
    def static_foo():
        print(Foo.a)
    
    @classmethod
    def class_foo(cls):
        print(cls.a)
    
    def foo(self):
        print(self.a)

# Foo.__dict__
# mappingproxy({'__module__': '__main__',
#              'a': 1,
#              '__init__': <function __main__.Foo.__init__(self, b=None)>,
#              'static_foo': <staticmethod at 0x108992160>,
#              'class_foo': <classmethod at 0x108992e50>,
#              'foo': <function __main__.Foo.foo(self)>,
#              '__dict__': <attribute '__dict__' of 'Foo' objects>,
#              '__weakref__': <attribute '__weakref__' of 'Foo' objects>,
#              '__doc__': None})

类的继承

在 Python3 中,类的继承解析使用的是 C3 算法,可以参考我的另一篇文章:Python MRO

实例:类的实例

手写类的实例化

class Foo:
    def __init__(self, name):
        self.name = name

def instance_maker(the_class, *args, **kwargs):
    new_inscance = the_class.__new__(the_class, *args, **kwargs)
    if isinstance(new_inscance, the_class):
        the_class.__init__(new_inscance, *args, **kwargs)
    return new_inscance

# 以下两条语句效果一致
x = Foo("foo")
z = instance_maker(Foo, "foo")

实例对象

当我们在编码的过程中定义了类实例化的内容,并在导入相关文件(模块)后执行相关代码,Python 解释器会在执行到类实例化内容的时候生成实例对象。

class Foo:
    def __init__(self, name):
        self.name = name

foo = Foo("foo")

类对象和实例对象的简单对比

  • 类对象是全局唯一的;
  • 实例对象在每次执行到实例化内容时都会生成;
  • 可以利用各种技巧实现单例模式,使得类对象在实例化的过程中只返回唯一的实例;
  • 类对象可以调用类属性、类方法以及静态方法;
  • 实例对象可以调用类属性、类方法、实例属性、实例方法以及静态方法;

注:类对象也是可以调用实例方法的,只是需要传入一个存在的实例对象作为 self;

函数是如何变成方法的 | self 是在何时被赋值的

class Foo:
    def __init__(self, name):
        self.name = name
    
    def foo(self):
        print(self.name)

foo = Foo("foo")
foo.foo()
# foo

Foo.foo()
# TypeError: foo() missing 1 required positional argument: 'self'

type(Foo.foo) # function | 函数
type(foo.foo) # method   | 方法

Foo.foo # <function __main__.Foo.foo(self)>
foo.foo # <bound method Foo.foo of <__main__.Foo object at 0x10bcc01c0>>

dir(Foo.foo)
# ...
# __call__          # 调用时执行的方法
# __code__          # 包含已编译函数的代码对象 bytecode
# __defaults__      # 所有位置或关键字参数的默认值的元组
# __kwdefaults__    # 所有关键字参数默认值的映射
# ...

dir(foo.foo)
# ...
# __call__          # 调用时执行的方法
# __func__          # 实现该方法的函数对象  
# __self__          # 该方法被绑定的实例,若没有绑定则为 None
# ...

# 实例对象下方法的 __func__ 指向的是类对象中的函数
id(foo.foo)          # 4504426880
id(foo.foo.__func__) # 4505398432
id(Foo.foo)          # 4505398432

# 实例对象下的方法绑定的 self 是实例对象本身
id(foo)              # 4493634912
id(foo.foo.__self__) # 4493634912
  • 定义在类中的函数会在类对象实例化后化作实例对象中绑定方法的 __func__ 属性;
  • 方法会将自己绑定的实例对象存放在 __slef__ 属性中;
  • 绑定方法在执行时会将 __self__ 属性当做 __func__ 的第一个参数传入。

以上,方法在无形中将 self 传入到了函数中。

各种优先级对比

class Foo(Bar):
    a = 1
    b = 2
    def __init__(self, b=None):
        if b:
            self.b = b
    
    def echo(self):
        print(self.a, self.b)
    
    @property
    def name(self):
        return "foo"
    
    @property
    def foo(self):
        return "property"
    

foo = Foo("foo")

# 实例属性 > 类属性
foo.a = 3
foo.echo()           # 3 foo
print(Foo.a, Foo.b)  # 1 2

# property 特性 > 实例属性
foo.foo         # property
foo.foo = "foo" # AttributeError: can't set attribute
foo.__dict__["foo"] = "foo"
foo.foo         # property
foo.b           # foo
Foo.b = property(lambda self: "property")
foo.b           # property

小结:类型、类、实例

类型、类和实例的关系

概念的抽象

Python 之所以要设计这么复杂的一套逻辑来构建「类」体系,其核心目的就是为了帮助使用者在使用的过程中能够更加方便的进行概念抽象化,并且能够趁手的使用被抽象化后的类。

关于抽象,我最早产生好奇的时候搜到了 CSDN 杏仁技术栈转载的章烨明的 谈谈到底什么是抽象,以及软件设计的抽象原则 以及 optman 的 抽象的层次,此外应该还看过其他的一些文章但是我没有保存下来所以也没法考究了。在那之后,我大致能在编程的过程中理解抽象以及那些被抽象的东西,并且能够沿着抽象层级一点一点的去拨开各类开源库的核心,从中学习好的编码思想。

现在,我对「抽象」这个概念又产生了新的困惑,我开始好奇原本抽象的上一层抽象是什么,我还好奇数学是被发现的还是被发明的。通过最近这段时间的阅读我已经有了笼统的概念,下面是我的一些浅显的理解,希望能够帮助同样好奇的你。

Python 中的抽象

# 在编程过程中,下面的概念自左向右抽象等级依次提高
variable → collections → method → classmodule → package 
# 但从编程语言的实现角度来看所有的概念又可以抽象为 object
                        ↓↓↓
                       object
                      ↓       ↓
                    object   type
# 而在 Python 的最终实现上,object 这个抽象概念被实现成了 objecttype 两个 object
# 通过 objecttype 就可以生产所有最上面提到的概念

通过道德经歪解抽象

注:以下歪解大都是以「更好的理解 Python」为出发点的。

道生一,一生二,二生三,三生万物。

——《道德经》

人们将自己对于现实世界的观察(道)进行抽象产生了对象(一)这个概念,再以对象为基础创建了一门编程语言,这门编程语言中万事万物都是对象,并通过对象和类型两个概念(二)去描述所有的对象(万物)。

当然,人们对世界的观察并非真正的「道」而是人们对「道」的「名」。但放到一门编程语言中,这些「名」组成了这门语言的「道」。

“一”可以是阴,也可以是阳。但是只能是一个面。“一”有它的对立面,这就有了阴阳。“二”就是阴阳。阴阳是有力量的, 他们互不相容,必定要相互作用,这种相互作用的趋势,就是“三”,就是冲气。道生一,一生二,二生三,把“生”字换成“有”更好理解。道有一,也有零;一有它的对立面,这就构成了二;二有三,因为二中的两个一相互对立,所以要相互作用,这个趋势就是三。“三生万物”的“生”理解为“产生”更合适。

——《道德经》 富强 译注

参照注释的讲解,可以对我上面的描述进行更进一步的理解:在开始创建一门面向对象的编程语言的时候,这门语言中还没有任何概念,因此我们从现实生活中进行抽象,得到了「对象」。仅有「对象」这个概念是无法开展下一步的工作的,因此我们尝试对「对象」进行描述得到了「类型」,在有了「类型」后「对象」和「类型」就有了一种相互作用的能力:「(反)实例化」。

在有了这样的一门编程语言之后,我们就可以利用面向对象的特性和抽象思想将现实生活中的业务场景使用编程语言表达出来。

结语

本来是想好好的剖析一下 Python 中的类,结果写出这么一篇四不像来。在写作的过程中总是想涵盖更多的东西,结果发现自己好像 hold 不住,于是开始删减,只保留了当前的这些内容,希望能够帮助你更好的理解 Python 中的类。

参考

  1. Python 官方文档
  2. 灼弦-知乎回答:Python 的 type 和 object 之间是怎么一种关系?
  3. 高山流水-知乎回答:Python 的 type 和 object 之间是怎么一种关系?
  4. 网易云课堂-Python 微专业(目前不开了已经)
  5. Providence:Python 源码学习(1):类型和对象
  6. Luciano Ramalho:流畅的 Python(安道 吴珂译)
  7. Lx's Blog:【CPython3.6源码分析】PyObject/PyObjectType