!!!强烈推荐的好文章!!!

对象的两个基本属性

Python所有对象结构体中的头两个字段都是相同的:

  • refcnt:对象的引用次数,若引用次数为0则表示此对象可以被垃圾回收了。
  • typeid:指向描述对象类型的对象的指针。

通过ctypes,我们可以很容易定义一个这样的结构体:PyObject。

本文只描述在32位操作系统下的情况,如果读者使用的是64位操作系统,需要对程序中的一些字段类型做一些改变。
from ctypes import *

class PyObject(Structure):
_fields_ = [("refcnt", c_size_t),
("typeid", c_void_p)]

下面让我们用PyObject做一些实验帮助理解这两个字段的含义:

>>> a = "this is a string"
>>> obj_a = PyObject.from_address(id(a)) ❶
>>> obj_a.refcnt ❷
1L
>>> b = [a]*10
>>> obj_a.refcnt ❸
11L
>>> obj_a.typeid ❹
505269056
>>> id(type(a))
505269056
>>> id(str)
505269056

❶通过id(a)可以获得对象a的内存地址,而PyObject.from_address()可以将指定的内存地址的内容转换为一个PyObject对象。通过此PyObject对象obj_a可以访问对象a的结构体中的内容。

❷查看对象a的引用次数,由于只有a这个名字引用它,因此值为1。接下来创建一个列表,此列表中的每个元素都是对象a,因此此列表应用了它10次,❸所以引用次数变为了11。

❸查看对象a的类型对象的地址,它和id(type(a))相同,而由于对象a的类型为str,因此也就是id(str)。

下面查看str类型对象的这两个字段:

>>> obj_str = PyObject.from_address(id(str))
>>> obj_str.refcnt
252L
>>> obj_str.typeid
505208152
>>> id(type)
505208152

可以看到str的类型就是type。再看看type对象:

>>> type_obj = PyObject.from_address(id(type))
>>> type_obj.typeid
505208152

type对象的类型指针就指向它自己,因为“type(type) is type”。

整数和浮点数对象

接下来看看整数和浮点数对象,这两个对象除了有PyObject中的两个字段之外,还有一个val字段保存实际的值。因此Python中一个整数占用12个字节,而一个浮点数占用16个字节:

>>> sys.getsizeof(1)
12
>>> sys.getsizeof(1.0)
16

我们无需重新定义refcnt和typeid这两个字段,通过继承PyObject,可以很方便地定义整数和浮点数对应的结构体,它们会继承父类中定义的字段:

class PyInt(PyObject):
_fields_ = [("val", c_long)] class PyFloat(PyObject):
_fields_ = [("val", c_double)]

下面是使用PyInt查看整数对象的例子:

>>> i = 2000
>>> i_obj = PyInt.from_address(id(a))
>>> i_obj.refcnt
1L
>>> i_obj.val
2000

通过PyInt对象,还可以修改整数对象的内容:

修改不可变对象的内容会造成严重的程序错误,请不要用于实际的程序中。
>>> j = i
>>> i_obj.val = 2012
>>> j
2012

由于i和j引用的是同一个整数对象,因此i和j的值同时发生了变化。

结构体大小不固定的对象

表示字符串和长整型数的结构体的大小不是固定的,这些结构体在C语言中使用了一种特殊的字段定义技巧,使得结构体中最后一个字段的大小可以改变。由于结构体需要知道最后一个字段的长度,因此这种结构中包含了一个size字段,保存最后一个字段的长度。在ctypes中无法表示这种长度不固定的字段,因此我们使用了动态创建结构体类的方法。

class PyVarObject(PyObject):
_fields_ = [("size", c_size_t)] class PyStr(PyVarObject):
_fields_ = [("hash", c_long),
("state", c_int),
("_val", c_char*0)] ❶ class PyLong(PyVarObject):
_fields_ = [("_val", c_uint16*0)] def create_var_object(struct, obj):
inner_type = None
for name, t in struct._fields_:
if name == "_val": ❷
inner_type = t._type_
if inner_type is not None:
tmp = PyVarObject.from_address(id(obj)) ❸
size = tmp.size
class Inner(struct): ❹
_fields_ = [("val", inner_type*size)]
Inner.__name__ = struct.__name__
struct = Inner
return struct.from_address(id(obj))

