首先需要明确的是,在Python的世界里,一切都是对象。

一、PyObject

PyObject是Python对象机制的基石,一切对象都有相同的PyObject部分。

PyObject的定义如下:

// source file: [object.h]
/* 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*.*/
typedef struct _object {PyObject_HEAD;
} PyObject;

Python对象最基本的内容,都包含在PyObject_HEAD这个宏中:

// source file: [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/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD                   \_PyObject_HEAD_EXTRA                \Py_ssize_t ob_refcnt;               \struct _typeobject *ob_type;

当我们在Visual Studio的release模式下编译Python时,是不会定义符号Py_TRACE_REFS的,所以实际发布的Python中,PyObject的定义十分简单:

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

Py_ssize_t就是64位整型。

ob_refcnt指的是对象的引用计数,它跟Python的内存管理机制有关,即当ob_refcnt降为0时,该对象就会从堆上被删除。ob_type是一个指向_typeobject结构体的指针,_typeobject结构体对应着Python中的类型对象,所以ob_type用来指向对象的类型,而相对应的类型的一些特性则由_typeobject结构体定义。

所以,PyObject定义的所有对象的核心就两个,一个引用计数,一个类型信息。

这里给出_typeobject的部分定义:

// source file: [object.h]
typedef struct _typeobject {PyObject_VAR_HEADconst 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;cmpfunc tp_compare;reprfunc tp_repr;/* Method suites for standard classes */PyNumberMethods *tp_as_number;PySequenceMethods *tp_as_sequence;PyMappingMethods *tp_as_mapping;/* .......... */
} PyTypeObject;
二、PyIntObject

PyIntObject:整数对象,可以说是最简单的对象了。

话不多说,先上定义:

// source file: [intobject.h]
typedef struct {PyObject_HEADlong ob_ival;
} PyIntObject;

可以说是jtm简单了,就是在PyObject的基础上增加了一个long类型的变量。从这里可以知道,Python中的int类型实际上就是c中原生类型long的简单封装。对于PyIntObject,它的类型对象是PyInt_Type
PyIntObject的创建可以通过PyInt_FromLong()来实现。当然也有PyInt_FromString()PyInt_FromUnicode(),但最终实现也都是依靠PyInt_FromLong()。在说PyInt_FromLong()之前,先介绍一下Python的小整数对象和大整数对象的内存管理。

小整数对象

对于小整数对象,可能会在程序中非常频繁地使用,如for循环。因此,Python存在一个小整数池,避免小整数被一次又一次地使用malloc在堆上申请空间和free,从而提高Python运行效率。

// source file: [intobject.c]
#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
/* References to small integers are saved in this array so that theycan be shared.The integers that are saved are those in the range-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
#endif

small_ints保存了指向[-5, 257]地所有指针。当需要定义小整数对象时,只需要指向相应的内存地址,并将该对象的引用计数+1。因为small_ints保存着一份所有小整数的指针,所以它们的引用计数不会降为0。你也可以通过改变NSMALLNEGINTSNSMALLPOSINTS的值来改变小整数池的范围,但是需要你重新编译Python。

大整数对象

对于大整数对象,Python为其提供了一块内存空间供这些大整数轮流使用,内存块空间满了则再开辟一块内存空间。这样不必每个PyIntObject都向内存申请一次空间,一定程度上考虑了效率问题。内存块由PyIntBlock这个结构维护。

// source file: [intobject.c]
/* Integers are quite normal objects, to make object handling uniform.(Using odd pointers to represent integers would save much spacebut require extra checks for this special case throughout the code.)Since a typical Python program spends much of its time allocatingand deallocating integers, these operations should be very fast.Therefore we use a dedicated allocation scheme with a much loweroverhead (in space and time) than straight malloc(): a simplededicated free list, filled when necessary with memory from malloc().block_list is a singly-linked list of all PyIntBlocks ever allocated,linked via their next members.  PyIntBlocks are never returned to thesystem before shutdown (PyInt_Fini).free_list is a singly-linked list of available PyIntObjects, linkedvia abuse of their ob_type members.
*/#define BLOCK_SIZE      1000    /* 1K less typical malloc overhead */
#define BHEAD_SIZE      8       /* Enough for a 64-bit pointer */
#define N_INTOBJECTS    ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject))struct _intblock {struct _intblock *next;PyIntObject objects[N_INTOBJECTS];
};typedef struct _intblock PyIntBlock;static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;

