python 中 ctypes 的使用尝试
最近在看Python的性能優(yōu)化方面的文章,突然想起ctypes這個模塊,對于這個模塊一直不是很理解,不過再次看完相關資料有了些新的觀點。
ctypes 這個模塊個人觀點就是提供一個Python類型與C類型數(shù)據(jù)轉(zhuǎn)換接口或者說是規(guī)則的一個模塊。ctypes定義的數(shù)據(jù)類型其實并不是一種數(shù)據(jù)類型,而更應該說是一種轉(zhuǎn)換規(guī)則。ctypes定義的數(shù)據(jù)類型都是需要和Python數(shù)據(jù)類型進行關聯(lián)的,然后傳給C函數(shù)進行調(diào)用,在C函數(shù)調(diào)用的時候是根據(jù)ctypes的數(shù)據(jù)類型進行轉(zhuǎn)換的,把關聯(lián)的Python數(shù)據(jù)類型轉(zhuǎn)換為C數(shù)據(jù)類型傳給C函數(shù)。如果是ctypes定義的指針或者地址,其實是將Python變量對應的內(nèi)存空間地址中的數(shù)據(jù)與ctypes數(shù)據(jù)類型進行關聯(lián),如果C函數(shù)內(nèi)部對傳過來的指針地址對應的變量進行修改,最后是ctypes將修改好的C數(shù)據(jù)類型轉(zhuǎn)為Python類型數(shù)據(jù)并將其存入之前Python變量對應的內(nèi)存空間中。
在調(diào)用ctypes時,程序的內(nèi)存空間其實可以分為Python數(shù)據(jù)內(nèi)存空間與C數(shù)據(jù)類型空間。ctypes定義的數(shù)據(jù)類型就是提供了一個Python數(shù)據(jù)類型與C數(shù)據(jù)類型轉(zhuǎn)換的對應關系。ctypes定義的數(shù)據(jù)類型都是需要和Python數(shù)據(jù)類型關聯(lián)的,在調(diào)用C函數(shù)的時候在實時的轉(zhuǎn)為C數(shù)據(jù)類型。其中,Python數(shù)據(jù)類型存在與Python數(shù)據(jù)內(nèi)存空間中,C數(shù)據(jù)類型存在與C數(shù)據(jù)內(nèi)存空間中。
需要注意的一點是,一般情況下C數(shù)據(jù)內(nèi)存空間是實時開辟的,用完就及時自動銷毀的,當然也有特例,那就是numpy定義的array類型變量等, numpy定義的數(shù)據(jù)類型其實就是一種經(jīng)過包裝的C數(shù)據(jù)類型,當然numpy定義的array等類型變量存在于C數(shù)據(jù)內(nèi)存空間中,而numpy下的array是可以持續(xù)存在的,不會自動銷毀。
ctypes 提供了一些基本數(shù)據(jù)類型用來映射 C 語言和 Python 的類型, 可以這樣說 ctypes 是提供的一種數(shù)據(jù)類型映射關系或是轉(zhuǎn)換關系。
給出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定義結構體轉(zhuǎn)換規(guī)則:(轉(zhuǎn)換為C數(shù)據(jù)內(nèi)存空間時為一種鏈表結構)
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數(shù)據(jù)類型與C數(shù)據(jù)類型映射關系打包在一起,是Structure類的繼承類。而Test類的對象則是關聯(lián)好了對應的python數(shù)據(jù)類型對象,轉(zhuǎn)換時調(diào)用Test類的具體對象(如,test1,test2,test3)便會根據(jù)打包的python數(shù)據(jù)類型在C數(shù)據(jù)內(nèi)存空間生成對應映射C類型數(shù)據(jù)。
需要注意的是不論使用ctypes定義數(shù)據(jù)類型還是結構體,其實都是不換為對應C數(shù)據(jù)類型開辟內(nèi)存空間的,ctypes只是提供兩者之間的對應關系:
如:
a = 100
b=ctypes.c_int(a)
其中,b是表示將根據(jù)Python數(shù)據(jù)類型int的a轉(zhuǎn)換成C內(nèi)存空間int的一種關系,當C函數(shù)調(diào)用b時則自動根據(jù)b所定義的規(guī)則在C數(shù)據(jù)內(nèi)存空間中開辟C語言int類型為100的數(shù)據(jù)。
給出Python官方給出的 ctypes 使用說明:
https://docs.python.org/zh-cn/3/library/ctypes.html
=================================================================
舉例說明Python變量與Python數(shù)據(jù)內(nèi)存空間的關系:
Python 變量a:
a變量------------>對應內(nèi)存地址空間(13521458792)-------------------------->該空間存儲的Python數(shù)據(jù)為Python類型的999。
上面的關系是由定義 a=999 生成的, 其中id(a)=13521458792 。
======================================================================
為了更清楚的證明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中執(zhí)行上面代碼(分步執(zhí)行):
執(zhí)行完上部分代碼,查看內(nèi)存使用情況:
執(zhí)行完下部分代碼,再次查看內(nèi)存使用情況:
可以看出 ctypes 并沒有提供數(shù)據(jù)類型的轉(zhuǎn)換,而是提供了數(shù)據(jù)轉(zhuǎn)換時對應的映射關系。
如果cytpes 定義的數(shù)據(jù)類型會直接生成對應的C類型數(shù)據(jù)在C類型的內(nèi)存空間中那么執(zhí)行下部分代碼后的內(nèi)存占用應該是 24.2% 以上,而實際只是從 12.1% 提高到了14.5%,多占用2.4%的內(nèi)存,而這2.4%的內(nèi)存不可能是轉(zhuǎn)換后的C類型數(shù)據(jù)只能是其對應的映射關系。
===============================================================================
參考文章:
https://www.cnblogs.com/night-ride-depart/p/4907613.html
ctypes 中的指針:
==================================================================
本文的前部分說Python程序中C語言類型的數(shù)據(jù)空間并不會持續(xù)存在,會隨著用完自動銷毀,其實這個說法并不準確,如果我們將C數(shù)據(jù)內(nèi)存空間下的數(shù)據(jù)給以一定方式保存(某種數(shù)據(jù)結構的形式保存在堆或棧中),也是可以進行持續(xù)保存的,當然這種保存是只在C類型內(nèi)存空間中,如果要轉(zhuǎn)回Python類型空間還是需要再轉(zhuǎn)換的。
給出一個 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 !!!
");
}
else //不是第一個element
{
current->p=p;
current=p;
printf("not first add !!!
");
}
//
}
}
void print_point()
{
walk=head;
if(walk==NULL)
{
printf("error, it is a empty list!!!
");
}
else
{
while(walk!=current)
{
printf("x: %d, y: %c
", walk->x, walk->y);
walk=walk->p;
}
if(walk!=NULL)
{
printf("x: %d, y: %c
", 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系統(tǒng)中編譯并執(zhí)行:
gcc test.c
編譯成動態(tài)鏈接庫:
gcc -fPIC -shared test.c -o test.so
有了動態(tài)鏈接庫,我們就可以使用ctypes模塊將Python數(shù)據(jù)轉(zhuǎn)為C類型數(shù)據(jù)并調(diào)用C語言下鏈接庫的函數(shù),給出代碼:
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定義好映射規(guī)則,即:Point 類的對象 : a, b, c, d, e
a, b, c, d, e 對象關聯(lián)好了Python命名空間下的Python數(shù)據(jù)類型,當調(diào)用c語言庫函數(shù)時將按照a,b,c,d,e對象所定義的映射在C語言內(nèi)存空間下生成對應的C語言數(shù)據(jù)類型。
兩次調(diào)用C庫中的clib.print_point()函數(shù)均可以打印C內(nèi)存空間下的棧中的數(shù)據(jù),這充分說明本文前述內(nèi)容的不充分的地方。C語言內(nèi)存空間下的數(shù)據(jù)只要我們加載的動態(tài)鏈接庫的接口變量,這里是 clib ,還存在,就是可以一直調(diào)用的。
如果我們申請完C語言內(nèi)存空間后如果刪除clib會不會自動釋放內(nèi)存呢???
代碼:
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執(zhí)行:
內(nèi)存占用:
刪除 clib 后查看內(nèi)存情況:
可以發(fā)現(xiàn)前面說的又有不對的地方,如果刪除clib變量了,但是C語言內(nèi)存空間還是沒有釋放,看來最終的答案是C語言內(nèi)存空間如果申請了就需要設置相應的C函數(shù)進行釋放,如果沒有進行C函數(shù)釋放那么在Python程序的生命周期內(nèi)C語言內(nèi)存空間所申請的空間都是會一直存在的。
于是再次改test.c的代碼增加free_point函數(shù):
void new_point(struct Point *p)
{
if (p)
{
if(current==NULL) //是第一個element
{
head=p;
current=p;
printf("first add !!!
");
}
else //不是第一個element
{
current->p=p;
current=p;
printf("not first add !!!
");
}
//
}
}
完整的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 !!!
");
}
else //不是第一個element
{
current->p=p;
current=p;
printf("not first add !!!
");
}
//
}
}
void print_point()
{
walk=head;
if(walk==NULL)
{
printf("error, it is a empty list!!!
");
}
else
{
while(walk!=current)
{
printf("x: %d, y: %c
", walk->x, walk->y);
walk=walk->p;
}
if(walk!=NULL)
{
printf("x: %d, y: %c
", walk->x, walk->y);
}
}
}
void free_point()
{
while(head!=NULL)
{
walk=head;
head=head->p;
printf("begin delete one element !!!
");
printf("x: %d, y: %c
", walk->x, walk->y);
free(walk);
printf("success delete one element !!!
");
//printf(" %x
", 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
這個釋放內(nèi)存的函數(shù)邏輯十分的清晰,但是運行起來卻報錯。
錯誤的提示也很明白,那就是不能用free來釋放這塊內(nèi)存,查了好久的C語言語法發(fā)現(xiàn)這么寫沒有語法問題,雖然C語言已經(jīng)是10多年前學的東西了,不過這么簡單的邏輯不應該出錯,這也是十分的不解。
最后在網(wǎng)上看到有人總結了這么一句C語言釋放堆內(nèi)存的解釋,十分受用,那就是——“誰申請,誰釋放”
在前面的操作中我們刪出了clib變量,那么就是不能再利用C語言動態(tài)鏈接庫文件中定義的函數(shù)來操作數(shù)據(jù)了,但是此時并不會釋放C內(nèi)存空間中的內(nèi)存,那我們?nèi)绻押虲內(nèi)存空間相關聯(lián)的ctypes變量刪除,那就是說我們利用ctypes變量映射Python變量的方式使用隱方式生成C語言內(nèi)存空間下對應的變量(調(diào)用動態(tài)鏈接庫中的函數(shù)自動映射的在C內(nèi)存空間下生成的數(shù)據(jù)),那么我們刪除掉這個映射關系,Python中的ctypes會不會本身就存在垃圾回收函數(shù),在Python的垃圾回收機制下自動的回收在C內(nèi)存空間下生成的堆空間呢???
于是操作:
最終發(fā)現(xiàn),設置ctypes下的數(shù)據(jù)類型雖然只是定義了一種映射關系,并不能在C語言內(nèi)存空間下生成對應的變量,最后還是需要調(diào)用C內(nèi)存空間下的函數(shù)才能生成對應的C類型數(shù)據(jù)變量,但是由于C內(nèi)存空間與Python內(nèi)存空間是隔離的,我們不能直接操作C內(nèi)存空間下的數(shù)據(jù),而C內(nèi)存下的數(shù)據(jù)本身又遵守“誰生成誰銷毀”的原則,這又導致我們無法利用C語言下的free函數(shù)來釋放對應的變量空間(這些變量空間是ctypes下定義的數(shù)據(jù)類型在調(diào)用C動態(tài)庫中函數(shù)自動由Python的ctypes生成的),因此,我們只有利用Python的語言機制和ctypes的語言機制來對C內(nèi)存空間下的變量進行釋放了,于是我們刪除掉對應的ctypes數(shù)據(jù)變量也就是刪除了Python變量與C變量的關聯(lián),這樣自然就可以觸發(fā)Python語言下的垃圾回收機制來釋放內(nèi)存。最后的總結還是那句,C語言下的內(nèi)存申請就是誰申請誰負責釋放。
================================================================
本博客是博主個人學習時的一些記錄,不保證是為原創(chuàng),個別文章加入了轉(zhuǎn)載的源地址還有個別文章是匯總網(wǎng)上多份資料所成,在這之中也必有疏漏未加標注者,如有侵權請與博主聯(lián)系。
總結
以上是生活随笔為你收集整理的python 中 ctypes 的使用尝试的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 每次电脑打开总是弹出很多广告窗口为什么电
- 下一篇: 7-4 找到共同的选修课-hebust