`
字符串
  • 浏览: 38029 次
文章分类
社区版块
存档分类
最新评论

用ctypes观察Python对象的内存结构

阅读更多

对象的两个基本属性

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 ❶

4 7 ❷

5 7

6 7

7 7

8 12

9 12

10 12

11 12

12 12

❶一开始列表的长度和其指针数组的长度都是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

10 17

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

分享到:
评论

相关推荐

    python ctypes模块

    ### Python ctypes 模块详解 #### 一、ctypes 模块简介 ctypes 是一个在 Python 2.5 及以上版本自带的强大模块,它允许 Python 脚本动态加载和调用 C 库中的函数,使得 C/C++ 和 Python 之间的交互变得简单而高效。...

    ctypes--Python调用c接口.pdf

    这意味着在Python中可以使用ctypes模块创建与C语言兼容的数据结构和变量类型。这包括整型、浮点型、指针、数组、结构体等。 3. ctypes提供了一系列的对象,如cdll、windll和oledll,分别用于加载使用不同调用约定的...

    使用c lib的python模块ctypes

    8. **内存管理**:当传递Python对象到C函数时,`ctypes`会自动管理内存,但当传递C库分配的内存回Python时,需要手动释放,以避免内存泄漏。 9. **位序和字节对齐**:不同的系统可能有不同的字节序(大端或小端)和...

    ctypes库的使用 python调用Windows DLL

    ### ctypes库的使用:Python调用Windows DLL #### 一、ctypes库简介与功能 ctypes 是 Python 的一个标准库模块,它提供了与 C 兼容的数据类型,并且能够轻松地调用 C 库(DLL)中的函数。这对于那些需要与 C 语言...

    Python ctypes tkinter 调用API函数,设计窗口控制工具

    这是一个使用Python ctypes和tkinter模块设计, 用API函数管理电脑其他窗口的工具, 应用了ctypes模块调用API函数, tkinter库实现用户界面。 程序中,用户选择一个窗口,即可更改这个窗口的标题、边框样式、透明度等...

    Python-PythonMSS纯Python中使用ctypes的超快速跨平台多屏截图模块

    Python MSS 是一个强大的纯Python...总之,Python MSS是一个高效且易于使用的Python库,借助ctypes实现了跨平台的屏幕截图功能。无论你是进行基本的屏幕抓取,还是复杂的游戏截图应用,这个库都是一个值得信赖的工具。

    python从内存地址上加载python对象过程详解

    这篇文章主要介绍了python从内存地址上加载pythn对象过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在python中我们可以通过id函数来获取某个python...

    python-ctypes模块中文帮助文档参照.pdf

    Python ctypes 模块中文帮助文档参照 ctypes 模块是 Python 的一个外部扩展库,提供了一个强大的工具来调用动态链接库(DLL)。本文档将详细介绍 ctypes 模块的使用方法和相关知识点。 一、加载动态链接库 ...

    python ctypes 中文帮助

    ctypes提供了一种简单的方法,将Python对象转换为C数据类型,并且可以调用C库中的函数,使得Python具备了底层编程的能力。 1. **加载动态链接库** - `cdll` 和 `windll`:ctypes通过这两个接口加载动态链接库。`c...

    07-python-生成编译注释-ctypes

    `ctypes`库通过Python对象映射C的数据类型,并使用标准C调用约定。它允许我们定义C风格的函数原型,然后调用这些函数,就像它们是Python原生函数一样。`ctypes`可以处理基本的C数据类型,如`c_int`、`c_char_p`等,...

    利用ctypes提高Python的执行速度

    接下来通过两个具体案例,展示如何使用`ctypes`来提高Python的执行速度。 ##### 案例1:素数检测 1. **Python实现**:首先使用纯Python实现素数检测功能。 ```python import math from timeit import timeit ...

    python-ctypes模块中文帮助文档.docx

    ### Python ctypes 模块知识点详解 #### 一、加载动态链接库 `ctypes`模块在Python中主要用于加载和调用C语言编写的共享库(动态链接库)。无论是Windows下的`.dll`文件还是Linux下的`.so`文件,都可以通过`ctypes...

    Python库 | ctypes_windows_sdk-0.0.8-py2.py3-none-any.whl

    通过ctypes_windows_sdk,Python开发者无需编写C/C++代码,就能直接利用这些资源,这对于那些希望使用Python进行系统编程或与Windows系统紧密交互的开发者来说,是一个非常有用的工具。 在使用ctypes_windows_sdk时...

    python ctypes 调用宇视网络设备sdk 登录、截图、云台控制等,二次开发,联系方式1261806584(QQ)

    ctypes是Python的标准库之一,它允许开发者使用Python调用C语言的函数。通过定义C数据类型、函数原型以及导出的C函数,ctypes能够无缝地将C库集成到Python代码中。这使得Python开发者可以利用C库的强大功能,同时...

    python通过ctypes封装调用c开源音频引擎libsoundio

    3. **定义C数据类型**:由于Python和C的数据类型不完全兼容,我们需要使用ctypes的`c_int`, `c_char_p`等类型来映射C的数据结构。例如,如果libsoundio有一个`struct SoundIo`,我们需要定义对应的Python类。 ```...

    Python库 | pyzmq-ctypes-2.1.10.tar.gz

    资源分类:Python库 所属语言:Python 资源全名:pyzmq-ctypes-2.1.10.tar.gz 资源来源:官方 安装方法:https://lanzao.blog.csdn.net/article/details/101784059

    python ctypes实现查找系统进程和进程里的模块

    python ctypes实现查找系统进程和进程里的模块

    python3使用ctypes在linux下调用共享库的范例

    演示了 1.字符串入参 2.字符串出参 3.传入变参 比较全面了,python代码直接可用,记得把.so文件加入PYTHONPATH. unit1.pas是lazarus写的共享库代码

Global site tag (gtag.js) - Google Analytics