❶在定义长度不固定的字段时,使用长度为0的数组定义一个不占内存的伪字段_val。create_var_object()用来创建大小不固定的结构体对象,❷首先搜索名为_val的字段,并将其类型保存到inner_type中。❸然后创建一个PyVarObject结构体读取obj对象中的size字段。❹再通过size字段的大小创建一个对应的Inner结构体类,它可以从struct继承,因为struct中的_val字段不占据内存。

下面我们用上面的程序做一些实验:

>>> s_obj = create_var_object(PyStr, s)
>>> s_obj.size
9L
>>> s_obj.val
'abcdegfgh'

当整数的范围超过了0x7fffffff时,Python将使用长整型整数:

>>> l = 0x1234567890abcd
>>> l_obj = create_var_object(PyLong, l)
>>> l_obj.size
4L
>>> val = list(l_obj.val)
>>> val
[11213, 28961, 20825, 145]

可以看到Python用了4个16位的整数表示0x1234567890abcd,下面我们看看长整型数是如何用数组表示的:

>>> hex((val[3] << 45) + (val[2] << 30) + (val[1] << 15) + val[0])
'0x1234567890abcdL'

即数组中的后面的元素表示高位,每个16为整数中有15位表示数值。

列表对象

列表对象的长度是可变的,因此不能采用字符串那样的结构体,而是使用了一个指针字段items指向可变长度的数组,而这个数组本身是一个指向PyObject的指针。allocated字段表示这个指针数组的长度,而size字段表示指针数组中已经使用的元素个数,即列表的长度。列表结构体本身的大小是固定的。

class PyList(PyVarObject):
_fields_ = [("items", POINTER(POINTER(PyObject))),
("allocated", c_size_t)] def print_field(self):
print self.size, self.allocated, byref(self.items[0])

我们用下面的程序查看往列表中添加元素时,列表结构体中的各个字段的变化:

def test_list():
alist = [1,2.3,"abc"]
alist_obj = PyList.from_address(id(alist)) for x in xrange(10):
alist_obj.print_field()
alist.append(x)

运行test_list()得到下面的结果:

>>> test_list()
3 3 <cparam 'P' (02B0ACE8)> ❶
4 7 <cparam 'P' (028975A8)> ❷
5 7 <cparam 'P' (028975A8)>
6 7 <cparam 'P' (028975A8)>
7 7 <cparam 'P' (028975A8)>
8 12 <cparam 'P' (02AAB838)>
9 12 <cparam 'P' (02AAB838)>
10 12 <cparam 'P' (02AAB838)>
11 12 <cparam 'P' (02AAB838)>
12 12 <cparam 'P' (02AAB838)>

❶一开始列表的长度和其指针数组的长度都是3,即列表处于饱和状态。因此❷往列表中添加新元素时,需要重新分配指针数组,因此指针数组的长度变为了7,而地址也发生了变化。这时列表的长度为4,因此指针数组中还有3个空位保存新的元素。由于每次重新分配指针数组时,都会预分配一些额外空间,因此往列表中添加元素的平均时间复杂度为O(1)。

下面再看看从列表删除元素时,各个字段的变化:

def test_list2():
alist = [1] * 10000
alist_obj = PyList.from_address(id(alist)) alist_obj.print_field()
del alist[10:]
alist_obj.print_field()

运行test_list2()得到下面的结果:

>>> test_list2()
10000 10000 <cparam 'P' (034E5AB8)>
10 17 <cparam 'P' (034E5AB8)>

可以看出大指针数组的位置没有发生变化,但是后面额外的空间被回收了。

文章来源:http://hyry.dip.jp/tech/slice/slice.html/10

