用 C 语言武装 Python ,让代码执行速度飞起来!

67 篇文章 0 订阅

众所周知,作为解释型语言的 Python 可不是什么超级快速的语言,但许多复杂的库函数(比如 NumPy 库)却能执行得相当快速。这主要是因为这些库的核心代码往往是用 C 或者 C++ 写好,并经过了编译,比解释执行的 Python 代码有更快的执行速度。

在这篇短文中,我们将详细聊一聊如何用 C 或者 C++ 写一个 Python 模组(或软件包),内容主要参考 Python 官方文档。作为范例,我也将用 C 写一个简单的 Python 模组,完成一个简单的数学计算: n!=n×(n-1)×(n-2)… 。为了实现上面的目标,我们需要两个文件:一个 Python 代码 setup.py,以及我们实际编写的 C 语言代码 cmath.c。

总的来说,我们将用 setup.py 把 C 语言写的代码 cmath.c 构建成一个 Python 库(这其中包括编译代码、查找 Python C 库、连接等操作)。

那么,让我们开始吧!

 

原理

为了让我们的程序/模组能在 Python 代码中被调用执行,模组需要和 Python 解释器 CPython 进行必要的通讯。因此,我们需要 Python.h 头文件里面的若干对象,并用它们构建出合适的结构体。

基本上,我们要做的是把实际的 C 语言方法包装起来,以便能够被 Python 解释器所调用,这样我们的 Python 代码才能够像使用普通的 Python 函数一样,调用这个方法。

编写算法并包装

首先,我们要在 cmath.c 里引入头文件:

#include Python.h

在 Python 头文件里,我们需要用来和 Python 解释器对接的对象(以及函数),都以 Py 开头。在这里,能代表所有 python 对象的 C 对象(基本上就是一个opaque——“不透明”对象)叫做 PyObject。

不过,在实际使用这些对象之前,我们先把求阶乘的算法写出来(注意,0的阶乘是1):

int fastfactorial(int n){

if(n<=1)

return 1;

else

return n * fastfactorial(n-1);

}

接着,我们给这个函数进行一下包装。这个包裹函数接收一个 PyObject 类型的指针(指向今后从 Python 代码传入的参数)作为参数,再返回一个 PyObject 类型的指针(指向上面函数的返回值)给外部。

为此,我们用以下代码来实现这个包裹函数:

static PyObject* factorial(PyObject* self, PyObject* args){

int n;

if (!PyArg_ParseTuple(args,"i",&n))

  return NULL;

int result = fastfactorial(n);

return Py_BuildValue("i",result);

}

这个函数始终需要一个指向模组对象本身的 self 指针,以及一个指向从 Python 代码传入参数的 args 指针(二者都是 PyObject 类型的对象)。我们用 PyArg_ParseTuple 方法来处理这些参数,并且声明我们需要的是整数类型(第二个参数 "i"),最后将处理结果赋值到变量 n 中。

接着自然是调用 fastfactorial(n) 来计算阶乘,并用 Python 头文件里的 Py_BuildValue 方法把返回值塞回 PyObject* 类型里。最后,我们的包裹函数将指向结果的指针对象返回给外部。

 

组装模组结构

