对象的两个基本属性
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
可以看出大指针数组的位置没有发生变化,但是后面额外的空间被回收了。
相关推荐
这是python2.5以后自带的一个很强大的模块,完美的将c c++于python结合起来
英文pdf, ctypes: python调用C动态库的接口库
ctypes库的使用 python调用Windows DLL ctypes是Python的一个外部库,提供和C语言兼容的数据类型,可以很方便地调用C DLL中的函数。
Python MSS - 纯Python中使用ctypes的超快速跨平台多屏截图模块
这篇文章主要介绍了python从内存地址上加载pythn对象过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在python中我们可以通过id函数来获取某个python...
python中调用c库的模块。 安装方法: linux: python setup.py install windows: setup.py install
给大家介绍了如何利用ctypes提高Python的执行速度,对大家学习使用python具有一定的参考借鉴价值。有需要的朋友们一起来看看吧。
资源分类:Python库 所属语言:Python 资源全名:pyzmq-ctypes-2.1.10.tar.gz 资源来源:官方 安装方法:https://lanzao.blog.csdn.net/article/details/101784059
这是一个使用Python ctypes和tkinter模块设计, 用API函数管理电脑其他窗口的工具, 应用了ctypes模块调用API函数, tkinter库实现用户界面。 程序中,用户选择一个窗口,即可更改这个窗口的标题、边框样式、透明度等...
python-ctypes模块中文帮助文档参照.pdf
python ctypes 中文帮助,有助于我们理解ctypes
python-ctypes模块中文帮助文档.docx
python ctypes实现查找系统进程和进程里的模块
t532.rar 测试代码 https://blog.csdn.net/wowocpp/article/details/105382257 python 调用 C++ dll 32位 64位 问题 ctypes.cdll.LoadLibrary
演示了 1.字符串入参 2.字符串出参 3.传入变参 比较全面了,python代码直接可用,记得把.so文件加入PYTHONPATH. unit1.pas是lazarus写的共享库代码
python学习笔记,包含生成编译注释,ctypes
OWFS' ( ) 使用 ctypes 的 python libowcapi 包装器 这个包提供了一个带有围绕 libowcapi (pyowfs/libcapi.py) 的薄包装器和一个稍微智能的包装器 (pyowfs/owfs.py) 的模块,以便于访问 1wire 设备。 它的灵感来自...
python通过ctypes调用c语言库函数;python向c函数传递二维数组,一维数组,指针灯参数;python向c函数传递结构体指针,c函数向python返回结构体指针;python向c以参数形式传递回调函数,且c函数回调python回调函数;...
python通过ctypes封装调用c开源音频引擎libsoundio,涉及到ctypes调用,定义复杂结构体,python可变参数封包和拆包等操作
python库,解压后可用。 资源全名:ctypes_windows_sdk-0.0.8-py2.py3-none-any.whl