python中ctypes的使用尝试

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

python中ctypes的使⽤尝试
最近在看Python的性能优化⽅⾯的⽂章,突然想起ctypes这个模块,对于这个模块⼀直不是很理解,不过再次看完相关资料有了些新的观点。

ctypes 这个模块个⼈观点就是提供⼀个Python类型与C类型数据转换接⼝或者说是规则的⼀个模块。

ctypes定义的数据类型其实并不是⼀种数据类型,⽽更应该说是⼀种转换规则。

ctypes定义的数据类型都是需要和Python数据类型进⾏关联的,然后传给C函数进⾏调⽤,在C函数调⽤的时候是根据ctypes的数据类型进⾏转换的,把关联的Python数据类型转换为C数据类型传给C函数。

如果是ctypes定义的指针或者地址,其实是将Python变量对应的内存空间地址中的数据与ctypes数据类型进⾏关联,如果C函数内部对传过来的指针地址对应的变量进⾏修改,最后是ctypes将修改好的C数据类型转为Python类型数据并将其存⼊之前Python变量对应的内存空间中。

在调⽤ctypes时,程序的内存空间其实可以分为Python数据内存空间与C数据类型空间。

ctypes定义的数据类型就是提供了⼀个Python数据类型与C数据类型转换的对应关系。

ctypes定义的数据类型都是需要和Python数据类型关联的,在调⽤C函数的时候在实时的转为C数据类型。

其中,Python数据类型存在与Python数据内存空间中,C数据类型存在与C数据内存空间中。

需要注意的⼀点是,⼀般情况下C数据内存空间是实时开辟的,⽤完就及时⾃动销毁的,当然也有特例,那就是numpy定义的array类型变量等, numpy定义的数据类型其实就是⼀种经过包装的C数据类型,当然numpy定义的array等类型变量存在于C数据内存空间中,⽽numpy下的array是可以持续存在的,不会⾃动销毁。

ctypes 提供了⼀些基本数据类型⽤来映射 C 语⾔和 Python 的类型,可以这样说 ctypes 是提供的⼀种数据类型映射关系或是转换关系。

给出ctypes的⼀些⽤法代码:
from ctypes import *
from platform import *
cdll_names = {
'Darwin' : 'libc.dylib',
'Linux' : 'libc.so.6',
'Windows': 'msvcrt.dll'
}
clib = cdll.LoadLibrary(cdll_names[system()])
a = b'a'
b = b'b'
s1 = c_char_p(a)
s2 = c_char_p(b)
print(id(a), id(b))
print('-'*80)
print( s1.value, type(s1), id(s1), id(s1.value), type(s1.value) )
print( s2.value, type(s2), id(s2), id(s2.value), type(s2.value) )
print('*'*80)
clib.strcat.argtype = (c_char_p, c_char_p)
clib.strcat.restype = c_char_p
s3 = clib.strcat(s1,s2)
print( s1.value, type(s1), id(s1), id(s1.value), type(s1.value) ) #ab
print( s2.value, type(s2), id(s2), id(s2.value), type(s2.value) ) #b
#print(s3, type(s3), id(s3) )
#print(a, id(a))
#print(b, id(b))
print("^"*80)
s1.value = b'c'
print( s1.value, type(s1), id(s1), id(s1.value), type(s1.value) ) #ab
print( s2.value, type(s2), id(s2), id(s2.value), type(s2.value) ) #b
结果:
(140474265252848, 140474265252896)
--------------------------------------------------------------------------------
('a', <class 'ctypes.c_char_p'>, 140474264436032, 140474265252848, <type 'str'>)
('b', <class 'ctypes.c_char_p'>, 140474263869200, 140474265252896, <type 'str'>)
********************************************************************************
('ab', <class 'ctypes.c_char_p'>, 140474264436032, 140474263886368, <type 'str'>)
('b', <class 'ctypes.c_char_p'>, 140474263869200, 140474265252896, <type 'str'>)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
('c', <class 'ctypes.c_char_p'>, 140474264436032, 140474265292896, <type 'str'>)
('b', <class 'ctypes.c_char_p'>, 140474263869200, 140474265252896, <type 'str'>)
⽤ctypes定义结构体转换规则:(转换为C数据内存空间时为⼀种链表结构)
from ctypes import *
class Test(Structure):
pass
Test._fields_ = [('x', c_int),
('y', c_char),
('next', POINTER(Test))]
test = Test(11, 97, None)
test2 = Test(12, 98, pointer(test))
test3 = Test(13, 99, pointer(test2))
print(test)
print(type(test))
print(test.x, test.y, test.next)
print(type(test.x), type(test.y), type(test.next))
test2=test3.next.contents
print(test2)
print(type(test2))
print(test2.x, test2.y, test2.next)
print(type(test2.x), type(test2.y), type(test2.next))
print(test3)
print(type(test3))
print(test3.x, test3.y, test3.next)
print(type(test3.x), type(test3.y), type(test3.next))
Test 类相当于把多个python数据类型与C数据类型映射关系打包在⼀起,是Structure类的继承类。

