{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Python 扩展模块" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 简介" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "C Library | Interface | Python\n", "---|---|---\n", "`c header`
`c implementation` | Wrapper `C` $\\leftrightarrows$ `Python`
communication between `py + c` | `import fact`
`fact.fact(10)`\n", "\n", "**Python** 扩展模块将 `PyInt(10)` 转化为 `CInt(10)` 然后调用 `C` 程序中的 `fact()` 函数进行计算,再将返回的结果转换回 `PyInt`。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 产生一个扩展模块" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "假设我们有这样的一个头文件和程序:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing fact.h\n" ] } ], "source": [ "%%file fact.h\n", "#ifndef FACT_H\n", "#define FACT_h\n", "int fact(int n);\n", "#endif" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing fact.c\n" ] } ], "source": [ "%%file fact.c\n", "#include \"fact.h\"\n", "int fact(int n)\n", "{\n", " if (n <= 1) return 1;\n", " else return n * fact(n - 1);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "定义包装函数:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing fact_wrap.c\n" ] } ], "source": [ "%%file fact_wrap.c\n", "\n", "/* Must include Python.h before any standard headers*/\n", "#include \n", "#include \"fact.h\"\n", "static PyObject* wrap_fact(PyObject *self, PyObject *args)\n", "{\n", " /* Python->C data conversion */\n", " int n, result;\n", " // the string i here means there is only one integer\n", " if (!PyArg_ParseTuple(args, \"i\", &n))\n", " return NULL;\n", " \n", " /* C Function Call */\n", " result = fact(n);\n", " \n", " /* C->Python data conversion */\n", " return Py_BuildValue(\"i\", result);\n", "}\n", "\n", "/* Method table declaring the names of functions exposed to Python*/\n", "static PyMethodDef ExampleMethods[] = {\n", " {\"fact\", wrap_fact, METH_VARARGS, \"Calculate the factorial of n\"},\n", " {NULL, NULL, 0, NULL} /* Sentinel */\n", "};\n", "\n", "/* Module initialization function called at \"import example\"*/\n", "PyMODINIT_FUNC \n", "initexample(void)\n", "{\n", " (void) Py_InitModule(\"example\", ExampleMethods);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 手动编译扩展模块" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "手动使用 `gcc` 编译,`Windows` 下如果没有 `gcc`,可以通过 `conda` 进行安装:\n", "\n", " conda install mingw4\n", " \n", "`Window 64-bit` 下编译需要加上 `-DMS_WIN64` 的选项,`include` 和 `lib` 文件夹的路径对应于本地 **Python** 安装的环境:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [], "source": [ "!gcc -DMS_WIN64 -c fact.c fact_wrap.c -IC:\\Miniconda\\include" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [], "source": [ "!gcc -DMS_WIN64 -shared fact.o fact_wrap.o -LC:\\Miniconda\\libs -lpython27 -o example.pyd" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`Windows` 下最终生成的文件后缀为 `.pyd` , `Unix` 下生成的文件后缀名为 `.so`。\n", "\n", "用法为:\n", "\n", "- `Windows 32-bit`\n", "```\n", "gcc -c fact.c fact_wrap.c -I\\include\n", "gcc -shared fact.o fact_wrap.o -L\\libs -lpython27 -o example.pyd\n", "```\n", "- `Unix`\n", "```\n", "gcc -c fact.c fact_wrap.c -I\n", "gcc -shared fact.o fact_wrap.o -L\\config -lpython27 -o example.so\n", "```\n", "\n", "编译完成后,我们就可以使用 `example` 这个模块了。\n", "\n", "导入生成的包:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['__doc__', '__file__', '__name__', '__package__', 'fact']\n" ] } ], "source": [ "import example\n", "print dir(example)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "使用 `example` 中的函数:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "factorial of 10: 3628800\n" ] } ], "source": [ "print 'factorial of 10:', example.fact(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 使用 setup.py 进行编译" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "清理刚才生成的文件:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": true }, "outputs": [], "source": [ "!rm -f example.pyd" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "写入 `setup.py`:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing setup.py\n" ] } ], "source": [ "%%file setup.py\n", "from distutils.core import setup, Extension\n", "\n", "ext = Extension(name='example', sources=['fact_wrap.c', 'fact.c'])\n", "\n", "setup(name='example', ext_modules=[ext])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "使用 `distutils` 中的函数,我们进行 `build` 和 `install`:\n", "\n", " python setup.py build (--compiler=mingw64)\n", " python setup.py install\n", "\n", "括号中的内容在 `windows` 中可能需要加上。\n", "\n", "这里我们使用 `build_ext --inplace` 选项将其安装在本地文件夹:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "running build_ext\n", "building 'example' extension\n", "creating build\n", "creating build\\temp.win-amd64-2.7\n", "creating build\\temp.win-amd64-2.7\\Release\n", "C:\\Miniconda\\Scripts\\gcc.bat -DMS_WIN64 -mdll -O -Wall -IC:\\Miniconda\\include -IC:\\Miniconda\\PC -c fact_wrap.c -o build\\temp.win-amd64-2.7\\Release\\fact_wrap.o\n", "C:\\Miniconda\\Scripts\\gcc.bat -DMS_WIN64 -mdll -O -Wall -IC:\\Miniconda\\include -IC:\\Miniconda\\PC -c fact.c -o build\\temp.win-amd64-2.7\\Release\\fact.o\n", "writing build\\temp.win-amd64-2.7\\Release\\example.def\n", "C:\\Miniconda\\Scripts\\gcc.bat -DMS_WIN64 -shared -s build\\temp.win-amd64-2.7\\Release\\fact_wrap.o build\\temp.win-amd64-2.7\\Release\\fact.o build\\temp.win-amd64-2.7\\Release\\example.def -LC:\\Miniconda\\libs -LC:\\Miniconda\\PCbuild\\amd64 -lpython27 -lmsvcr90 -o \"C:\\Users\\Jin\\Documents\\Git\\python-tutorial\\07. interfacing with other languages\\example.pyd\"\n" ] } ], "source": [ "!python setup.py build_ext --inplace" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 使用编译的模块" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "进行测试:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "factorial of 10: 3628800\n" ] } ], "source": [ "import example\n", "\n", "print 'factorial of 10:', example.fact(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "定义 `Python` 函数:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3628800\n", "3628800\n" ] } ], "source": [ "def pyfact(n):\n", " if n <= 1: return 1\n", " return n * pyfact(n-1)\n", "\n", "print pyfact(10)\n", "print example.fact(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "时间测试:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The slowest run took 13.17 times longer than the fastest. This could mean that an intermediate result is being cached \n", "1000000 loops, best of 3: 213 ns per loop\n" ] } ], "source": [ "%timeit example.fact(10)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1000000 loops, best of 3: 1.43 µs per loop\n" ] } ], "source": [ "%timeit pyfact(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "如果使用 `fact` 计算比较大的值: " ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "example.fact(100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "会出现溢出的结果,因为 `int` 表示的值有限,但是 `pyfact` 不会有这样的问题:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pyfact(100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "将生成的文件压缩到压缩文件中:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import zipfile\n", "\n", "f = zipfile.ZipFile('07-02-example.zip','w',zipfile.ZIP_DEFLATED)\n", "\n", "names = 'fact.o fact_wrap.c fact_wrap.o example.pyd setup.py'.split()\n", "for name in names:\n", " f.write(name)\n", "\n", "f.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "清理生成的文件:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [], "source": [ "!rm -f fact*.*\n", "!rm -f example.*\n", "!rm -f setup*.*\n", "!rm -rf build" ] } ], "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.9" } }, "nbformat": 4, "nbformat_minor": 0 }