最近在看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 使用说明:

https://docs.python.org/zh-cn/3/library/ctypes.html

=================================================================

举例说明Python变量与Python数据内存空间的关系:

Python 变量a:

a变量------------>对应内存地址空间(13521458792)-------------------------->该空间存储的Python数据为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中执行上面代码(分步执行):

执行完上部分代码,查看内存使用情况:

执行完下部分代码,再次查看内存使用情况:

可以看出 ctypes 并没有提供数据类型的转换,而是提供了数据转换时对应的映射关系。

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

===============================================================================

参考文章:

https://www.cnblogs.com/night-ride-depart/p/4907613.html

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(); }

完整的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()

这个释放内存的函数逻辑十分的清晰,但是运行起来却报错。

错误的提示也很明白,那就是不能用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语言下的内存申请就是谁申请谁负责释放。

================================================================

python 中 ctypes 的使用尝试的更多相关文章

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

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

  2. python中使用ctypes调用MinGW生成的动态链接库(dll)

    关于gcc编译dll的我就不说了,网上举例一大堆,下面以g++为例. 假设有一个test.cpp文件如下: extern "C" { __declspec(dllexport) d ...

  3. python中使用 C 类型的数组以及ctypes 的用法

    Python 在 ctypes 中为我们提供了类似C语言的数据类型, 它的用途(我理解的)可能是: (1) 与 其他语言(如 C.Delphi 等)写的动态连接库DLL 进行交换数据,因为 pytho ...

  4. 孤荷凌寒自学python第五十二天初次尝试使用python读取Firebase数据库中记录

    孤荷凌寒自学python第五十二天初次尝试使用python读取Firebase数据库中记录 (完整学习过程屏幕记录视频地址在文末) 今天继续研究Firebase数据库,利用google免费提供的这个数 ...

  5. 精通 Oracle+Python,第 9 部分:Jython 和 IronPython — 在 Python 中使用 JDBC 和 ODP.NET

    成功的编程语言总是会成为顶级开发平台.对于 Python 和世界上的两个顶级编程环境 Java 和 Microsoft .NET 来说的确如此. 虽然人们因为 Python 能够快速组装不同的软件组件 ...

  6. python中的collections

    python中有大量的内置模块,很多是属于特定开发的功能性模块,但collections是属于对基础数据的类型的补充模块,因此,在日常代码中使用频率更高一些,值得做个笔记,本文只做主要关键字介绍,详细 ...

  7. Python爬虫学习(4): python中re模块中的向后引用以及零宽断言

    使用小括号的时候,还有很多特定用途的语法.下面列出了最常用的一些: 表4.常用分组语法 分类 代码/语法 说明 捕获 (exp) 匹配exp,并捕获文本到自动命名的组里 (?<name>e ...

  8. 如何在Python中实现这五类强大的概率分布

    R编程语言已经成为统计分析中的事实标准.但在这篇文章中,我将告诉你在Python中实现统计学概念会是如此容易.我要使用Python实现一些离散和连续的概率分布.虽然我不会讨论这些分布的数学细节,但我会 ...

  9. Python::re 模块 -- 在Python中使用正则表达式

    前言 这篇文章,并不是对正则表达式的介绍,而是对Python中如何结合re模块使用正则表达式的介绍.文章的侧重点是如何使用re模块在Python语言中使用正则表达式,对于Python表达式的语法和详细 ...

  10. 线程安全及Python中的GIL

    线程安全及Python中的GIL 本博客所有内容采用 Creative Commons Licenses 许可使用. 引用本内容时,请保留 朱涛, 出处 ,并且 非商业 . 点击 订阅 来订阅本博客. ...

随机推荐

  1. list对象转数组

    list对象转数组 package com.example.core.mydemo.json5; import org.apache.commons.collections4.CollectionUt ...

  2. const 和 volatile 指针

    关键字 const 和 volatile 规定了指针的处理方式: const 规定指针在初始化后是受保护的,不能够再修改. volatile 规定了变量的值能够被用户应用程序外部的操作所修改. 因此, ...

  3. 实现 Emlog 最新评论列表不显示博主的评论回复

    Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` 实现 Emlog 最新评论列表不显示博主的评论回复 日期: ...

  4. vue.config.js配置优化

    vue.config.js完整代码如下: 'use strict'; // Template version: 1.3.1 // see http://vuejs-templates.github.i ...

  5. 李沐多模态串讲视频总结 ALBEF VLMo BLIP CoCa BEITv3 模型简要介绍

    开场 多模态串讲的上篇是比较传统的多模态任务 多模态最后的模态交互很重要 传统的缺点是都用了预训练的目标检测器,训练和部署都很困难. ViLT 把预训练的目标检测器换成了一层的 Patch Embed ...

  6. ffmpeg 学习:主要结构体之间关系

    背景 学习例程源码的时候,搞不清楚各结构体之间是什么含义. 解析 FFmpeg 有多个重要的结构体,解协议,解分装,解码,解封装. 解协议: http,rstp,rtmp,mms. AVIOConte ...

  7. 开源日志组件Sejil--附带日志管理界面

    1.开源日志组件源码:  https://github.com/alaatm/Sejil 2.下载下来发现里面对于不同的.net core 版本的配置提供了对应的示例 .Net Core 3.1 Pr ...

  8. html2canvas 页面截屏

    $(document).ready(function () { $(".example1").on("click", function (event) { va ...

  9. Nuxt框架中内置组件详解及使用指南(二)

    title: Nuxt框架中内置组件详解及使用指南(二) date: 2024/7/7 updated: 2024/7/7 author: cmdragon excerpt: 摘要:"本文详 ...

  10. Java(screw)生成数据库表结构

    数据库支持 MySQL MariaDB TIDB Oracle SqlServer PostgreSQL Cache DB(2016) 文档生成支持 html word markdown 方式一:代码 ...