我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Python 扩展使用 C/C++ 给

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Python 扩展使用 C/C++ 给

640.gif?wxfrom=5&wx_lazy=1

本文来自作者 gashero 在 GitChat 上分享「Python 的 C 扩展开发惯例」,「阅读原文」查看交流实录

「文末高能」

编辑 | 嘉仔

目录

1   简介    1.1   Python扩展模块的用途和优点    1.2   设计扩展模块的流程 2   setup.py脚本 3   函数接口、参数传递、简单返回值    3.1   函数接口    3.2   参数传递    3.3   简单返回值 4   元组、列表、字典、缓冲区 5   异常处理、引用计数    5.1   抛出异常    5.2   引用计数 6   GIL与多线程

1. 简介

本文记录Python的扩展模块开发中的常用惯例,以便读者可以低成本的使用Python扩展模块来提高应用性能。

文本不是扩展模块的入门教程,而是面向对扩展模块有一定概念,但一直未能有效应用起来的读者。

1.1   Python扩展模块的用途和优点

Python扩展模块的常见几种用途及其优点:

提升计算性能

Python的扩展模块使用C/C++编写,其计算性能也是C/C++同级别的。跨语言通信接口上的性能损失小到忽略不计,所以能够提供非常好的性能支持。典型如Numpy包,用于科学计算,其底层调用了第三方的数学计算库,性能也是同级别的。

使用多核心计算能力

扩展模块通过对GIL的控制,可以使用上CPU的多核心计算能力,而不是受限于纯Python程序的单核心限制。结合多线程可以定制使用多少个核心。

系统组件隔离和模块化

通过把每个C/C++函数提供给Python接口,使得函数之间不共享任何状态,实现了良好的组建隔离,有助于开发和测试。同时由于参数全部通过Python传递,易于打印和中断,可调试性有了很大的提高。

使用第三方库

并非每种库都有Python支持,此时就需要自己编写扩展模块实现系统对接了。但现代流行的大型库,很多都有官方的Python扩展模块,使得应用质量有了较大提高,典型如OpenCV和PyCUDA。

1.2 设计扩展模块的流程

  • 编写setup.py:配置扩展模块元信息的脚本

  • 引入必要头文件:以Python.h为主的几个常用头文件

  • 设计导出函数表:导出给Python使用的函数表

  • 模块初始化函数:几个必要的初始化步骤

2. setup.py脚本

setup.py脚本是用于构建扩展模块的配置。简单的网上有很多,而如下给出一个项目中比较实用的例子:

import platform from distutils.core import setup,Extension from distutils import sysconfig cfg_vars=sysconfig.get_config_vars() if 'OPT' in cfg_vars:    cfg_vars['OPT']=cfg_vars['OPT'].replace('-Wstrict-prototypes','') mod_expy=Extension('libxx.expy',    sources=['libxx/expy.c',],    libraries=['jpeg',],    include_dirs=['/opt/local/include',],    library_dirs=['/opt/local/lib',],    define_macros=[('MAJOR_VERSION','1'),('MINOR_VERSION','0')],    ) #区分各种平台的编译 if platform.system()=='Darwin':    os.environ['ARCHFLAGS']='-arch x86_64'    extmodlist=[mod_expy,] elif platform.system()='Linux':    os.environ['ARCHFLAGS']='-arch i386 -arch x86_64'    extmodlist=[mod_expy,] else:    raise RuntimeError('Unknown system()=%s'%repr(platform.system())) setup(name='libxx',    version='1.0',    description='libxx-python',    author='gashero',    author_email='xxx@xxx.xxx',    packages=['libxx',],    ext_modules=extmodlist,    url='http://example.com/',    long_description='xxxxxx xxxxxx', )

相对于各种Hello World级别的setup.py。如上脚本增添的实用性功能:

  • 提供了更完善的编译参数,比如编译时特定的宏定义,编译器参数等

  • 引用了其他第三方库

  • 区分不同平台的编译,对Linux和Mac有所区分

  • 提供欧冠你了更加完善的元信息