实际上,Python是通过block_listfree_list这两个单向链表来实现大整数对象的内存管理的。block_list维护者一个个内存块,free_list维护者未使用的各个PyIntObject

block_list的节点类型是PyIntBlock,通过next指针来指向下一个节点。而free_list的节点类型是PyIntObject,它只有ob_refcntob_typeob_ival三个成员变量,是由哪个变量承担next的责任呢?别急,我们先看看Python怎么创建整数对象。

整型对象的创建和删除

前面说到,Python是通过PyInt_FromLong()这个C API来创建整型对象的。

// source file: [intobject.c]
PyObject* PyInt_FromLong(long ival)
{register PyIntObject *v;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0// [1]尝试使用小整数对象池if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {v = small_ints[ival + NSMALLNEGINTS];Py_INCREF(v);
#ifdef COUNT_ALLOCSif (ival >= 0)quick_int_allocs++;elsequick_neg_int_allocs++;
#endifreturn (PyObject *) v;}
#endif// [2]使用通用整数池if (free_list == NULL) {if ((free_list = fill_free_list()) == NULL)return NULL;}/* Inline PyObject_New */v = free_list;free_list = (PyIntObject *)Py_TYPE(v);(void)PyObject_INIT(v, &PyInt_Type);v->ob_ival = ival;return (PyObject *) v;
}

PyInt_FromLong()的执行流程很简单:

  • 如果小整数对象池机制被激活,则尝试使用小整数对象池
  • 否则,使用通用整数对象池

注意看第26行free_list = (PyIntObject *)Py_TYPE(v);,其中Py_TYPE()的定义为#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type),可见,free_list是利用ob_type这个字段来保存下一节点的地址。

fill_free_list()的调用时机有两个:一是首次调用PyInt_FromLong()时,这时候block_listfree_list都等于NULL;二是当block_list上所有的内存都填满之后,free_list也指向NULL。

下面是fill_free_list()的定义:

static PyFloatObject* fill_free_list(void)
{PyFloatObject *p, *q;/* XXX Float blocks escape the object heap. Use PyObject_MALLOC ??? */p = (PyFloatObject *) PyMem_MALLOC(sizeof(PyFloatBlock));if (p == NULL)return (PyFloatObject *) PyErr_NoMemory();((PyFloatBlock *)p)->next = block_list;block_list = (PyFloatBlock *)p;p = &((PyFloatBlock *)p)->objects[0];q = p + N_FLOATOBJECTS;while (--q > p)Py_TYPE(q) = (struct _typeobject *)(q-1);Py_TYPE(q) = NULL;return p + N_FLOATOBJECTS - 1;
}

Python中不同对象在销毁时会进行不同的动作,销毁动作在与对象对应的类型对象中被定义,这个关键的操作就是类型对象中的tp_dealloc。(记得吗,对象的类型对象ob_type)。
PyIntObject对象的tp_dealloc操作如下:

// source file: [intobject.c]
static void int_dealloc(PyIntObject *v)
{if (PyInt_CheckExact(v)) {Py_TYPE(v) = (struct _typeobject *)free_list;free_list = v;}elsePy_TYPE(v)->tp_free((PyObject *)v);
}

正常的销毁过程(待销毁PyIntObject对象v):

  1. v->ob_type = free_list
  2. free_list = v

现在了解了整数的创建与销毁的过程:创建过程首先尝试小整数池,再尝试通用整数池。通用整数池通过fill_free_list()来初始化内存块,那么,小整数池是什么时候初始化的呢?完成小整数池的创建和初始化的函数是_PyInt_Init():