⽽Test类的对象则是关联好了对应的python数据类型对象,转换时调⽤Test类的具体对象(如,test1,test2,test3)便会根据打包的python数据类型在C数据内存空间⽣成对应映射C类型数据。

需要注意的是不论使⽤ctypes定义数据类型还是结构体,其实都是不换为对应C数据类型开辟内存空间的,ctypes只是提供两者之间的对应关系:
如:
a = 100
b=ctypes.c_int(a)
其中,b是表⽰将根据Python数据类型int的a转换成C内存空间int的⼀种关系,当C函数调⽤b时则⾃动根据b所定义的规则在C数据内存空间中开辟C语⾔int类型为100的数据。

给出Python官⽅给出的 ctypes 使⽤说明:
=================================================================
举例说明Python变量与Python数据内存空间的关系:
Python 变量a:
a变量------------>对应内存地址空间(135********)-------------------------->该空间存储的Python数据为Python类型的999。

上⾯的关系是由定义 a=999 ⽣成的,其中id(a)=135******** 。

======================================================================
为了更清楚的证明ctypes只是提供映射关系,可以看下⾯代码:
import ctypes
import random
d=ctypes.c_double*100000000
data=[]
for i in range(100000000):
data.append(random.random())
#上半部分
#################################
#下半部分
c_data=d(*data)
在ide中执⾏上⾯代码(分步执⾏):
执⾏完上部分代码,查看内存使⽤情况:
执⾏完下部分代码,再次查看内存使⽤情况:
可以看出 ctypes 并没有提供数据类型的转换,⽽是提供了数据转换时对应的映射关系。

如果cytpes 定义的数据类型会直接⽣成对应的C类型数据在C类型的内存空间中那么执⾏下部分代码后的内存占⽤应该是 24.2% 以上,⽽实际只是从 12.1% 提⾼到了14.5%,多占⽤2.4%的内存,⽽这2.4%的内存不可能是转换后的C类型数据只能是其对应的映射关系。

===============================================================================
参考⽂章:
ctypes 中的指针:
==================================================================
本⽂的前部分说Python程序中C语⾔类型的数据空间并不会持续存在,会随着⽤完⾃动销毁,其实这个说法并不准确,如果我们将C数据内存空间下的数据给以⼀定⽅式保存(某种数据结构的形式保存在堆或栈中),也是可以进⾏持续保存的,当然这种保存是只在C类型内存空
间中,如果要转回Python类型空间还是需要再转换的。

给出⼀个 C语⾔代码:
//test.c
#include <stdio.h>
#include <stdlib.h>
// Point 结构体
struct Point
{
int x;
char y;
struct Point *p;
};
static struct Point* head = NULL;
static struct Point* current = NULL;
static struct Point* walk = NULL;
void new_point(struct Point *p)
{
if (p)
{
if(current==NULL) //是第⼀个element
{
head=p;
current=p;
printf("first add \n");
}
else //不是第⼀个element
{
current->p=p;
current=p;
printf("not first add \n");
}
//
}
}
void print_point()
{
walk=head;
if(walk==NULL)
{
printf("error, it is a empty list \n");
}
else
{
while(walk!=current)
{
printf("x: %d, y: %c \n", walk->x, walk->y);
walk=walk->p;
}
if(walk!=NULL)
{
printf("x: %d, y: %c \n", walk->x, walk->y);
}
}
}
void main()
{
struct Point a;
a.x=97;
a.y='a';
a.p=NULL;
struct Point b;
b.x=98;
b.y='b';
b.p=NULL;
struct Point c;
c.x=99;
c.y='c';
c.p=NULL;
struct Point d;
d.x=100;
d.y='d';
d.p=NULL;
struct Point e;
e.x=101;
e.y='e';
e.p=NULL;
new_point(&a);
new_point(&b);
new_point(&c);
new_point(&d);
new_point(&e);
print_point();
}
该⽂件命名为 test.c 。

