{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%matplotlib inline\n", "import numpy as np" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> **作者**:\tValentin Haenel\n", "\n", "本章包含了许多可以在Python使用原生代码(主要是C/C++)方式的*介绍*,这个过程通常被称为*封装*。本章的目的是给你有哪些技术存在已经各自有优劣式的一点儿感觉,这样你可以根据你的具体需求选择适合的方式。无论如何,只要你开始做封装,你几乎都必然需要咨询你选定技术的文档。\n", "\n", "---\n", "章节内容\n", "\n", " 简介\n", " Python-C-Api\n", " Ctypes\n", " SWIG\n", " Cython\n", " 总结\n", " 进一步阅读和参考\n", " 练习\n", "\n", "---\n", "\n", "## 2.8.1 简介\n", "\n", "本章将涵盖一下技术:\n", "\n", "- Python-C-Api\n", "- Ctypes\n", "- SWIG (简化封装器和接口生成器)\n", "- Cython\n", "\n", "这四种技术可能是最知名的,其中Cython可能是最高级的,并且你应该最优先使用它。其他的技术也很重要,如果你想要从不同点角度理解封装问题。之前提到过,还有其他的替代技术,但是,理解了以上这些基础的,你就可以评估你选择的技术是否满足你的需求。\n", "\n", "在评估技术时,下列标准会有帮助:\n", "- 需要额外的库吗?\n", "- 代码可以自动生成?\n", "- 是否需要编译?\n", "- 与Numpy数组交互是否有良好的支持?\n", "- 是否支持C++?\n", "\n", "在你动手前,应该先考虑一下使用情景。在于原生代码交互时,通常来自于两个应用场景:\n", "\n", "- 需要利用C/C++中现存的代码,或者是因为它已经存在,或者是因为它更快。\n", "- Python代码太慢,将内部循环变成原生代码\n", "\n", "每个技术都使用来自math.h的`cos`函数的封装来进行演示。尽管这是一个无聊例子,但是它确实给我们很好的演示了封装方法的基础,因为每个技术也包含了一定程度Numpy支持,这也用计算一些数组来计算consine的例子来演示。\n", "\n", "最后,两个小警示:\n", "\n", "- 所有这些方法在Python解释器中都可能崩溃(细分错误),因为在C代码中的错误。\n", "- 所有的例子都在Linux中完成,他们应该在其他操作系统中也可用。\n", "- 在大多数例子中,你都会需要C编译器。\n", "\n", "## 2.8.2 Python-C-Api\n", "\n", "Python-C-API是标准Python解释器(即CPython)的基础。使用这个API可以在C和C++中写Python扩展模块。很明显,由于语言兼容性的优点,这些扩展模块可以调用任何用C或者C++写的函数。\n", "\n", "当使用Python-C-API时,人们通常写许多样板化的代码,首先解析函数接收的参数,然后构建返回的类型。\n", "\n", "**优点**\n", "- 不需要额外的库\n", "- 许多系层的控制\n", "- C++完全可用\n", "\n", "**不足**\n", "- 可以需要一定的努力\n", "- 高代码成本\n", "- 必须编译\n", "- 高维护成本\n", "- 如果C-API改变无法向前兼容Python版本\n", "- 引用计数错误很容易出现,但是很难被跟踪。\n", "\n", "\n", "---\n", "\n", "**注意** 此处的Python-C-Api例子主要是用来演示。许多其他例子的确依赖它,因此,对于它如何工作有一个高层次的理解。在99%的使用场景下,使用替代技术会更好。\n", "\n", "---\n", "\n", "---\n", "\n", "**注意** 因为引用计数很容易出现然而很难被跟踪,任何需要使用Python C-API的人都应该阅读[官方Python文档关于对象、类型和引用计数的部分](https://docs.python.org/2/c-api/intro.html#objects-types-and-reference-counts)。此外,有一个名为[cpychecker](https://gcc-python-plugin.readthedocs.org/en/latest/cpychecker.html)的工具可以发现引用计数的常见错误。\n", "\n", "---\n", "\n", "### 2.8.2.1 例子\n", "\n", "下面的C扩展模块,让来自标准`math`库的`cos`函数在Python中可用:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "/* 用Python-C-API封装来自math.h的cos函数的例子 */\n", "\n", "#include \n", "#include \n", "\n", "/* wrapped cosine function */\n", "static PyObject* cos_func(PyObject* self, PyObject* args)\n", "{\n", " double value;\n", " double answer;\n", "\n", " /* parse the input, from python float to c double */\n", " if (!PyArg_ParseTuple(args, \"d\", &value))\n", " return NULL;\n", " /* if the above function returns -1, an appropriate Python exception will\n", " * have been set, and the function simply returns NULL\n", " */\n", "\n", " /* call cos from libm */\n", " answer = cos(value);\n", "\n", " /* construct the output from cos, from c double to python float */\n", " return Py_BuildValue(\"f\", answer);\n", "}\n", "\n", "/* define functions in module */\n", "static PyMethodDef CosMethods[] =\n", "{\n", " {\"cos_func\", cos_func, METH_VARARGS, \"evaluate the cosine\"},\n", " {NULL, NULL, 0, NULL}\n", "};\n", "\n", "/* module initialization */\n", "PyMODINIT_FUNC\n", "\n", "initcos_module(void)\n", "{\n", " (void) Py_InitModule(\"cos_module\", CosMethods);\n", "}" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "如你所见,有许多样板,既包括 «massage» 的参数和return类型以及模块初始化。尽管随着扩展的增长,这些东西中的一些是分期偿还,模板每个函数需要的模板还是一样的。\n", "\n", "标准python构建系统`distutils`支持从`setup.py`编译C-扩展, 非常方便:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from distutils.core import setup, Extension\n", "\n", "# 定义扩展模块\n", "cos_module = Extension('cos_module', sources=['cos_module.c'])\n", "\n", "# 运行setup\n", "setup(ext_modules=[cos_module])" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "这可以被编译:\n", "\n", "```\n", "\n", "$ cd advanced/interfacing_with_c/python_c_api\n", "\n", "$ ls\n", "cos_module.c setup.py\n", "\n", "$ python setup.py build_ext --inplace\n", "running build_ext\n", "building 'cos_module' extension\n", "creating build\n", "creating build/temp.linux-x86_64-2.7\n", "gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/include/python2.7 -c cos_module.c -o build/temp.linux-x86_64-2.7/cos_module.o\n", "gcc -pthread -shared build/temp.linux-x86_64-2.7/cos_module.o -L/home/esc/anaconda/lib -lpython2.7 -o /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/python_c_api/cos_module.so\n", "\n", "$ ls\n", "build/ cos_module.c cos_module.so setup.py\n", "\n", "```\n", "\n", "- `build_ext` 是构建扩展模块\n", "- `--inplace` 将把编译后的扩展模块输出到当前目录\n", "\n", "文件`cos_module.so`包含编译后的扩展,我们可以在IPython解释器中加载它:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "In [1]: import cos_module\n", "\n", "In [2]: cos_module?\n", "Type: module\n", "String Form:\n", "File: /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/python_c_api/cos_module.so\n", "Docstring: \n", "\n", "In [3]: dir(cos_module)\n", "Out[3]: ['__doc__', '__file__', '__name__', '__package__', 'cos_func']\n", "\n", "In [4]: cos_module.cos_func(1.0)\n", "Out[4]: 0.5403023058681398\n", "\n", "In [5]: cos_module.cos_func(0.0)\n", "Out[5]: 1.0\n", "\n", "In [6]: cos_module.cos_func(3.14159265359)\n", "Out[7]: -1.0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "现在我们看一下这有多强壮:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "In [10]: cos_module.cos_func('foo')\n", "---------------------------------------------------------------------------\n", "TypeError Traceback (most recent call last)\n", " in ()\n", "----> 1 cos_module.cos_func('foo')\n", "\n", "TypeError: a float is required" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2.8.2.2. Numpy 支持\n", "\n", "Numpy模拟Python-C-API, 自身也实现了C-扩展, 产生了Numpy-C-API。这个API可以被用来创建和操作来自C的Numpy数组, 当写一个自定义的C-扩展。也可以看一下:参考:`advanced_numpy`。\n", "\n", "---\n", "**注意** 如果你确实需要使用Numpy C-API参考关于[Arrays](http://docs.scipy.org/doc/numpy/reference/c-api.array.html)和[Iterators](http://docs.scipy.org/doc/numpy/reference/c-api.iterator.html)的文档。\n", "\n", "---\n", "\n", "下列的例子显示如何将Numpy数组作为参数传递给函数,以及如果使用(旧)Numpy-C-API在Numpy数组上迭代。它只是将一个数组作为参数应用到来自`math.h`的cosine函数,并且返回生成的新数组。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "/* 使用Numpy-C-API封装来自math.h的cos函数 . */\n", "\n", "#include \n", "#include \n", "#include \n", "\n", "/* 封装cosine函数 */\n", "static PyObject* cos_func_np(PyObject* self, PyObject* args)\n", "{\n", "\n", " PyArrayObject *in_array;\n", " PyObject *out_array;\n", " NpyIter *in_iter;\n", " NpyIter *out_iter;\n", " NpyIter_IterNextFunc *in_iternext;\n", " NpyIter_IterNextFunc *out_iternext;\n", "\n", " /* parse single numpy array argument */\n", " if (!PyArg_ParseTuple(args, \"O!\", &PyArray_Type, &in_array))\n", " return NULL;\n", "\n", " /* construct the output array, like the input array */\n", " out_array = PyArray_NewLikeArray(in_array, NPY_ANYORDER, NULL, 0);\n", " if (out_array == NULL)\n", " return NULL;\n", "\n", " /* create the iterators */\n", " in_iter = NpyIter_New(in_array, NPY_ITER_READONLY, NPY_KEEPORDER,\n", " NPY_NO_CASTING, NULL);\n", " if (in_iter == NULL)\n", " goto fail;\n", "\n", " out_iter = NpyIter_New((PyArrayObject *)out_array, NPY_ITER_READWRITE,\n", " NPY_KEEPORDER, NPY_NO_CASTING, NULL);\n", " if (out_iter == NULL) {\n", " NpyIter_Deallocate(in_iter);\n", " goto fail;\n", " }\n", "\n", " in_iternext = NpyIter_GetIterNext(in_iter, NULL);\n", " out_iternext = NpyIter_GetIterNext(out_iter, NULL);\n", " if (in_iternext == NULL || out_iternext == NULL) {\n", " NpyIter_Deallocate(in_iter);\n", " NpyIter_Deallocate(out_iter);\n", " goto fail;\n", " }\n", " double ** in_dataptr = (double **) NpyIter_GetDataPtrArray(in_iter);\n", " double ** out_dataptr = (double **) NpyIter_GetDataPtrArray(out_iter);\n", "\n", " /* iterate over the arrays */\n", " do {\n", " **out_dataptr = cos(**in_dataptr);\n", " } while(in_iternext(in_iter) && out_iternext(out_iter));\n", "\n", " /* clean up and return the result */\n", " NpyIter_Deallocate(in_iter);\n", " NpyIter_Deallocate(out_iter);\n", " Py_INCREF(out_array);\n", " return out_array;\n", "\n", " /* in case bad things happen */\n", " fail:\n", " Py_XDECREF(out_array);\n", " return NULL;\n", "}\n", "\n", "/* 在模块中定义函数 */\n", "static PyMethodDef CosMethods[] =\n", "{\n", " {\"cos_func_np\", cos_func_np, METH_VARARGS,\n", " \"evaluate the cosine on a numpy array\"},\n", " {NULL, NULL, 0, NULL}\n", "};\n", "\n", "/* 模块初始化 */\n", "PyMODINIT_FUNC\n", "initcos_module_np(void)\n", "{\n", " (void) Py_InitModule(\"cos_module_np\", CosMethods);\n", " /* IMPORTANT: this must be called */\n", " import_array();\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "要编译这个模块,我们可以再用`distutils`。但是我们需要通过使用func:numpy.get_include确保包含了Numpy头部:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from distutils.core import setup, Extension\n", "import numpy\n", "\n", "# define the extension module\n", "cos_module_np = Extension('cos_module_np', sources=['cos_module_np.c'],\n", " include_dirs=[numpy.get_include()])\n", "\n", "# run the setup\n", "setup(ext_modules=[cos_module_np])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "要说服我们自己这个方式确实有效,我们来跑一下下面的测试脚本:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cos_module_np\n", "import numpy as np\n", "import pylab\n", "\n", "x = np.arange(0, 2 * np.pi, 0.1)\n", "y = cos_module_np.cos_func_np(x)\n", "pylab.plot(x, y)\n", "pylab.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "这会产生以下的图像:\n", "\n", "![](http://www.scipy-lectures.org/_images/test_cos_module_np.png)\n", "\n", "## 2.8.3. Ctypes\n", "\n", "Ctypes是Python的一个外来函数库。它提供了C兼容的数据类型,并且允许在DLLs或者共享的库中调用函数。它可以用来在纯Python中封装这些库。\n", "\n", "**优点**\n", "- Python标准库的一部分\n", "- 不需要编译\n", "- 代码封装都是在Python中\n", "\n", "**不足**\n", "- 需要代码作为一个共享的库(粗略地说,在windows中是 \\*.dll,在Linux中是\\*.so,在Mac OSX中是 \\*.dylib)\n", "- 对C++支持并不好\n", "\n", "### 2.8.3.1 例子\n", "\n", "如前面提到的,代码封装完全在Python中。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "\"\"\" 用ctypes封装来自math.h的 cos 函数。 \"\"\"\n", "\n", "import ctypes\n", "from ctypes.util import find_library\n", "\n", "# find and load the library\n", "libm = ctypes.cdll.LoadLibrary(find_library('m'))\n", "# set the argument type\n", "libm.cos.argtypes = [ctypes.c_double]\n", "# set the return type\n", "libm.cos.restype = ctypes.c_double\n", "\n", "\n", "def cos_func(arg):\n", " ''' 封装math.h cos函数 '''\n", " return libm.cos(arg)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 寻找和加载库可能非常依赖于你的操作系统,检查[文档](https://docs.python.org/2/library/ctypes.html#loading-dynamic-link-libraries)来了解细节\n", "- 这可能有些欺骗性,因为math库在系统中已经是编译模式。如果你想要封装一个内置的库,需要先编译它,可能需要或者不需要额外的工作。\n", "我们现在可以像前面一样使用这个库:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "In [1]: import cos_module\n", "\n", "In [2]: cos_module?\n", "Type: module\n", "String Form:\n", "File: /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/ctypes/cos_module.py\n", "Docstring: \n", "\n", "In [3]: dir(cos_module)\n", "Out[3]:\n", "['__builtins__',\n", " '__doc__',\n", " '__file__',\n", " '__name__',\n", " '__package__',\n", " 'cos_func',\n", " 'ctypes',\n", " 'find_library',\n", " 'libm']\n", "\n", "In [4]: cos_module.cos_func(1.0)\n", "Out[4]: 0.5403023058681398\n", "\n", "In [5]: cos_module.cos_func(0.0)\n", "Out[5]: 1.0\n", "\n", "In [6]: cos_module.cos_func(3.14159265359)\n", "Out[6]: -1.0" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "### 2.8.3.2 Numpy支持\n", "\n", "Numpy包含一些与ctypes交互的支持。特别是支持将特定Numpy数组属性作为ctypes数据类型研究,并且有函数可以将C数组和Numpy数据互相转换。\n", "\n", "更多信息,可以看一下Numpy手册的对应部分或者`numpy.ndarray.ctypes`和`numpy.ctypeslib`的API文档。\n", "\n", "对于下面的例子,让我们假设一个C函数,输入输出都是一个数组,计算输入数组的cosine并将结果输出为一个数组。\n", "\n", "库包含下列头文件(尽管在这个例子中并不是必须这样,为了完整,我们还是把这一步列出来):" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "void cos_doubles(double * in_array, double * out_array, int size);" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "这个函数实现在下列的C源文件中:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "#include \n", "\n", "/* Compute the cosine of each element in in_array, storing the result in\n", " * out_array. */\n", "void cos_doubles(double * in_array, double * out_array, int size){\n", " int i;\n", " for(i=0;i\n", "\n", "double cos_func(double arg){\n", " return cos(arg);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "头文件`cos_module.h`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "double cos_func(double arg);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "尽管我们的目的是将`cos_func`暴露给Python。要用SWIG来完成这个目的,我们需要写一个包含SWIG指导的接口文件。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "/* Example of wrapping cos function from math.h using SWIG. */\n", "\n", "%module cos_module\n", "%{\n", " /* the resulting C file should be built as a python extension */\n", " #define SWIG_FILE_WITH_INIT\n", " /* Includes the header in the wrapper code */\n", " #include \"cos_module.h\"\n", "%}\n", "/* Parse the header file to generate wrappers */\n", "%include \"cos_module.h\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "如你所见,这里不需要太多的代码。对于这个简单的例子,它简单到只需要在接口文件中包含一个头文件,来向Python暴露函数。但是,SWIG确实允许更多精细包含或者排除在头文件中发现的函数,细节检查一下文档。\n", "\n", "生成编译的封装器是一个两阶段的过程:\n", "\n", "- 在接口文件上运行`swig`可执行文件来生成文件`cos_module_wrap.c`, 其源文件是自动生成的Python C-extension和`cos_module.py`, 是自动生成的Python模块。\n", "\n", "- 编译`cos_module_wrap.c`到`_cos_module.so`。幸运的,`distutils`知道如何处理SWIG接口文件, 因此我们的`setup.py`是很简单的:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from distutils.core import setup, Extension\n", "\n", "setup(ext_modules=[Extension(\"_cos_module\",\n", " sources=[\"cos_module.c\", \"cos_module.i\"])])" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "$ cd advanced/interfacing_with_c/swig\n", "\n", "$ ls\n", "cos_module.c cos_module.h cos_module.i setup.py\n", "\n", "$ python setup.py build_ext --inplace\n", "running build_ext\n", "building '_cos_module' extension\n", "swigging cos_module.i to cos_module_wrap.c\n", "swig -python -o cos_module_wrap.c cos_module.i\n", "creating build\n", "creating build/temp.linux-x86_64-2.7\n", "gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/include/python2.7 -c cos_module.c -o build/temp.linux-x86_64-2.7/cos_module.o\n", "gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/include/python2.7 -c cos_module_wrap.c -o build/temp.linux-x86_64-2.7/cos_module_wrap.o\n", "gcc -pthread -shared build/temp.linux-x86_64-2.7/cos_module.o build/temp.linux-x86_64-2.7/cos_module_wrap.o -L/home/esc/anaconda/lib -lpython2.7 -o /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/swig/_cos_module.so\n", "\n", "$ ls\n", "build/ cos_module.c cos_module.h cos_module.i cos_module.py _cos_module.so* cos_module_wrap.c setup.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "我们可以像前面的例子中那样加载和运行`cos_module`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "In [1]: import cos_module\n", "\n", "In [2]: cos_module?\n", "Type: module\n", "String Form:\n", "File: /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/swig/cos_module.py\n", "Docstring: \n", "\n", "In [3]: dir(cos_module)\n", "Out[3]:\n", "['__builtins__',\n", " '__doc__',\n", " '__file__',\n", " '__name__',\n", " '__package__',\n", " '_cos_module',\n", " '_newclass',\n", " '_object',\n", " '_swig_getattr',\n", " '_swig_property',\n", " '_swig_repr',\n", " '_swig_setattr',\n", " '_swig_setattr_nondynamic',\n", " 'cos_func']\n", "\n", "In [4]: cos_module.cos_func(1.0)\n", "Out[4]: 0.5403023058681398\n", "\n", "In [5]: cos_module.cos_func(0.0)\n", "Out[5]: 1.0\n", "\n", "In [6]: cos_module.cos_func(3.14159265359)\n", "Out[6]: -1.0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "接下来我们测试一下强壮性,我们看到我们可以获得一个更多的错误信息 (虽然, 严格来讲在Python中没有double类型):" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "In [7]: cos_module.cos_func('foo')\n", "---------------------------------------------------------------------------\n", "TypeError Traceback (most recent call last)\n", " in ()\n", "----> 1 cos_module.cos_func('foo')\n", "\n", "TypeError: in method 'cos_func', argument 1 of type 'double'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2.8.4.2 Numpy 支持\n", "\n", "Numpy在`numpy.i`文件中提供了[SWIG的支持](http://docs.scipy.org/doc/numpy/reference/swig.html)。这个接口文件定义了许多所谓的typemaps,支持了Numpy数组和C-Arrays的转化。在接下来的例子中,我们将快速看一下typemaps实际是如何工作的。\n", "\n", "我们有相同的`cos_doubles`函数,在ctypes例子中:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "void cos_doubles(double * in_array, double * out_array, int size);" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "#include \n", "\n", "/* Compute the cosine of each element in in_array, storing the result in\n", " * out_array. */\n", "void cos_doubles(double * in_array, double * out_array, int size){\n", " int i;\n", " for(i=0;i\n", "File: /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/cython/cos_module.so\n", "Docstring: \n", "\n", "In [3]: dir(cos_module)\n", "Out[3]:\n", "['__builtins__',\n", " '__doc__',\n", " '__file__',\n", " '__name__',\n", " '__package__',\n", " '__test__',\n", " 'cos_func']\n", "\n", "In [4]: cos_module.cos_func(1.0)\n", "Out[4]: 0.5403023058681398\n", "\n", "In [5]: cos_module.cos_func(0.0)\n", "Out[5]: 1.0\n", "\n", "In [6]: cos_module.cos_func(3.14159265359)\n", "Out[6]: -1.0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "并且,测试一下强壮性,我们可以看到我们得到了更好的错误信息:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "In [7]: cos_module.cos_func('foo')\n", "---------------------------------------------------------------------------\n", "TypeError Traceback (most recent call last)\n", " in ()\n", "----> 1 cos_module.cos_func('foo')\n", "\n", "/home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/cython/cos_module.so in cos_module.cos_func (cos_module.c:506)()\n", "\n", "TypeError: a float is required" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "此外,不需要Cython完全传输到C math库的声明,上面的代码可以简化为:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "\"\"\" Simpler example of wrapping cos function from math.h using Cython. \"\"\"\n", "\n", "from libc.math cimport cos\n", "\n", "def cos_func(arg):\n", " return cos(arg)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "在这种情况下,`cimport`语句用于导入`cos`函数。\n", "\n", "### 2.8.5.2 Numpy支持\n", "\n", "Cython通过`numpy.pyx`文件支持Numpy,允许你为你的Cython代码添加Numpy数组类型,即就像指定变量`i`是`int`类型,你也可以指定变量`a`是带有给定的`dtype`的`numpy.ndarray`。同时,同时特定的优化比如边际检查也是支持的。看一下[Cython文档](http://docs.cython.org/src/tutorial/numpy.html)的对应部分。如果你想要将Numpy数组作为C数组传递给Cython封装的C函数,在[Cython wiki](http://wiki.cython.org/tutorials/NumpyPointerToC)上有对应的部分。\n", "\n", "在下面的例子中,我们将演示如何用Cython来封装类似的`cos_doubles`。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "void cos_doubles(double * in_array, double * out_array, int size);" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "#include \n", "\n", "/* Compute the cosine of each element in in_array, storing the result in\n", " * out_array. */\n", "void cos_doubles(double * in_array, double * out_array, int size){\n", " int i;\n", " for(i=0;i np.PyArray_DATA(in_array),\n", " np.PyArray_DATA(out_array),\n", " in_array.shape[0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "可以使用`distutils`来编译:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from distutils.core import setup, Extension\n", "import numpy\n", "from Cython.Distutils import build_ext\n", "\n", "setup(\n", " cmdclass={'build_ext': build_ext},\n", " ext_modules=[Extension(\"cos_doubles\",\n", " sources=[\"_cos_doubles.pyx\", \"cos_doubles.c\"],\n", " include_dirs=[numpy.get_include()])],\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "与前面的编译Numpy例子类似,我们需要`include_dirs`选项。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "$ ls\n", "cos_doubles.c cos_doubles.h _cos_doubles.pyx setup.py test_cos_doubles.py\n", "$ python setup.py build_ext -i\n", "running build_ext\n", "cythoning _cos_doubles.pyx to _cos_doubles.c\n", "building 'cos_doubles' extension\n", "creating build\n", "creating build/temp.linux-x86_64-2.7\n", "gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include -I/home/esc/anaconda/include/python2.7 -c _cos_doubles.c -o build/temp.linux-x86_64-2.7/_cos_doubles.o\n", "In file included from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1722,\n", " from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:17,\n", " from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/arrayobject.h:15,\n", " from _cos_doubles.c:253:\n", "/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/npy_deprecated_api.h:11:2: warning: #warning \"Using deprecated NumPy API, disable it by #defining NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\"\n", "/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/__ufunc_api.h:236: warning: ‘_import_umath’ defined but not used\n", "gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include -I/home/esc/anaconda/include/python2.7 -c cos_doubles.c -o build/temp.linux-x86_64-2.7/cos_doubles.o\n", "gcc -pthread -shared build/temp.linux-x86_64-2.7/_cos_doubles.o build/temp.linux-x86_64-2.7/cos_doubles.o -L/home/esc/anaconda/lib -lpython2.7 -o /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/cython_numpy/cos_doubles.so\n", "$ ls\n", "build/ _cos_doubles.c cos_doubles.c cos_doubles.h _cos_doubles.pyx cos_doubles.so* setup.py test_cos_doubles.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "和前面一样,我们来验证一下它是有效的:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import numpy as np\n", "import pylab\n", "import cos_doubles\n", "\n", "x = np.arange(0, 2 * np.pi, 0.1)\n", "y = np.empty_like(x)\n", "\n", "cos_doubles.cos_doubles_func(x, y)\n", "pylab.plot(x, y)\n", "pylab.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![](http://www.scipy-lectures.org/_images/test_cos_doubles2.png)\n", "\n", "### 2.8.6 总结\n", "\n", "这个部分演示了四种与原生代码交互的技术。下表概述了这些技术的一些方面。\n", "\n", "|x|Part of CPython|Compiled|Autogenerated|Numpy Support|\n", "|---|---|---|---|---|\n", "|Python-C-API|True|True|False|True|\n", "|Ctypes|True|False|False|True|\n", "|Swig|False|True|True|True|\n", "|Cython|False|True|True|True|\n", "\n", "在上面的技术中,Cython是最现代最高级的。特别是,通过为Python代码添加类型来增量优化代码的技术是惟一的。\n", "\n", "## 2.8.7 Further Reading and References\n", "\n", "[Gaël Varoquaux关于避免数据复制的博客](http://gael-varoquaux.info/blog/?p=157)给出了一些如何精明的处理内存管理的见解。如果你在大数据量时出现问题,可以回到这里寻找一些灵感。\n", "\n", "## 2.8.8 练习\n", "\n", "因为这是一个新部分,练习更像是一个接下来应该查看什么的指示器,因此,看一下那些你觉得更有趣的部分。如果你有关于练习更好点子,请告诉我们!\n", "\n", "- 下载每个例子的源码,并且在你的机器上运行并编译他们。\n", "- 对每个例子做一些修改,并且自己验证一下是否有效。 ( 比如,将cos改为sin。)\n", "- 绝大多数例子,特别是包含了Numpy的例子,可能还是比较脆弱,对输入错误反应较差。找一些方法来让例子崩溃,找出问题所在,并且设计潜在的解决方案。这些是有些点子:\n", " - 数字溢出\n", " - 输入输出数组长度不一致\n", " - 多维度数据\n", " - 空数组\n", " - non-double类型数组\n", " - \n", "- 使用`%timeit`IPython魔法函数来测量不同解决方案的执行时间\n", "\n", "### 2.8.8.1 Python-C-API\n", "\n", "- 修改Numpy例子以便函数有两个输入参数,第二个参数是预分配输出数组,让它与其他的Numpy例子一致。\n", "- 修改这个例子,以便这个函数只有一个输入数组,在原地修改这个函数。\n", "- 试着用新的[Numpy迭代协议](http://docs.scipy.org/doc/numpy/reference/c-api.iterator.html)修改例子。如果你刚好获得了一个可用的解决方案,请将其提交一个请求到github。\n", "- 你可能注意到了,Numpy-C-API例子只是Numpy例子没有封装`cos_doubles`但是直接将`cos`函数应用于Numpy数组的元素上。这样做与其他技术相比有什么优势。\n", "- 你可以只用Numpy-C-API来封装`cos_doubles`。你可能需要确保数组有正确的类型,并且是单维度和内存临近。\n", "\n", "### 2.8.8.2 Ctypes\n", "\n", "- 修改Numpy例子以便`cos_doubles_func`为你处理预分配,让它更像Numpy-C-API例子。\n", "\n", "### 2.8.8.3. SWIG\n", "\n", "- 看一下SWIG自动生成的代码,你能理解多少?\n", "- 修改Numpy例子,以便`cos_doubles_func`为你处理预处理,让它更像Numpy-C-API例子。\n", "- 修改`cos_doubles` C 函数,以便它返回分配的数组。你可以用SWIG typemaps类封装吗? 如果不可以,为什么不可以? 对于这种特殊的情况有没有什么变通方法? (提示: 你知道输出数组的大小, 因此,可以从返回的`double \\*`构建Numpy数组。\n", "\n", "### 2.8.8.4 Cython\n", "\n", "- 看一下Cython自动生成的代码。仔细看一下Cython插入的一些评论。你能看到些什么?\n", "- 看一下Cython文档中[与Numpy工作](http://docs.cython.org/src/tutorial/numpy.html)的部分, 学习一下如何使用Numpy增量优化python脚本。\n", "- 修改Numpy例子,以便`cos_doubles_func`为你处理预处理,让它更像Numpy-C-API例子。" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.11" } }, "nbformat": 4, "nbformat_minor": 0 }