通常大家自己编写的扩展模块规模都不大,如上setup.py脚本也足够使用了。如果涉及到更复杂的多个源文件的扩展模块,只需要继续增加Extension对象即可。

3. 函数接口、参数传递、简单返回值

3.1   函数接口

用于定义函数,并传递给Python调用。就是定义PyMethodDef类型的实例。一个例子如下:

static PyMethodDef expyMethods[]={    {"add",expy_add,METH_VARARGS,"Add 2 number"},    {NULL,NULL,0,NULL} };

此定义的4个字段中的第3个字段用以决定函数的调用方式,可选如下:

  • METH_VARARGS:多个形式参数,最常用的玩法

  • METH_NOARGS:无参数,常用于输出类函数

  • METH_VARARGS|METH_KEYWORDS :同时拥有匿名参数和关键字参数

最后一个字段则是函数的文档,用于 help() 函数时显示。

3.2   参数传递

使用匿名参数和包含关键字参数的函数定义是不同的,两者如下:

static PyObject* func(PyObject* self, PyObject* args); static PyObject* func(PyObject* self, PyObject* args, PyObject **kwargs);

如果没有对应的内容则会传递来NULL。对于模块函数,而不是对象方法,可以不关注self。

参数解析过程使用 PyArg_ParseTuple() 和 PyArg_ParseTupleAndKeywords() 。用一个字符串来描述参数列表,随后传入各个对象的引用地址。

鉴于参数和返回值的类型可能不是对双方都友好,所以一般建议不要传递的类型:

  • 体系结构相关的数字类型,如int、long,而是传递长度已知的数字类型

  • 结构体,因为结构体实际存储的位置可能是有生命周期的,同样适用于C++的对象

  • 地址指针,因为也是体系结构相关的,包括函数指针

实际上需要传递地址、结构体、对象时,一般建议在扩展模块里写个对象,虽然更困难一些,但是兼容性会好的多。

不要把任何内容留在C/C++程序里,如全局变量。

3.3   简单返回值

函数的返回值必须是Python对象。

简单的没有返回值的函数,可以直接在函数末尾使用一个宏来实现:

Py_RETURN_NONE;

这对于简单那的输出函数是没问题的。

更复杂一点的可以使用 Py_BuildValue() 也是用一个字符串描述格式后传入各个值,甚至可以构造复杂的数据结构。

4.  元组、列表、字典、缓冲区

元组在Python中的作用是不可修改的,这对于返回内容给Python是很友好的,而且性能也比列表要好。

列表的价值在于可以存储长度不固定的内容。比如C/C++很多的第三方库会用迭代的方式返回内容。在函数开始时并不知道能返回多少个对象,只能每次调用next来获取下一个对象。此时就适合创建个list,然后每次用append()方法来加入元素。例如:

PyObject *retlist=PyList_New(0);    //创建长度为0的队列 while(pos) {    PyList_Append(retlist, Py_BuildValue("{}"));    pos=pos->next; }

字典也可以用于返回一个单独的结构体内容。即将C/C++结构体中每个字段作为字典中的一个元素来设置,并返回。这种方式的可读性会很好。

使用dict存储信息的例子,key使用字符串:

PyObject *fmtinfo=PyDict_New(); PyDict_SetItemString(fmtinfo, "index", PyInt_FromLong(1));

但是不建议用于返回list中的dict,毕竟性能消耗较多。对于返回多个结构体,更适合的方式还是list中的tuple。

返回内容长度可预估的情况下使用 Py_BuildValue() 更合适。

对于不需要理解内容的字符串,可以使用buffer类型,会比str类型更节省内存和性能。buffer类型相对于str类型的区别是,buffer不会检查系统中内容相同的两个字符串。而str会确保系统里不会有重复的字符串。这个计算过程也是要耗时间的。

新建buffer,获取其指针后进行操作的例子:

Py_ssize_t buflen=1000,_buflen; void *bufptr; PyObject *buf=PyBuffer_New(buflen); if (PyObject_AsWriteBuffer(buf, &bufptr, &_buflen)<0) {    return NULL; }

5.  异常处理、引用计数

5.1   抛出异常