在Ubuntu18.04系统中编译并执⾏:
gcc test.c
编译成动态链接库:
gcc -fPIC -shared test.c -o test.so
有了动态链接库,我们就可以使⽤ctypes模块将Python数据转为C类型数据并调⽤C语⾔下链接库的函数,给出代码:import ctypes
from ctypes import c_int, c_char, Structure, POINTER, pointer, cdll
class Point(Structure):
pass
Point._fields_ = [('x', c_int),
('y', c_char),
('next', POINTER(Point))]
a=Point(97, b'a', None)
b=Point(98, b'b', None)
c=Point(99, b'c', None)
d=Point(100, b'd', None)
e=Point(101, b'e', None)
a.next=pointer(b)
b.next=pointer(c)
c.next=pointer(d)
d.next=pointer(e)
clib = cdll.LoadLibrary('./test.so')
clib.new_point.argtype = POINTER(Point)
clib.new_point.restype = None
clib.print_point.argtype = None
clib.print_point.restype = None
clib.new_point(pointer(a))
clib.new_point(pointer(b))
clib.new_point(pointer(c))
clib.new_point(pointer(d))
clib.new_point(pointer(e))
print("-"*50)
clib.print_point()
print("-"*50)
clib.print_point()
该Python代码命名为 test.py ⽂件。

运⾏结果:
可以看到我们⽤ctypes定义好映射规则,即:Point 类的对象: a, b, c, d, e
a, b, c, d, e 对象关联好了Python命名空间下的Python数据类型,当调⽤c语⾔库函数时将按照a,b,c,d,e对象所定义的映射在C语⾔内存空间下⽣成对应的C语⾔数据类型。

两次调⽤C库中的clib.print_point()函数均可以打印C内存空间下的栈中的数据,这充分说明本⽂前述内容的不充分的地⽅。

C语⾔内存空间下的数据只要我们加载的动态链接库的接⼝变量,这⾥是 clib ,还存在,就是可以⼀直调⽤的。

如果我们申请完C语⾔内存空间后如果删除clib会不会⾃动释放内存呢
代码:
import ctypes
from ctypes import c_int, c_char, Structure, POINTER, pointer, cdll, byref
class Point(Structure):
pass
Point._fields_ = [('x', c_int),
('y', c_char),
('next', POINTER(Point))]
clib = cdll.LoadLibrary('./test.so')
clib.new_point.argtype = POINTER(Point)
clib.new_point.restype = None
clib.print_point.argtype = None
clib.print_point.restype = None
l = []
for i in range(10000*10000):
l.append( Point(i, b'a', None) )
clib.new_point(byref(l[-1]))
其中,test.so 链接库为本⽂前⾯所给。

在IDE执⾏:
内存占⽤:
删除 clib 后查看内存情况:
可以发现前⾯说的⼜有不对的地⽅,如果删除clib变量了,但是C语⾔内存空间还是没有释放,看来最终的答案是C语⾔内存空间如果申请了就需要设置相应的C函数进⾏释放,如果没有进⾏C函数释放那么在Python程序的⽣命周期内C语⾔内存空间所申请的空间都是会⼀直存在的。