现在,我们已经把实际的阶乘函数封装完毕,接下来需要构造一个 PyModuleDef 结构体的实例(这个对象也是由 Python.h 所定义的。这个结构体定义了模组的结构,以便 Python 解释器载入调用。而模组的另一个组成部分是定义它的所有方法,这由另一个结构体 PyMethodDef 实现——它其实就相当于一个数组,里面列出了模组中所有的方法和对应的说明。

在当前例子中,我们定义了如下的 PyMethodDef 对象:

static PyMethodDef mainMethods[] = {

{"factorial",factorial,METH_VARARGS,"Calculate the factorial of n"},

{NULL,NULL,0,NULL}

};

这个对象里目前共有 2 个元素——我们在最末尾加入了一个由 NULL 组成的结构体,做为结尾。第 0 个对象是我们定义的方法,它的结构是:先是方法名 factorial,其次是实际调用的函数对象,注意这里调用的是上一节定义的包裹函数;接下来指定了这个方法是从 METH_VARARGS 这个常量中获得它的参数;最后是一个说明字符串。

于是,我们已经定义了这个 Python 模组中的所有方法(本例中就一个),我们可以创建一个 PyModuleDef 的实例,作为代表整个 Python 模组的对象。

代码如下:

static PyModuleDef cmath = {

PyModuleDef_HEAD_INIT,

"cmath","Factorial Calculation",

-1,

mainMethods

};

在上面的代码中,我们首先定义了模组名 cmath 以及简短的文档字符串,然后再把所有的方法组成的数组 mainMethods 放进去。

最后一步,我们要添加一个函数,并让 python 代码导入这个模组的时候执行这个函数。

代码如下:

PyMODINIT_FUNC PyInit_cmath(void){

return PyModule_Create(&cmath);

}

函数的返回类型是 PyMODINIT_FUNC,这表明函数实际上返回的是一个 PyObject 类型的指针。这个指针指向由 PyModule_Create 生成的 Python 模组本身(这个模组对象本身也是一个 PyObject对象)。当一个模组被 Python 代码导入时,这个方法就会被调用,并返回一个指向整个模组对象,包含了所有方法的指针。

 

小编给大家推荐一个学习氛围超好的地方,C/C++交流企鹅裙:【870+963+251】适合在校大学生,小白,想转行,想通过这个找工作的加入。裙里有大量学习资料,有大神解答交流问题,每晚都有免费的直播课程

 

编译打包模组

现在我们的 C 代码文件已经准备好了,所有的方法都已经包装到位,Python 解释器导入、执行所需的结构体也已经定义完善。于是,我们可以开始构建最终的二进制文件了。在这个过程中,我们的 C 代码需要被编译、并和正确的库文件连接(本例中,我们用到的主要是 Python 头文件中定义的那些方法和对象)。为了简化构建过程,我们可以用到 distutils.core 模组里的 setup和 Extension 方法。

简单地说,这两个方法基本上能搞定整个构建过程。我们只要把 setup.py 和 cmath.c 放在同一个文件夹里,然后引入这两个方法即可。

这是完整的 setup.py 文件内容:

from distutils.core import setup, Extension

factorial_module = Extension('cmath',sources = ['cmath.c'])

setup(name = 'MathExtension',

      version='1.0',

      description = 'This is a math package',

      ext_modules = [factorial_module]

    )

在上面的代码中,我们首先声明了 factorial_module 变量,作为一个 C 语言扩展对象,源代码 source 来自我们的 C 代码文件。这一行基本就是告诉 setup 方法要编译的源文件是哪个。

接下来,我们调用 setup() 函数,这个函数接收的参数就是将来要构建的包名( MathExtension)、版本号(1.0)、简短的描述文档,以及要包括在内的 C 语言扩展/模组对象( factorial_module )。这样,setup.py 就写好了,是不是很简单?

最后,我们运行一下 setup.py。运行时可以选择两种不同的模式。如果是 build,程序就只编译这个模块(一个 .so 格式的库文件)并把编译结果放在当前文件夹里的 build 子文件夹内;如果是 install,则会将编译结果放在 python 的环境变量 PATH 指向的文件夹里,以便其他程序调用。

今天的例子里,我们选择 build 选项。在终端/命令提示符里输入以下命令:

python setup.py build

如果一切正常,你就会在当前文件夹里看到一个 build 文件夹,并在里面看到编译出来的 .so 文件。这个库文件可以被 Python 脚本调用,并执行我们用 C 编写的阶乘函数。

 

测试结果

让我们试一下吧。我简单地写了一个 test.py,并把它放在和 .so 文件同一个文件夹下,方便调用(当然,你如果用了 install 选项,那就无需这么做,在任意目录都能调用这个包)。

test.py 文件的内容如下:

from cmath import factorial

print(factorial(6))

运行一下,得到结果 720。搞定!我们用 C 语言写的这个小模组成功地导入并执行啦!

恭喜你已经看完了今天的小教程,你打算给自己的 python 增加哪些威力强大的模块呢?欢迎留言吐槽!

本书围绕数据的表示、存取、计算、分析和可视化等内容分两部分详细介绍Python语言程序设计:Python程序设计基础部分(第1章至第6章、第10章)主要介绍Python作为一门高级编程语言所涉及的语法知识、控制结构、函数与模块、类和对象、图形界面设计,并穿插了计数、累加、连乘等数值计算常用算法内容;Python数据管理与分析部分(第7章至第9章、第11章)主要介绍数据文件操作、数据库操作、数据分析和数据可视化等知识。 本书按照首先论道、继而论理、然后操作的顺序,从哲学视角看程序设计,使读者在认识物质世界规律的过程中了解计算机的特点、程序设计的特点和人机交互的规律,在认识世界、解释世界和改造世界的实践中掌握Python编程特点、技术和技巧,学会结构化程序设计、面向对象程序设计、人机交互界面设计和数据分析等方面的编程技术。 本书可供大数据专业学生学习程序设计使用,也可作为高等院校“Python程序设计”课程教材,还可供数据分析人员参考。 内容实用——理论与实践结合,重点突出应用 体系完善——构建完整的大数据专业解决方案 产教融合——高校企业共参与,对标行业标准 资源丰富——微课、课件、教案、源码、答案 随着国家大数据战略的深入实施,各行业智慧化建设急需数据分析人才和智能应用人才。智慧化简单来说是一个以机器替换人力的过程,而机器的“灵魂”是程序。Python已经成为公认的驱动大数据智能应用的主流编程语言Python程序设计的书籍已经琳琅满目,每一本书都凝聚了作者对Python的理解和对程序设计的认识,都是作者编程开发和教学经验的总结,都折射出作者的专业背景。由于大数据专业学生对程序设计的要求不是很高,但又需要具备一定的计算思维能力,熟悉用程序进行数据分析的一般流程,因此程序设计教材要言不甚深、文不甚俗,既要覆盖相关技术,又不能面面俱到,注重对问题的分析和解释,用程序表达算法。鉴于此,我们编写了本书。 本书每一章的标题都以Python开头,凸显Python在各个部分都有其独特的编程理念和方法。与其他高级编程语言如C、C++和Java等相比,Python在数据的表示、处理和可视化方面都有绝对的优势。有编程基础的学习者在学习Python时最好能忘掉以往程序设计语言的语法,彻底转变观念,以全新的姿态融入到Python的编程特点和规律之中。如变量定义、数据类型、数据结构、控制结构、类和对象、文件访问、数据分析和可视化,每一部分都有其特别之处,都值得我们重新认识,重新使用,重新熟悉。每一章开始的思维导图都是对本章技术脉络的梳理,开门见山地给学习者展示本章的知识和技术体系,以便学习者在学习过程中始终能保持思路清晰和整体把握。每一章开头的本章导读都是编者多年来程序开发与设计教学经验的提炼与升华,都是对程序设计的理解和感悟,值得学习者深入领会。每一章开头的本章要点都是要求学习者深入理解的重要知识和熟练掌握的关键技术。每一章的小结都是对本章要点的具体解释,供学习者复习查询。 本书为河北省高等教育教学改革研究与实践项目“新工科背景下警务大数据应用专业人才培养模式与教学实践研究”(编号:2018GJJG450)的阶段性成果。 下面是本书的体系结构图。 第1章Python编程初步。学习本章,要了解Python作为一种计算机程序设计脚本语言,结合了解释性、编译性和互动性的特点;了解在Linux和Windows中安装Python的方法;了解IDLE、PyCharm和Jupyter三种常用Python程序编辑环境。工欲善其事,必先利其器,通过对本章的学习,学习者可拥有一个强大的编程工具,从此开启数据分析编程之旅。 第2章Python语言基础。Python作为一门与计算机交流的编程语言,有着跟自然语言相似的特点:字、词、句、段落、篇章,以及相应的行文语法规则。学习本章,要理解程序行文的字词句,主要包括基本数据类型、常量和变量、运算符和表达式;理解程序的段落和篇章,主要包括常用内置函数、库函数和系统函数的使用;掌握程序的语法规则,主要包括常用的变量定义和标识符命名规则、语句组织成文编码规则等。这些都是程序设计的基础,学习者只有对此熟练掌握后,才能在后续的学习中得心应手。 第3章Python组合数据类型。组合数据类型是Python语言区别于其他高级编程语言的一大特色,通过组合数据类型,省去了其他语言各种复杂数据结构的设计,给编程人员带来了极大的方便,这也是Python流行于数据分析领域的原因之一。学习本章,要熟练掌握Python组合数据类型(列表、元组、字符串、字典、集合)的创建、访问和常见基本操作,以及序列解包功能。 第4章 Python控制结构。针对物质随时间由简单向复杂、由低级向高级发展的顺序,Python语言有相应的顺序结构语句;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值