用来通知Python,在函数里出现了错误。几个常用的内置异常:

   PyExc_ZeroDivisionError :被0除    PyExc_IOError :IO错误    PyExc_TypeError :类型错误,如参数类型不对    PyExc_ValueError :值的范围错误    PyExc_RuntimeError :运行时错误    PyExc_OSError :各种与OS交互时的错误

可以根据自己的喜好来抛出异常。实际抛出异常的方法如:

if (ret<0) {    PyErr_SetString(PyExc_RuntimeError,"Runtime failed!");    return NULL; }

有时希望抛出的异常包含一些参数,比如错误码,来方便更好的调试。可以用如下方法:

if (ret<0) {    PyErr_Format(PyExc_OSError, "ERROR[%d]=%s",            errno, strerror(errno)); }

各种C/C++库里大量使用了错误码,所以如上方法的使用非常广泛。在Python里看到函数出错抛出的异常,并包含了错误码是比平时写纯C/C++程序动则crash要美好的多。而编写扩展模块的过程使得这个工作规范化了。

5.2   引用计数

扩展模块的引用计数是很魔幻的,也是广为诟病的一个问题。

实际使用中,每个Python API都会在文档里注明其对引用计数的操作。借用就是不改变引用计数,而引用则是会增加1的。

为了避免每次都恶心的去查找这些内容,可以利用一些友好的函数。比如 Py_BuildValue() 直接传入的就是C/C++的数据,其内负责生成复杂的数据结构,并管理好引用计数。

tuple、buffer的引用计数管理也比list、str的要简单,推荐使用。

6.   GIL与多线程

GIL是限制Python使用多核心的直接原因,根本原因是Python解释器内部有一些全局变量,典型如异常处理。而又有很多Python API和第三方模块在使用这些全局变量,使得GIL的改进一直得不到进展。

在扩展模块层面是可以释放GIL的,使得CPU控制权交还给Python,而当前C/C++代码也可以继续执行。但务必注意的是任何Python API的调用都必须在GIL控制下进行。所以在执行密集计算的任务前释放GIL,完成计算后,重新申请GIL,再进行返回值和异常处理。

在扩展模块的函数里释放和申请GIL的第一种写法:

static PyObject *fun(PyObject *self, PyObject *args) {    //....    PyThreadState *_save;    _save=PyEval_SaveThread();    block();    PyEval_RestoreThread(_save);    //... }

该方法还需要在模块初始化时调用 PyEval_InitThreads() 。

另一种方法则简便的多:

Py_BEGIN_ALLOW_THREADS; //可能阻塞的操作 Py_END_ALLOW_THREADS;

所以,一种简单的使用多核计算的方法是,把任务拆分成多个小份,每个小份都放在一个线程中运行。线程里调用扩展模块中的计算函数,计算函数在实际计算时释放GIL。

这种方式可以有效的利用Python来管理线程,而无需在C/C++里使用pthread之类的麻烦事,只需要最简单的做计算就好了。


福利

640?wx_fmt=png

「阅读原文」看交流实录,你想知道的都在这里

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Python 扩展使用 C/C++ 给

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Python 扩展使用 C/C++ 给

本文来自作者 gashero 在 GitChat 上分享「Python 的 C 扩展开发惯例」,「阅读原文」查看交流实录「文末高能」编辑 | 嘉仔目录1   简介    1.1   Python扩展模块的用途和优点    1.2   设计扩
2023-01-31

再探C/C++扩展Python

上篇博文是初用c/c++扩展Python,只是简单的举个例子,有兴趣的可以去上篇博文里看看那个例子的代码,代码如下:#includestatic PyObject *pr_isprime(PyObject *self,P
2023-01-31

Python的C扩展-应用与陷阱

Python的C扩展-应用与陷阱 1. 背景2. Python扩展的用武之地-库测试 (1)动态库的测试(2)静态库的测试3 python模块级扩展4 小结反馈建议 1. 背景Python作为一种流行的动态脚本语言,既有
2023-01-31

使用C++为node.js写扩展模块

前提: 安装好node.js、Python2.7与visual studio 2013。 过程: 首先安装GYP项目生成工具,npm install -g node-gyp 。 建立test目录,这是我们的工作目录,在此目录下再建一个src
2022-06-04

Node.js Addons翻译(C/C++扩展)

PS:请先升级Node 6.2.1,Node 升级命令 npm install -g n;n stable.NOde.js扩展是一个通过C/C++编写的动态链接库,并通过Node.js的函数require()函数加载,用起来就像使用一个普通
2022-06-04

如何使用 C++ STL 扩展 C++ 语言的功能?

c++++ stl 为 c++ 提供容器、算法和函数,增强其功能:容器:存储数据的对象,包括顺序容器和关联容器。算法:操作数据的函数,包括排序、搜索和其他算法。函数:其他有用的函数,如数学、字符操作和随机函数。如何使用 C++ STL 扩展
如何使用 C++ STL 扩展 C++ 语言的功能?
2024-05-24

Win7如何给C盘扩容?Win7给C盘扩容的方式

C盘做为C盘假如存储空间小时会危害电脑上的运转速率,这个时候我们可以根据扩大C盘来处理这个问题,那麼Win7要如何给C盘开展扩容呢?下边就和小编一起来看一下怎么实际操作吧。Win7给C盘扩容的方式1、在网页页面中寻找我的电脑图标。2、鼠标点
2023-07-24

如何使用Cython为Python编写更快的C扩展

本篇文章为大家展示了如何使用Cython为Python编写更快的C扩展,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。在我们这个包含了 7 个 PyPI 库的系列文章中学习解决常见的 Python 问
2023-06-16

C# 9使用foreach扩展的示例详解

在 C# 9 中,foreach 循环可以使用扩展方法。在本文中,我们将通过例子回顾 C# 9 中如何扩展 foreach 循环,感兴趣的小伙伴可以了解一下
2023-01-06

使用Python扩展lldb

Xcode集成了LLDB,进一步简化了程序调试流程。虽然LLDB很强大,但是它的命令很有限。所幸的是,lldb包含了对python的支持,使得lldb的拓展成为可能。本人在开发过程中很喜欢使用image lookup 命令,但是苦于每次只能
2023-01-31

Python C扩展的引用计数问题分析

这篇文章主要讲解了“Python C扩展的引用计数问题分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python C扩展的引用计数问题分析”吧!Python GC机制对于Python这种
2023-06-19

用 Go 编写的 Python 扩展会忽略 Ctrl+C

php小编苹果发现,使用Go编写的Python扩展在处理Ctrl+C信号时可能会出现问题。通常情况下,当我们在命令行中按下Ctrl+C时,会发送一个SIGINT信号给正在运行的程序,以请求其停止执行。然而,使用Go编写的Python扩展似乎
用 Go 编写的 Python 扩展会忽略 Ctrl+C
2024-02-13

C# 3.0中扩展方法怎么用

这篇文章主要介绍了C# 3.0中扩展方法怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。Extension Methods 使用扩展方法,使用的时候需要注意的地方1.C#
2023-06-17

怎么为Python写一个C++扩展模块

今天小编给大家分享一下怎么为Python写一个C++扩展模块的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。源代码和往常一样,
2023-07-06

C#中string常用扩展有哪些

这篇文章主要为大家展示了“C#中string常用扩展有哪些”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“C#中string常用扩展有哪些”这篇文章吧。string是c#里面最最常用的类,和它的使
2023-06-17

C#中泛型类和扩展方法如何使用

这篇文章介绍了C#中泛型类和扩展方法的用法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
2022-11-13

如何使用 C++ 函数对象扩展 STL 算法?

可以通过使用函数对象来扩展 stl 算法,函数对象是具有调用运算符 (operator()) 的类或结构。只需要将函数对象作为算法的参数传递即可,例如使用 std::sort 算法排序容器时,可以传递 std::greater 函数对象作为
如何使用 C++ 函数对象扩展 STL 算法?
2024-04-25

如何在PHP中使用FFI扩展调用C函数

今天就跟大家聊聊有关如何在PHP中使用FFI扩展调用C函数,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。什么是 FFIFFI , Foreign Function Interface
2023-06-15

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录