Python 用ctypes观察Python对象的内存结构 -- (转)的更多相关文章

  1. java中的进程与线程及java对象的内存结构【转】

    原文地址:http://rainforc.iteye.com/blog/2039501   1.实现线程的三种方式:   使用内核线程实现     内核线程(Kernel Thread, KLT)就是 ...

  2. JVM——深入分析对象的内存布局

    概述 一个对象本身的内在结构需要一种描述方式,这个描述信息是以字节码的方法存储在方法区中的.Class本身就是一个对象,都以KB为单位,如果new Integer()为了表示一个数据就占用KB级别的内 ...

  3. java对象在内存中的结构

    在HotspotJVM中,32位机器下,Integer对象的大小是int的几倍? 我们都知道在java语言规范已经规定了int的大小是4个字节,那么Integer对象的大小是多少呢?要知道一个对象的大 ...

  4. [Java基础] Java对象内存结构

    转载地址:http://www.importnew.com/1305.html 原文于2008年11月13日 发表, 2008年12月18日更新:这里还有一篇关于Java的Sizeof运算符的实用库的 ...

  5. java对象在内存中的结构(HotSpot虚拟机)

    一.对象的内存布局 HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header).实例数据(Instance Data)和对齐填充(Padding). 从上面的这张图里面可以 ...

  6. Java对象在内存的结构

    一.内存布局 对象在内存中存储的结构由三部分组成:对象头.实例数据.对齐填充. 对象头 MarkWord(标记字段):哈希码.分代年龄.锁标志位.偏向线程ID.偏向时间戳等信息.Mark Word被设 ...

  7. 面试题:JVM在Java堆中对对象的创建、内存结构、访问方式

    一.对象创建过程 1.检查类是否已被加载 JVM遇到new指令时,首先会去检查这个指令参数能否在常量池中定位到这个类的符号引用,检查这个符号引用代表的类是否已被加载.解析.初始化,若没有,则进行类加载 ...

  8. python的变量,对象的内存地址以及参数传递过程

    作为一个由c/c++转过来的菜鸟,刚接触Python的变量的时候很不适应,应为他的行为很像指针,void* ,不知道大家有没有这样的感觉.其实Python是以数据为本,变量可以理解为标签.作为c/c+ ...

  9. 使用ctypes在Python中调用C++动态库

    使用ctypes在Python中调用C++动态库 入门操作 使用ctypes库可以直接调用C语言编写的动态库,而如果是调用C++编写的动态库,需要使用extern关键字对动态库的函数进行声明: #in ...

随机推荐

  1. Windows资源监控工具大全

    在利用LoadRunner进行性能测试的时候,Windows服务器的资源是经常需要监控的对象.其实除了LoadRunner提供的计数器,似乎Window服务器并不像Unix或者Linux提供众多的性能 ...

  2. php裁剪图片(支持定点裁剪)

    /** * 图片裁剪函数,支持指定定点裁剪和方位裁剪两种裁剪模式 * @param <string> $src_file 原图片路径 * @param <int> $new_w ...

  3. Linux下安装MySQL管理工具MySQL Administrator和MySQL Query Browser(转载)

    文章来源:http://blog.csdn.net/sunrier/article/details/7572299 Linux下MySQL Administrator和MySQL Query Brow ...

  4. Python35 升级 pip

    使用pip安装插件的时候报错: You are using pip version 8.1.1, however version 9.0.1 is available.You should consi ...

  5. asp.net 间隔一段时间执行某方法

    设想网站后台每秒自动更新一下Cache["test"]中的值,通过这个实现就可以完成一些在间隔多少时间更新一下数据库的操作. 1.定义一个事件类BMAEvent,在Processo ...

  6. java计算某日期之后的日期

    public static void main(String[] args) { // 时间表示格式可以改变,yyyyMMdd需要写例如20160523这种形式的时间 SimpleDateFormat ...

  7. BZOJ 1221 软件开发(费用流)

    容易看出这是显然的费用流模型. 把每天需要的餐巾数作为限制.需要将天数拆点,x’表示每天需要的餐巾,x’’表示每天用完的餐巾.所以加边 (s,x',INF,0),(x'',t,INF,0). 餐巾可以 ...

  8. cogs1667[SGU422]傻叉小明打字

    其实和CF498bName that Tune差不多 题意: 现在需要依次输入n个字符,第i个字符输入的时候有pi的概率输错,不论是第几次输入(0<=pi<=0.5).每输入一个字符的用时 ...

  9. 【bzoj4921】[Lydsy六月月赛]互质序列 暴力

    题目描述 给出一个序列,要求删除一段非空区间,使得剩下的数的个数大于等于2.求所有删除方式剩下的数的最大公约数的和. 输入 第一行包含一个正整数n(3<=n<=100000),表示序列的长 ...

  10. wsgiref 源码解析

    Web Server Gateway Interface(wsgi),即Web服务器网关接口,是Web服务器软件和用Python编写的Web应用程序之间的标准接口. 想了解更多关于WSGI请前往: h ...