于是再次改test.c的代码增加free_point函数:
void new_point(struct Point *p)
{
if (p)
{
if(current==NULL) //是第⼀个element
{
head=p;
current=p;
printf("first add \n");
}
else //不是第⼀个element
{
current->p=p;
current=p;
printf("not first add \n");
}
//
}
}
完整的test.c 代码:
//test.c
#include <stdio.h>
#include <stdlib.h>
// Point 结构体
struct Point
{
int x;
char y;
struct Point *p;
};
static struct Point* head = NULL;
static struct Point* current = NULL;
static struct Point* walk = NULL;
void new_point(struct Point *p)
{
if (p)
{
if(current==NULL) //是第⼀个element
{
head=p;
current=p;
printf("first add \n");
}
else //不是第⼀个element
{
current->p=p;
current=p;
printf("not first add \n");
}
//
}
}
void print_point()
{
walk=head;
if(walk==NULL)
{
printf("error, it is a empty list \n");
}
else
{
while(walk!=current)
{
printf("x: %d, y: %c \n", walk->x, walk->y); walk=walk->p;
}
if(walk!=NULL)
{
printf("x: %d, y: %c \n", walk->x, walk->y); }
}
}
void free_point()
{
while(head!=NULL)
{
walk=head;
head=head->p;
printf("begin delete one element \n");
printf("x: %d, y: %c \n", walk->x, walk->y);
free(walk);
printf("success delete one element \n");
//printf(" %x \n", walk);
}
}
void main()
{
struct Point a;
a.x=97;
a.y='a';
a.p=NULL;
struct Point b;
b.x=98;
b.y='b';
b.p=NULL;
struct Point c;
c.x=99;
c.y='c';
c.p=NULL;
struct Point d;
d.x=100;
d.y='d';
d.p=NULL;
struct Point e;
e.x=101;
e.y='e';
e.p=NULL;
new_point(&a);
new_point(&b);
new_point(&c);
new_point(&d);
new_point(&e);
print_point();
}
View Code
完整的test.py代码:
import ctypes
from ctypes import c_int, c_char, Structure, POINTER, pointer, cdll, byref class Point(Structure):
pass
Point._fields_ = [('x', c_int),
('y', c_char),
('next', POINTER(Point))]
clib = cdll.LoadLibrary('./test.so')
clib.new_point.argtype = POINTER(Point)
clib.new_point.restype = None
clib.print_point.argtype = None
clib.print_point.restype = None
clib.free_point.argtype = None
clib.free_point.restype = None
l = []
for i in range(10000):
l.append( Point(i, b'a', None) )
clib.new_point(byref(l[-1]))
print("-"*50)
#clib.print_point()
clib.free_point()
View Code
这个释放内存的函数逻辑⼗分的清晰,但是运⾏起来却报错。

错误的提⽰也很明⽩,那就是不能⽤free来释放这块内存,查了好久的C语⾔语法发现这么写没有语法问题,虽然C语⾔已经是10多年前学的东西了,不过这么简单的逻辑不应该出错,这也是⼗分的不解。

最后在⽹上看到有⼈总结了这么⼀句C语⾔释放堆内存的解释,⼗分受⽤,那就是——“谁申请,谁释放”
在前⾯的操作中我们删出了clib变量,那么就是不能再利⽤C语⾔动态链接库⽂件中定义的函数来操作数据了,但是此时并不会释放C内存空间中的内存,那我们如果把和C内存空间相关联的ctypes变量删除,那就是说我们利⽤ctypes变量映射Python变量的⽅式使⽤隐⽅式⽣成C 语⾔内存空间下对应的变量(调⽤动态链接库中的函数⾃动映射的在C内存空间下⽣成的数据),那么我们删除掉这个映射关系,Python中的ctypes会不会本⾝就存在垃圾回收函数,在Python的垃圾回收机制下⾃动的回收在C内存空间下⽣成的堆空间呢
于是操作:
最终发现,设置ctypes下的数据类型虽然只是定义了⼀种映射关系,并不能在C语⾔内存空间下⽣成对应的变量,最后还是需要调⽤C内存空间下的函数才能⽣成对应的C类型数据变量,但是由于C内存空间与Python内存空间是隔离的,我们不能直接操作C内存空间下的数据,⽽C内存下的数据本⾝⼜遵守“谁⽣成谁销毁”的原则,这⼜导致我们⽆法利⽤C语⾔下的free函数来释放对应的变量空间(这些变量空间是ctypes下定义的数据类型在调⽤C动态库中函数⾃动由Python的ctypes⽣成的),因此,我们只有利⽤Python的语⾔机制和ctypes的语⾔机制来对C内存空间下的变量进⾏释放了,于是我们删除掉对应的ctypes数据变量也就是删除了Python变量与C变量的关联,这样⾃然就可以触发Python语⾔下的垃圾回收机制来释放内存。

最后的总结还是那句,C语⾔下的内存申请就是谁申请谁负责释放。

================================================================。

相关文档
最新文档