6 对象的创建
学习了上一篇博客的内容,我们可以进一步问一个问题了:假如我们命令python创建一个整数型对象,python内部是怎样从无到有创建一个对象的?
一般来说,方法有两种:一是通过C API创建,一是通过类型对象PyInt_Type创建。
1 用C API创建
所谓C API,就是python设计者在C语言源码中预留给开发者的现成的函数接口,封装好了新建、插入、析构等基本操作,方便用户自由拓展python的功能,当然,他们在python内部也是大量调用的。以新建对象为例,C API主要有两种形式:
1 PyObject* intobj = PyObject_New(Pyobject, &PyInt_Type); 2 PyObject* intobj = PyInt_FromLong(10);
第一种方法来自PyObject,是python内部所有类型通用的,可以返回一个某类型的对象,我们可以在objimpl.h头文件中见到他的真容:
【objimpl.h】 #define PyObject_New(type, typeobj) \ ( (type *) _PyObject_New(typeobj) )
这是一个宏定义,首先抛开_PyObject_New内部的细节,我们知道上面的第一行代码会返回一个PyObject类型的对象。然后再来看_PyObject_New函数:
【Object.c】 _PyObject_New(PyTypeObject *tp) { PyObject *op; op = (PyObject *) PyObject_MALLOC(_PyObject_SIZE(tp)); if (op == NULL) return PyErr_NoMemory(); return PyObject_INIT(op, tp); }
这里为PyObject类型的对象op开辟了空间,也就是新建了一个PyObject对象,然后转到PyObject_INIT。这次,我们干脆刨根问底:
【objimpl.h】 #define PyObject_INIT(op, typeobj) \ ( Py_TYPE(op) = (typeobj), _Py_NewReference((PyObject *)(op)), (op) )//很喜欢这里逗号表达式的巧妙运用 【object.h】 #define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) 【object.c】 void _Py_NewReference(PyObject *op) { _Py_INC_REFTOTAL; op->ob_refcnt = 1; _Py_AddToAllObjects(op, 1); _Py_INC_TPALLOCS(op); }
我们一口气找齐了创建对象过程中几乎所有的操作,现在梳理一下:
1 新建一个PyObject类型的指针并为它开辟内存空间
2 设置他的ob__type,是它称为指定类型
3 设置新对象的引用次数
所以,我们对第一种C API创建对象的过程已经很熟悉了。顺便提一下,相似的API还有很多,他们的格式均为PyObject_***,都是基于PyObject,但系统会根据对象类型的不同最终执行不同的函数。
与第一种API的普适性相对,第二种API是python为每种对象量身定制的,比如PyIntObject有PyInt_FromLong、PyInt_FromString等,PyStringObject有PyString_FromFormat、PyString_FromStringAndSize等等,具体的功能实现与函数名对应,再此只举一例:
【intobject.c】 PyObject * PyInt_FromLong(long ival) { … /* Inline PyObject_New */ v = free_list; free_list = (PyIntObject *)Py_TYPE(v); PyObject_INIT(v, &PyInt_Type); v->ob_ival = ival; return (PyObject *) v; }
这里的free_list涉及到python的缓冲机制,暂时就到后面讲,但是其他的操作,我们也都很熟悉,过程如下:
1 得到内存空间(这里没有用molloc函数分配,而是直接从缓冲池获取,这个后面再详细讲)
2 设置类型
3 初始化引用次数
4 设置自己的参数值(这里是整数的数值ob_ival)
2 通过类型对象创建
分析了两种通过python内部C API创建对象的方式,我们已经对python内部对象的创建过程有了比较清楚的认识,但是有一个问题你可能也注意到了,那就是这些强大的API永远只是对python的内建对象有效,而对于我们用户自定义的类型,比如calss A这样的对象,python的API就是束手无策了,因为系统不可能事先准备好PyA_New这样的函数。
但是,对于A这样的类型,python又是怎样创建实例的呢?这就需要我们把注意力从最底层的代码收回,看看python在调用自己的C API之前做了那些操作。
首先,回顾一下上篇博客中我们的结论,那就是最基本PyObject结构体中只有两项内容:
int ob_refcnt; \ struct _typeobject *ob_type;
_refcnt是个整数,新建对相关的操作显然跟他关系不大,据此,我们有理由相信,python中创建对象的秘密就隐藏在ob_type中了。让我们回到typeobject的定义:
【object.h】 /* Attribute descriptor and subclassing stuff */ struct PyMethodDef *tp_methods; struct PyMemberDef *tp_members; struct PyGetSetDef *tp_getset; struct _typeobject *tp_base; 。。。 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 */ 。。。 destructor tp_del;
从这段代码是从object.h中截取的PyTypeObject中的部分属性。其中,tp_base是当前类型对象的“基类”,跟java中“父类”的概念差不多;tp_new即为对象的创建方法。我们在创建对象时,一般会指定对象的类型,因此python就会从指定的类型对象中查找tp_new方法,执行新建的操作。为了说明这一点,我们做个简单的实验:
首先,创建一个整数对象i:
i = 10
在python中,我们是可以直接看到对象的tp_base和tp_new的:
>>> i = 10 >>> type(i) <type 'int'> >>> int.__new__ <built-in method __new__ of type object at 0x1E221000> >>> i.__new__ <built-in method __new__ of type object at 0x1E221000> >>> int.__base__ <type 'object'> >>> object.__new__ <built-in method __new__ of type object at 0x1E2272A8>
我们发现,整数i中有他的类型对象int相同的__new__函数,而且int的__new__函数与它的基类object的__new__函数不同。另外,我们也可以相信,int与object的__new__函数一定为我上面提到的C API,是python内部已经写好的。现在,我们可以回到之前的问题了:对于自定义的类型,我们没有实现为它编写任何__new__函数,它的__new__函数又如何呢?
为此,我们测试一个简单的不能再简单的自定义类型A:
class A(int): pass >>> A.__base__ <type 'int'> >>> A.__new__ <built-in method __new__ of type object at 0x1E221000> >>> A.__base__.__new__ <built-in method __new__ of type object at 0x1E221000> >>> A.__base__.__base__.__new__ <built-in method __new__ of type object at 0x1E2272A8>
因此,我们很清楚地看到,A的__new__正是来自它的基类int。
如果你还不太确定,我们不妨再测试一个自定义类B:
class B(A): pass >>> B.__base__ <class '__main__.A'> >>> B.__new__ <built-in method __new__ of type object at 0x1E2272A8> >>> B.__base__.__new__ <built-in method __new__ of type object at 0x1E2272A8>
现在,我们应该有理由相信了,python中创建对相关的机制是这样的:
1 从类型对象A中寻找tp_new方法
2 如果没找到,转而找到它的基类object,在object中找tp_new方法
3 以此类推,直到找到某个有tp_new的基类,返回他的方法
我们也知道,即使是自定义类型,python的也规定它必须继成某一基类,而通过层层追溯,最后总会找到一个是系统内建对象的基类,这时,就可以调用对应类型的tp_new方法,也就是我们上面介绍到的C API。当然,通过修改类中的__init__方法,我们还可以针对自己的类补充创建时的初始化操作。
对于python中对象的创建过程,今天就简单剖析到这了。事实上,python具体创建对象时,还会有许多复杂的操作,如缓冲池的使用等,这些后面会详细分析。持续关注啊!
相关推荐
《Python源码剖析——深度探索动态语言核心技术》是一本深入探讨Python编程语言核心机制的书籍。这本书旨在揭示Python内部的工作原理,帮助读者理解其动态语言的本质。通过阅读本书,开发者可以提升对Python语言的...
在Python源码剖析2——对象机制中,我们可以了解到Python如何创建、管理和操作各种对象,包括对象的生命周期、内存管理(如引用计数和垃圾回收)以及对象的继承和多态性。这对于我们理解Python的运行原理和优化代码...
《Python源码剖析-深度探索动态语言核心技术》是2008年出版的一本深入解析Python语言内核的专业书籍。本书旨在帮助读者理解Python的内部工作机制,通过细致地剖析源码,揭示Python作为动态语言的核心技术。对于想要...
《深入剖析iOS游戏开发:基于lhunath-Cocos2D-iPhone源码分析》 Cocos2D-iPhone是一款强大的2D游戏开发框架,适用于iOS平台。它基于Python的Cocos2D,提供了丰富的功能,如场景管理、动作效果、精灵动画等,使得...
《Python源码剖析——深度探索动态语言核心技术》是关于Python编程语言的一份深入解析资源,主要关注其内部工作机制。这份资源包含两个简单的范例,旨在帮助我们理解Python的语法解析和执行过程。以下是对这些知识点...
### Python爬虫基础教程——批量抓取 #### 一、引言 随着互联网技术的飞速发展,数据已经成为新时代的“石油”,而网络爬虫则是获取这些数据的重要工具之一。Python作为一种简洁高效的编程语言,拥有丰富的第三方...
本篇将深度剖析"iOS游戏应用源代码——unixpickle-BoxScreensaver-b87d9d3.zip"中的关键知识点,帮助开发者们增进对iOS游戏开发的理解。 首先,我们要了解的是"unixpickle"这个名字。Unixpickle是一个Python库,...
《深入探索Quo编程框架——源自quo-源码.rar的剖析》 Quo是一个功能强大的Python框架,专注于简化命令行接口的开发。它允许开发者创建交互式、灵活且易于维护的命令行应用。通过深入分析"quo-源码.zip"中的内容,...
第2章通过Android源码中的一处实例深入地介绍了JNI技术。 第3章围绕init进程,介绍了如何解析init.rc以启动Zygote和属性服务(property service)的工作原理。 第4章剖析了zygote和system_server进程的工作...