[Python自己主动化]使用C来扩展Python
一、需求背景
Python 差点儿能解决你所遇到的全部问题,但 Python 常被人提及的问题就是速度问题,这时假设想提升 Python 的速度,基本都会使用 C/C++ 来扩展 Python 接口。这样的方法不过提升 Python 速度的诸多方法中的一种而已。
同一时候,对于一些使用 Python 来解决的问题比較棘手时,也能够考虑使用 C/C++ 来扩展 Python 接口。这样在调用的时候。就会降低一些 Python 带来的麻烦。
基于不同的平台,使用 C/C++ 扩展 Python 接口时,产生的文件也不一样(*.so 或 *.dll),然后将其改为合适的后缀。本文使用 Win7 和 VS2013 来编写 Python 扩展。
二、准备工作
使用 VS2013 新建一个project,并做例如以下操作:
设置 Configuration Type 属性为 Dynamic Library(.dll)
选择 Release 模式(由于没有 python27_d.lib 文件)
加入 python 头文件路径(默觉得
C:\Python27\include\
)加入 python 库文件路径(默觉得
C:\Python27\libs\
)能够将 include 和 libs 拷贝放到一个文件夹下。然后放在project文件夹
三、Python 对象
Python 对象在 Python 解析器中都为 PyObject
。
而在 C/C++ 中仅仅能声明为 PyObject* 类型的 python 对象。然后使用该对象相应的初始化函数初始化。
如:PyTuple_New
, PyList_New
, PyDict_New
, Py_BuildValue
等。
怎样构建一个 {'key1':
{'key2': ['valuex', 'valuey']}}
对象?
PyObject* pObj1 = PyDict_New(); PyObject* pObj2 = PyDict_New(); PyObject* pObj3 = PyList_New(2); PyList_SetItem(pObj3, 0, Py_BuildValue("s", "valuex")); PyList_SetItem(pObj3, 1, Py_BuildValue("s", "valuey")); PyDict_SetItem(pObj2, "key2", pObj3); PyDict_SetItem(pObj1, "key1", pObj2);
四、Python 内存管理
Python 对象管理使用引用计数技术。从而实现自己主动垃圾回收机制。
提供两个宏 Py_INCREF
和 Py_DECREF
来管理引用计数。
前文中申请的 pObj1/pObj2/pObj3 对象假设须要释放。应该怎样处理?
不能直接 free/delete
,必须使用 Py_DECREF(pObj1)
。然后 pObj1
= NULL
就可以。
五、编写 Python 扩展
编写 Python 扩展主要包含三步:
实现接口函数
定义方法列表
实现初始化函数
演示样例功能
实现一个
add
函数,接受三个整形參数,求其和并返回。
1. 实现接口函数
先看代码,然后进行说明。
static PyObject* add(PyObject* self, PyObject* args) { int x = 0; int y = 0; int z = 0; int i = 0; if (!PyArg_ParseTuple(args, "iii", &x, &y, &z)) { return NULL; } i = x + y + z; return Py_BuildValue("i", i); }
接口函数必须是 static
函数。同一时候必须返会 PyObject*
类型。
接口函数的第一个參数是 PyObject*
self
,该參数是 Python 内部使用的,临时不深究。
接口函数的第二个參数是 PyObject*
args
,该參数是一个參数列表。把全部參数都整合为一个string
。因此须要从这个 string
參数里解析參数。函数PyArg_ParseTuple
就是来完毕这项任务的。该函数的第一个參数 agrs
就是须要转换的參数,第二个參数 iii
是格式符号。第三个參数及后面的參数就是提取出来的參数存放的真正位置。这些參数必须传递其地址。
对于 add
函数,会提取三个參数,各自是 x
,y
,z
。当中,格式串 "iii"
代表三个 int
。
类型为string
则是"s"
。假设有三个 string
则格式符号为 "ss"
。
接口函数必须返回结果,其类型能够是 C/C++ 类型或是自己定义类型。可是最后都必须把其转换成 PyObject*
,
让 Python 认识这个对象,这个类型转换使用函数 Py_BuildValue
来完毕,该函数是函数 PyArg_ParseTuple
的逆过程。它的第一个參数和 PyArg_ParseTuple
的第二个參数一样。都是格式化符号,第二个參数是须要转换的參数。函数 Py_BuildValue
会把全部的返回仅仅组装成一个
tuple 给Python 所用。
2. 定义方法列表
相同,还是先看演示样例代码,再进行解释说明。
static PyMethodDef addMethods[] = { {"add", (PyCFunction)add, METH_VARARGS, "add(int1, int2, int3)"}, {NULL, NULL, 0, NULL} };
PyMethodDef
是一个
C 结构体。用来完毕一个映射,须要把扩展的函数都映射到这个表里。
表的第一个字段是 Python 真正认识的。也就是 Python 里的能够使用的方法名字。
第二个字段是 Python 里的这种方法名字的详细的 C/C++ 实现的函数名。 在 Python 里调用add
,
真正运行的是用 C/C++ 实现的 add
函数。
第三个字段是 METH_VARARGS
,它告诉
Python 函数 add
是调用
C/C++ 函数来实现的。
第四个字段是这个函数的说明信息。假设在 Python 里 help
这个函数,将会显示这个说明信息。也即是在
Python 里的函数的文档说明。
须要注意的是:这个列表必须以 {
NULL, NULL, 0, NULL }
形式结束。
3. 实现初始化函数
这步是编写扩展实现的最后一步。也还是先看看代码。
PyMODINIT_FUNC initadd() { Py_InitModule("add", addMethods); }
须要特别注意的是,这个函数的名字不能修改,必须是 init + 模块名字。 这里的模块名为add
,所以这个函数就是 initadd()
。
这样
Python 在导入 add
模块时。才干找到这个函数,并调用。
该函数调用 Py_InitModule
函数来将模块名字和映射表结合在一起。
它表示 add
这个模块使用addMethods
这个映射表。告诉
Python 应该这样导入该模块。
六、产生包文件
在 Windows 操作系统下。扩展的 Python 接口文件以 pyd
的形式存在,其产生 pyd
文件的方式有两种。
直接编译成 DLL 文件
使用 distutils 模块
1. 直接编译成 DLL 文件
这样的方法也是最简单的方法。
-
将编写好的接口文件(cpp。位于DLLproject中)进行编译,会得到一个 DLL 文件,本例为
add.dll
。 -
将
add.dll
改名为add.pyd
。 -
将
add.pyd
复制到 Python 安装文件夹的 DLLs 文件夹下(默觉得C:\Python27\DLLs\
)。 -
如今能够使用
add
函数了(后文演示)。
2. 使用 distutils 模块
Python 提供了扩展接口的打包接口。先看代码(其它扩展接口的使用大同小异)。
#coding=utf-8 #setup.py from distutils.core import setup, Extension addmodule = Extension("add", sources = ["add.cpp"]) setup(name = "add", version = "1.0", description = "Test Demo", author = "thinkerou", ext_modules = [addmodule])
然后使用命令 python
setup.py build --compiler=msvc
,假设一切顺利,将会在 setup.py 同级文件夹下产生一个 build
文件夹。其有两个文件夹 lib.win32-2.7
和 temp.win32-2.7
,关注前一个文件夹,在其下产生了一个文件 add.pyd
,这也正是第一种方法中产生的
DLL 改名后的名字。
3. 提示错误 error: Unable to find vcvarsall.bat
在使用 distutils 模块进行打包时,在调用 Python 命令进行 build 的过程中,假设出现例如以下错误信息:
error: Unable to find vcvarsall.bat
错误的原因是由于 Python 扩展编译工具在注冊表中找不到 VS2010 的注冊信息所致。
能够使用例如以下方法来进行解决:
在 Python 安装文件夹下,找到 Lib\distutils\msvc9compiler.py
。默认路径为C:\Python27\Lib\distutils\msvc9compiler.py
。然后在该文件里找到toolskey
= “VS%0.f0COMNTOOLS” % version
一行将其凝视掉。然后添加一行 toolskey
= "VS100COMNTOOLS"
,如此便能解决这个错误。
而对于 "VS100COMNTOOLS"
所代表的意义,能够查看注冊表中相应的信息知道,注冊表中位置为:
HKEYLOCALMACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
在 Environment 下会有 VS100COMNTOOLS
,VS110COMNTOOLS
。VS120COMNTOOLS
,这三项不是一定有,须要依据机器安装
Visual Studio 的版本号而定,各自代表什么意思。能够依据相应项后面的路径值知道。
七、使用扩展接口
如前所述,将 add.pyd
文件复制到
Python 安装文件夹下的 DLLs 文件夹下后,就能够使用 add
函数了。
使用 import
导入 add
包,例如以下:
>>> import add >>> add.add(1, 2, 3) 6
附:说明
兴许会基于此。介绍搜狗内部使用C扩展Python的详细应用。敬请期待!
转载请注明来自搜狗測试