// source file: [intobject.c]
int _PyInt_Init(void)
{PyIntObject *v;int ival;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++) {if (!free_list && (free_list = fill_free_list()) == NULL)return 0;/* PyObject_New is inlined */v = free_list;free_list = (PyIntObject *)Py_TYPE(v);(void)PyObject_INIT(v, &PyInt_Type);v->ob_ival = ival;small_ints[ival + NSMALLNEGINTS] = v;}
#endifreturn 1;
}

_PyInt_Init()可以看出,小整数对象也是存在与由block_listfree_list两个单向链表维护的内存块中,创建和初始化之后就永生不灭,不会也不可能被加入到free_list的范围。

三、PyStringObject
定长对象和变长对象

对于PyIntObject对象,无论整数有多大,都可以保存在ob_ival中。但是对于字符串对象,就没那么简单了。显然,字符串对象需要维护n个char型变量。看上去这种“n个…”似乎也是一类Python对象的共同特征,因此,Python在PyObject对象之外还有一类表示可变长度对象的结构体–PyVarObject

/* PyObject_VAR_HEAD defines the initial segment of all variable-size* container objects.  These end with a declaration of an array with 1* element, but enough space is malloc'ed so that the array actually* has room for ob_size elements.  Note that ob_size is an element count,* not necessarily a byte count.*/
#define PyObject_VAR_HEAD               \PyObject_HEAD                       \Py_ssize_t ob_size; /* Number of items in variable part */typedef struct {PyObject_VAR_HEAD
} PyVarObject;

不包含可变长度数据的对象称为“定长对象”,而像字符串对象这样的包含可变长度数据的对象称为“变长对象”,他们的区别在于定长对象的不同对象占用的内存大小是一样的,而变长对象的不同对象占用的内存可能不一样。ob_size指的是变长对象中元素的个数,而不是字节长度。

还可以将对象分为可变对象mutable和不可变对象immutable。像intstrtuple都属于不可变对象类型,list属于可变对象类型。

定长对象和变长对象是针对同一类型不同对象的说法,可变对象和不可变对象是针对某个对象的说法。

字符串的定义和创建

现在,我们可以看看PyStringObject在Python中的定义了:

// source file: [stringobject.h]
typedef struct {PyObject_VAR_HEADlong ob_shash;int ob_sstate;char ob_sval[1];
} PyStringObject;

虽然在PyStringObject的定义中,ob_sval是一个字符的字符数组(为什么不用char *ob_sval呢?),但是它实际上是作为一个字符指针指向一段内存的,而这段内存的大小由ob_size和变长对象元素的单位长度决定的。对于PyStringObject,元素为char,单位长度为1。同C中字符串一样,PyStringObject内部维护的字符串末尾必须以'\0'结尾,所以ob_sval实际上指向的是一段长度为(ob_size + 1) * sizeof(char)个字节的内存。

由于PyStringObject的长度由ob_size维护,所以PyStringObject对象的中间可能会出现'\0'。这里与C中遇到'\0'就认为字符串结束不同。

ob_shash用来缓存PyStringObject对象的hash值,它在dict对象中会发挥重要作用。如果一个PyStringObject对象还没有被计算hash值,那么ob_shash的初始值为-1
ob_sstate用来标记该PyStringObject对象是否已经经过intern机制的处理。

PyStringObject的创建有好几条路径,我们先看看最基本的PyString_FromString()

// source file: [stringobject.c]
PyObject *
PyString_FromString(const char *str)
{register size_t size;register PyStringObject *op;assert(str != NULL);size = strlen(str);/* ...... *//* Inline PyObject_NewVar */op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + size);if (op == NULL)return PyErr_NoMemory();(void)PyObject_INIT_VAR(op, &PyString_Type, size);op->ob_shash = -1;op->ob_sstate = SSTATE_NOT_INTERNED;Py_MEMCPY(op->ob_sval, str, size+1);/* ...... */return (PyObject *) op;
}

PyStringObject对象创建完之后,在内存中的状态如下:

字符串对象的intern机制

PyString_FromString()后面有这么一段代码:

// source file: [stringobject.c]
PyObject* PyString_FromString(const char *str)
{/* ...... *//* share short strings */if (size == 0) {PyObject *t = (PyObject *)op;PyString_InternInPlace(&t);op = (PyStringObject *)t;nullstring = op;Py_INCREF(op);} else if (size == 1) {PyObject *t = (PyObject *)op;PyString_InternInPlace(&t);op = (PyStringObject *)t;characters[*str & UCHAR_MAX] = op;Py_INCREF(op);}return (PyObject *) op;
}

这里为长度为0和1的字符串都做了intern处理,也就是PyString_InternInPlace()

字符串的对象intern机制之目的是:对于被intern之后的字符串,比如"Ruby",在整个Python的运行期间,系统中都只有唯一的一个与字符串"Ruby"对应的PyStringObject对象。这样当判断两个PyStringObject对象是否相同是,如果它们都被intern了,那么只需要简单地检查他们对应的PyObject*是否相同即可。这个机制既节省了空间,又简化了对PyStringObject对象的比较。

PyString_InternInPlace()的定义如下:

// source file: [stringobject.c]
void PyString_InternInPlace(PyObject **p)
{register PyStringObject *s = (PyStringObject *)(*p);PyObject *t;if (s == NULL || !PyString_Check(s))Py_FatalError("PyString_InternInPlace: strings only please!");/* If it's a string subclass, we don't really know what puttingit in the interned dict might do. */if (!PyString_CheckExact(s))return;if (PyString_CHECK_INTERNED(s))return;if (interned == NULL) {interned = PyDict_New();if (interned == NULL) {PyErr_Clear(); /* Don't leave an exception */return;}}t = PyDict_GetItem(interned, (PyObject *)s);if (t) {Py_INCREF(t);Py_SETREF(*p, t);return;}if (PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s) < 0) {PyErr_Clear();return;}/* The two references in interned are not counted by refcnt.The string deallocator will take care of this */Py_REFCNT(s) -= 2;PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL;
}

可以看到,intern机制的实现主要依靠一个名为interneddict实现的,interned是一个<PyObject*, PyObject*>的字典。在为某个字符串对象a施加intern机制时首先判断interned中是否存在b,它的原生字符串内容与a相同,如果有,则将指向a的指针指向b,然后a的引用计数-1;否则,将a添加到interned

在为对象a设置interned时,会创建<PyObject*, PyObject*>键值对,而这两个PyObject*都会使对象a的引用计数+1,但是由于这两个指针的特殊性,这两个指针不应该影响对象a的引用计数,否则对象a的引用计数将永远无法降为0。这正是会有Py_REFCNT(s) -= 2的原因。

据我了解(不确定),Python会为由字符、数字、下划线组成的字符串施加intern,但是在PyString_FronString()中只看到了长度为0和1时的intern处理。其他的intern操作发生在哪里??

字符缓冲池

小整数的缓冲池是在Python初始化时被创建的,而字符的缓冲池在Python初始化时都指向NULL,当字符被创建时才加入到字符缓冲池。

// source file: [stringobject.c]
static PyStringObject *characters[UCHAR_MAX + 1];

《python源码剖析》第一部分 作者:陈儒 - python的内建对象 <一>相关推荐

  1. Python 源码剖析(一)—— vs2013 编译 python 源码

    参考 windows环境下编译python 准备 VS 2013(其实 vs 版本编译的差异不大,设置也基本相同) python 源码文件: Python-2.7.3.tgz 编译 (1)解压 Pyt ...

  2. Python 源码剖析(二)—— 第一次修改 Python 源代码

    对于输出信息,使用 printf 最为简单.但是 printf 要输出 Python 中的某个对象却不是那么方便,幸好 Python 的 C API 提供了一个输出对象的接口: object.h(在 ...

  3. python源码剖析读书笔记总结_《Python源码剖析》读书笔记:内存垃圾回收

    Python内存回收的基石是引用计数,"当一个对象的引用被创建或复制时,对象的引用技术加1:当一个对象的引用被销毁时,对象的引用技术减1",如果对象的引用计数减少为0,将对象的所占 ...

  4. Python猫荐书系统之四:《Python源码剖析》

    大家好,新一期的荐书栏目如期跟大家见面了. 先来看看今天的主角是谁:<Python源码剖析--深度探索动态语言核心技术>,2008年出版,作者 @陈儒 ,评分8.7分. 是的,你没看错,出 ...

  5. Python源码剖析[16] —— Pyc文件解析

    Python源码剖析[16] -- Pyc文件解析 2008-02-28 18:29:55|  分类: Python |举报 |字号 订阅 Python源码剖析 --Pyc文件解析 本文作者: Rob ...

  6. Python源码剖析[1] —— 编译Python

    [ 绝对原创,转载请注明出处] 注意 :第一部分Python总体架构采用了网络文档<The Architecture of Python>,这是网络上唯一可见的以剖析Python实现为己任 ...

  7. Python源码剖析:前言

    第0章:前言 0.0 我的前言  在几个月学习的中,已经学习了python基本.进阶的语法,如果有读者不清楚的话,可以参考我之前的专栏<python进阶>.  而在这个专栏<pyth ...

  8. python源码剖析代码例子_Python源码剖析笔记5-模块机制

    python中经常用到模块,比如import xxx,from xxx import yyy这样子,里面的机制也是需要好好探究一下的,这次主要从黑盒角度来探测模块机制,源码分析点到为止,详尽的源码分析 ...

  9. Python源码剖析[19] —— 执行引擎之一般表达式(2)

    Python源码剖析 --Python执行引擎之一般表达式(2) 本文作者: Robert Chen(search.pythoner@gmail.com ) 3.2     Simple.py 前面我 ...

  10. python源码剖析—— python中的字节码对象初探

    一.代码对象 每个初学python的人都会认为python是一种解释型语言,这个不能说错.但是python并不是真的对执行的python代码的每一行进行解释,虽然我们有一个所谓的"解释器&q ...

最新文章

  1. Docker run 命令【转】
  2. 维修技嘉B250M-DS3H不开机一例
  3. 4、Docker 提交运行中容器作为新的镜像
  4. 【CentOS】磁盘管理与vim编译器
  5. python中的线程threading.Thread()使用
  6. 一文弄懂Numpy中ndarray的维度(dimension)/轴数(axis/axes)问题
  7. cpri带宽不足的解决方法_白皮书:FPGA赋能下一代通信和网络解决方案(第四部分)...
  8. mysql 设计模式_mysql – 你会推荐什么版本设计模式
  9. httpHandler实现.Net无后缀名Web访问
  10. Tecplot 安装记录
  11. Ubuntu 12 ssh 安装 终端命令 报软件依赖 错误(未解决)
  12. 苹果手机声音突然变小是怎么回事_苹果手机听筒声音小怎么回事?
  13. Python -- 关于函数的学习(五) — 传递任意数量的实参
  14. 第1节 细胞是生命活动的基本单位
  15. arduino/Mixly心知天气
  16. ArcGIS工具 - 导出数据库结构
  17. 目前计算机病毒只有网络病毒吗,目前计算机病毒只有网络型病毒。()
  18. 2021年三季度中国家居用品行业A股上市企业营收排行榜:欧派家居、顾家家居排名前2位,且近五年第三季度的净利润均逐年递增(附热榜TOP61详单)
  19. java计算机毕业设计幼儿影视节目智能推荐系统源码+数据库+系统+lw文档+部署
  20. Go模拟Kubernetes Client进行单元测试

热门文章

  1. FR8012HAQ利用ADC实现检测电池电压检测的解决方案
  2. 我训练了一个AI来复制我的声音,结果吓坏了我
  3. 好的管理系统应该为健身俱乐部做这六件事
  4. 派代年会:85后总裁董欣达亮相
  5. Kafka的结构、特点和原理(细节)
  6. 【U8+】用友U8+现金流量参照科目为对方科目时,现金流量表的设置
  7. Arduino Uno 连接 接近开关
  8. Python flappy bird 小游戏
  9. 超级计算机由,官方:现在的世界500强超级计算机由Linux
  10. mysql数据库中针对敏感信息字段加密处理问题