|
5 | 5 | ----------
|
6 | 6 | 问题
|
7 | 7 | ----------
|
8 |
| -You want to write a simple C extension module directly using Python’s extension API |
9 |
| -and no other tools. |
| 8 | +你想不依靠其他工具,直接使用Python的扩展API来编写一些简单的C扩展模块。 |
10 | 9 |
|
11 | 10 | |
|
12 | 11 |
|
13 | 12 | ----------
|
14 | 13 | 解决方案
|
15 | 14 | ----------
|
16 |
| -For simple C code, it is straightforward to make a handcrafted extension module. As a |
17 |
| -preliminary step, you probably want to make sure your C code has a proper header file. |
18 |
| -For example, |
19 |
| - |
20 |
| -/* sample.h */ |
21 |
| -
|
22 |
| -#include <math.h> |
23 |
| - |
24 |
| -extern int gcd(int, int); |
25 |
| -extern int in_mandel(double x0, double y0, int n); |
26 |
| -extern int divide(int a, int b, int *remainder); |
27 |
| -extern double avg(double *a, int n); |
28 |
| -
|
29 |
| -typedef struct Point { |
30 |
| - double x,y; |
31 |
| -} Point; |
32 |
| - |
33 |
| -extern double distance(Point *p1, Point *p2); |
34 |
| -
|
35 |
| -Typically, this header would correspond to a library that has been compiled separately. |
36 |
| -With that assumption, here is a sample extension module that illustrates the basics of |
37 |
| -writing extension functions: |
38 |
| - |
39 |
| -#include "Python.h" |
40 |
| -#include "sample.h" |
41 |
| - |
42 |
| -/* int gcd(int, int) */ |
43 |
| -static PyObject *py_gcd(PyObject *self, PyObject *args) { |
44 |
| - int x, y, result; |
45 |
| -
|
46 |
| - if (!PyArg_ParseTuple(args,"ii", &x, &y)) { |
47 |
| - return NULL; |
48 |
| - } |
49 |
| - result = gcd(x,y); |
50 |
| - return Py_BuildValue("i", result); |
51 |
| -} |
52 |
| - |
53 |
| -/* int in_mandel(double, double, int) */ |
54 |
| -static PyObject *py_in_mandel(PyObject *self, PyObject *args) { |
55 |
| - double x0, y0; |
56 |
| - int n; |
57 |
| - int result; |
58 |
| -
|
59 |
| - if (!PyArg_ParseTuple(args, "ddi", &x0, &y0, &n)) { |
60 |
| - return NULL; |
61 |
| - } |
62 |
| - result = in_mandel(x0,y0,n); |
63 |
| - return Py_BuildValue("i", result); |
64 |
| -} |
65 |
| - |
66 |
| -/* int divide(int, int, int *) */ |
67 |
| -static PyObject *py_divide(PyObject *self, PyObject *args) { |
68 |
| - int a, b, quotient, remainder; |
69 |
| - if (!PyArg_ParseTuple(args, "ii", &a, &b)) { |
70 |
| - return NULL; |
71 |
| - } |
72 |
| - quotient = divide(a,b, &remainder); |
73 |
| - return Py_BuildValue("(ii)", quotient, remainder); |
74 |
| -} |
75 |
| -
|
76 |
| -/* Module method table */ |
77 |
| -static PyMethodDef SampleMethods[] = { |
78 |
| - {"gcd", py_gcd, METH_VARARGS, "Greatest common divisor"}, |
79 |
| - {"in_mandel", py_in_mandel, METH_VARARGS, "Mandelbrot test"}, |
80 |
| - {"divide", py_divide, METH_VARARGS, "Integer division"}, |
81 |
| - { NULL, NULL, 0, NULL} |
82 |
| -}; |
83 |
| -
|
84 |
| -/* Module structure */ |
85 |
| -static struct PyModuleDef samplemodule = { |
86 |
| - PyModuleDef_HEAD_INIT, |
87 |
| -
|
88 |
| - "sample", /* name of module */ |
89 |
| - "A sample module", /* Doc string (may be NULL) */ |
90 |
| - -1, /* Size of per-interpreter state or -1 */ |
91 |
| - SampleMethods /* Method table */ |
92 |
| -}; |
93 |
| -
|
94 |
| -/* Module initialization function */ |
95 |
| -PyMODINIT_FUNC |
96 |
| -PyInit_sample(void) { |
97 |
| - return PyModule_Create(&samplemodule); |
98 |
| -} |
99 |
| -
|
100 |
| -For building the extension module, create a setup.py file that looks like this: |
101 |
| - |
102 |
| -# setup.py |
103 |
| -from distutils.core import setup, Extension |
104 |
| - |
105 |
| -setup(name='sample', |
106 |
| - ext_modules=[ |
107 |
| - Extension('sample', |
108 |
| - ['pysample.c'], |
109 |
| - include_dirs = ['/some/dir'], |
110 |
| - define_macros = [('FOO','1')], |
111 |
| - undef_macros = ['BAR'], |
112 |
| - library_dirs = ['/usr/local/lib'], |
113 |
| - libraries = ['sample'] |
114 |
| - ) |
115 |
| - ] |
116 |
| -) |
117 |
| - |
118 |
| -Now, to build the resulting library, simply use python3 buildlib.py build_ext -- |
119 |
| -inplace. For example: |
120 |
| - |
121 |
| -bash % python3 setup.py build_ext --inplace |
122 |
| -running build_ext |
123 |
| -building 'sample' extension |
124 |
| -gcc -fno-strict-aliasing -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes |
125 |
| - -I/usr/local/include/python3.3m -c pysample.c |
126 |
| - -o build/temp.macosx-10.6-x86_64-3.3/pysample.o |
127 |
| -gcc -bundle -undefined dynamic_lookup |
128 |
| -build/temp.macosx-10.6-x86_64-3.3/pysample.o \ |
129 |
| - -L/usr/local/lib -lsample -o sample.so |
130 |
| -bash % |
131 |
| - |
132 |
| -As shown, this creates a shared library called sample.so. When compiled, you should |
133 |
| -be able to start importing it as a module: |
134 |
| - |
135 |
| ->>> import sample |
136 |
| ->>> sample.gcd(35, 42) |
137 |
| -7 |
138 |
| ->>> sample.in_mandel(0, 0, 500) |
139 |
| -1 |
140 |
| ->>> sample.in_mandel(2.0, 1.0, 500) |
141 |
| - |
142 |
| -0 |
143 |
| ->>> sample.divide(42, 8) |
144 |
| -(5, 2) |
145 |
| ->>> |
146 |
| - |
147 |
| -If you are attempting these steps on Windows, you may need to spend some time fiddling |
148 |
| -with your environment and the build environment to get extension modules to build |
149 |
| -correctly. Binary distributions of Python are typically built using Microsoft Visual |
150 |
| -Studio. To get extensions to work, you may have to compile them using the same or |
151 |
| -compatible tools. See the Python documentation. |
| 15 | +对于简单的C代码,构建一个自定义扩展模块是很容易的。 |
| 16 | +作为第一步,你需要确保你的C代码有一个正确的头文件。例如: |
| 17 | + |
| 18 | +:: |
| 19 | + |
| 20 | + /* sample.h */ |
| 21 | + |
| 22 | + #include <math.h> |
| 23 | + |
| 24 | + extern int gcd(int, int); |
| 25 | + extern int in_mandel(double x0, double y0, int n); |
| 26 | + extern int divide(int a, int b, int *remainder); |
| 27 | + extern double avg(double *a, int n); |
| 28 | + |
| 29 | + typedef struct Point { |
| 30 | + double x,y; |
| 31 | + } Point; |
| 32 | + |
| 33 | + extern double distance(Point *p1, Point *p2); |
| 34 | + |
| 35 | +通常来讲,这个头文件要对应一个已经被单独编译过的库。 |
| 36 | +有了这些,下面我们演示下编写扩展函数的一个简单例子: |
| 37 | + |
| 38 | +:: |
| 39 | + |
| 40 | + #include "Python.h" |
| 41 | + #include "sample.h" |
| 42 | + |
| 43 | + /* int gcd(int, int) */ |
| 44 | + static PyObject *py_gcd(PyObject *self, PyObject *args) { |
| 45 | + int x, y, result; |
| 46 | + |
| 47 | + if (!PyArg_ParseTuple(args,"ii", &x, &y)) { |
| 48 | + return NULL; |
| 49 | + } |
| 50 | + result = gcd(x,y); |
| 51 | + return Py_BuildValue("i", result); |
| 52 | + } |
| 53 | + |
| 54 | + /* int in_mandel(double, double, int) */ |
| 55 | + static PyObject *py_in_mandel(PyObject *self, PyObject *args) { |
| 56 | + double x0, y0; |
| 57 | + int n; |
| 58 | + int result; |
| 59 | + |
| 60 | + if (!PyArg_ParseTuple(args, "ddi", &x0, &y0, &n)) { |
| 61 | + return NULL; |
| 62 | + } |
| 63 | + result = in_mandel(x0,y0,n); |
| 64 | + return Py_BuildValue("i", result); |
| 65 | + } |
| 66 | + |
| 67 | + /* int divide(int, int, int *) */ |
| 68 | + static PyObject *py_divide(PyObject *self, PyObject *args) { |
| 69 | + int a, b, quotient, remainder; |
| 70 | + if (!PyArg_ParseTuple(args, "ii", &a, &b)) { |
| 71 | + return NULL; |
| 72 | + } |
| 73 | + quotient = divide(a,b, &remainder); |
| 74 | + return Py_BuildValue("(ii)", quotient, remainder); |
| 75 | + } |
| 76 | + |
| 77 | + /* Module method table */ |
| 78 | + static PyMethodDef SampleMethods[] = { |
| 79 | + {"gcd", py_gcd, METH_VARARGS, "Greatest common divisor"}, |
| 80 | + {"in_mandel", py_in_mandel, METH_VARARGS, "Mandelbrot test"}, |
| 81 | + {"divide", py_divide, METH_VARARGS, "Integer division"}, |
| 82 | + { NULL, NULL, 0, NULL} |
| 83 | + }; |
| 84 | + |
| 85 | + /* Module structure */ |
| 86 | + static struct PyModuleDef samplemodule = { |
| 87 | + PyModuleDef_HEAD_INIT, |
| 88 | + |
| 89 | + "sample", /* name of module */ |
| 90 | + "A sample module", /* Doc string (may be NULL) */ |
| 91 | + -1, /* Size of per-interpreter state or -1 */ |
| 92 | + SampleMethods /* Method table */ |
| 93 | + }; |
| 94 | + |
| 95 | + /* Module initialization function */ |
| 96 | + PyMODINIT_FUNC |
| 97 | + PyInit_sample(void) { |
| 98 | + return PyModule_Create(&samplemodule); |
| 99 | + } |
| 100 | + |
| 101 | +要绑定这个扩展模块,像下面这样创建一个 ``setup.py`` 文件: |
| 102 | + |
| 103 | +.. code-block:: python |
| 104 | +
|
| 105 | + # setup.py |
| 106 | + from distutils.core import setup, Extension |
| 107 | +
|
| 108 | + setup(name='sample', |
| 109 | + ext_modules=[ |
| 110 | + Extension('sample', |
| 111 | + ['pysample.c'], |
| 112 | + include_dirs = ['/some/dir'], |
| 113 | + define_macros = [('FOO','1')], |
| 114 | + undef_macros = ['BAR'], |
| 115 | + library_dirs = ['/usr/local/lib'], |
| 116 | + libraries = ['sample'] |
| 117 | + ) |
| 118 | + ] |
| 119 | + ) |
| 120 | +
|
| 121 | +为了构建最终的函数库,只需简单的使用 ``python3 buildlib.py build_ext --inplace`` 命令即可: |
| 122 | + |
| 123 | +:: |
| 124 | + |
| 125 | + bash % python3 setup.py build_ext --inplace |
| 126 | + running build_ext |
| 127 | + building 'sample' extension |
| 128 | + gcc -fno-strict-aliasing -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes |
| 129 | + -I/usr/local/include/python3.3m -c pysample.c |
| 130 | + -o build/temp.macosx-10.6-x86_64-3.3/pysample.o |
| 131 | + gcc -bundle -undefined dynamic_lookup |
| 132 | + build/temp.macosx-10.6-x86_64-3.3/pysample.o \ |
| 133 | + -L/usr/local/lib -lsample -o sample.so |
| 134 | + bash % |
| 135 | + |
| 136 | +如上所示,它会创建一个名字叫 ``sample.so`` 的共享库。当被编译后,你就能将它作为一个模块导入进来了: |
| 137 | + |
| 138 | +:: |
| 139 | + |
| 140 | + >>> import sample |
| 141 | + >>> sample.gcd(35, 42) |
| 142 | + 7 |
| 143 | + >>> sample.in_mandel(0, 0, 500) |
| 144 | + 1 |
| 145 | + >>> sample.in_mandel(2.0, 1.0, 500) |
| 146 | + |
| 147 | + 0 |
| 148 | + >>> sample.divide(42, 8) |
| 149 | + (5, 2) |
| 150 | + >>> |
| 151 | + |
| 152 | +如果你是在Windows机器上面尝试这些步骤,可能会遇到各种环境和编译问题,你需要花更多点时间去配置。 |
| 153 | +Python的二进制分发通常使用了Microsoft Visual Studio来构建。 |
| 154 | +为了让这些扩展能正常工作,你需要使用同样或兼容的工具来编译它。 |
| 155 | +参考相应的 `Python文档 <https://docs.python.org/3/extending/windows.html>`_ |
152 | 156 |
|
153 | 157 | |
|
154 | 158 |
|
155 | 159 | ----------
|
156 | 160 | 讨论
|
157 | 161 | ----------
|
158 |
| -Before attempting any kind of handwritten extension, it is absolutely critical that you |
159 |
| -consult Python’s documentation on “Extending and Embedding the Python Interpret‐ |
160 |
| -er”. Python’s C extension API is large, and repeating all of it here is simply not practical. |
161 |
| -However, the most important parts can be easily discussed. |
162 |
| -First, in extension modules, functions that you write are all typically written with a |
163 |
| -common prototype such as this: |
164 |
| - |
165 |
| -static PyObject *py_func(PyObject *self, PyObject *args) { |
166 |
| - ... |
167 |
| -} |
168 |
| -
|
169 |
| -PyObject is the C data type that represents any Python object. At a very high level, an |
170 |
| -extension function is a C function that receives a tuple of Python objects (in PyObject |
171 |
| -*args) and returns a new Python object as a result. The self argument to the function |
172 |
| -is unused for simple extension functions, but comes into play should you want to define |
173 |
| -new classes or object types in C (e.g., if the extension function were a method of a class, |
174 |
| -then self would hold the instance). |
175 |
| -The PyArg_ParseTuple() function is used to convert values from Python to a C rep‐ |
176 |
| -resentation. As input, it takes a format string that indicates the required values, such as |
177 |
| -“i” for integer and “d” for double, as well as the addresses of C variables in which to place |
178 |
| -the converted results. PyArg_ParseTuple() performs a variety of checks on the number |
179 |
| -and type of arguments. If there is any mismatch with the format string, an exception is |
180 |
| -raised and NULL is returned. By checking for this and simply returning NULL, an ap‐ |
181 |
| -propriate exception will have been raised in the calling code. |
182 |
| -The Py_BuildValue() function is used to create Python objects from C data types. It |
183 |
| -also accepts a format code to indicate the desired type. In the extension functions, it is |
184 |
| -used to return results back to Python. One feature of Py_BuildValue() is that it can |
185 |
| -build more complicated kinds of objects, such as tuples and dictionaries. In the code |
186 |
| -for py_divide(), an example showing the return of a tuple is shown. However, here are |
187 |
| -a few more examples: |
188 |
| -
|
189 |
| -return Py_BuildValue("i", 34); // Return an integer |
190 |
| -return Py_BuildValue("d", 3.4); // Return a double |
191 |
| -return Py_BuildValue("s", "Hello"); // Null-terminated UTF-8 string |
192 |
| -return Py_BuildValue("(ii)", 3, 4); // Tuple (3, 4) |
193 |
| - |
194 |
| -Near the bottom of any extension module, you will find a function table such as the |
195 |
| -SampleMethods table shown in this recipe. This table lists C functions, the names to use |
196 |
| -in Python, as well as doc strings. All modules are required to specify such a table, as it |
197 |
| -gets used in the initialization of the module. |
198 |
| -The final function PyInit_sample() is the module initialization function that executes |
199 |
| -when the module is first imported. The primary job of this function is to register the |
200 |
| -module object with the interpreter. |
201 |
| -As a final note, it must be stressed that there is considerably more to extending Python |
202 |
| -with C functions than what is shown here (in fact, the C API contains well over 500 |
203 |
| -functions in it). You should view this recipe simply as a stepping stone for getting started. |
204 |
| -To do more, start with the documentation on the PyArg_ParseTuple() and Py_Build |
205 |
| -Value() functions, and expand from there. |
| 162 | +在尝试任何手写扩展之前,最好能先参考下Python文档中的 |
| 163 | +`扩展和嵌入Python解释器 <https://docs.python.org/3/extending/index.html>`_ . |
| 164 | +Python的C扩展API很大,在这里整个去讲述它没什么实际意义。 |
| 165 | +不过对于最核心的部分还是可以讨论下的。 |
| 166 | + |
| 167 | +首先,在扩展模块中,你写的函数都是像下面这样的一个普通原型: |
| 168 | + |
| 169 | +:: |
| 170 | + |
| 171 | + static PyObject *py_func(PyObject *self, PyObject *args) { |
| 172 | + ... |
| 173 | + } |
| 174 | + |
| 175 | +``PyObject`` 是一个能表示任何Python对象的C数据类型。 |
| 176 | +在一个高级层面,一个扩展函数就是一个接受一个Python对象 |
| 177 | +(在 PyObject *args中)元组并返回一个新Python对象的C函数。 |
| 178 | +函数的 ``self`` 参数对于简单的扩展函数没有被使用到, |
| 179 | +不过如果你想定义新的类或者是C中的对象类型的话就能派上用场了。比如如果扩展函数是一个类的一个方法, |
| 180 | +那么 ``self`` 就能引用那个实例了。 |
| 181 | +
|
| 182 | +``PyArg_ParseTuple()`` 函数被用来将Python中的值转换成C中对应表示。 |
| 183 | +它接受一个指定输入格式的格式化字符串作为输入,比如“i”代表整数,“d”代表双精度浮点数, |
| 184 | +同样还有存放转换后结果的C变量的地址。 |
| 185 | +如果输入的值不匹配这个格式化字符串,就会抛出一个异常并返回一个NULL值。 |
| 186 | +通过检查并返回NULL,一个合适的异常会在调用代码中被抛出。 |
| 187 | + |
| 188 | +``Py_BuildValue()`` 函数被用来根据C数据类型创建Python对象。 |
| 189 | +它同样接受一个格式化字符串来指定期望类型。 |
| 190 | +在扩展函数中,它被用来返回结果给Python。 |
| 191 | +``Py_BuildValue()`` 的一个特性是它能构建更加复杂的对象类型,比如元组和字典。 |
| 192 | +在 ``py_divide()`` 代码中,一个例子演示了怎样返回一个元组。不过,下面还有一些实例: |
| 193 | + |
| 194 | +:: |
| 195 | + |
| 196 | + return Py_BuildValue("i", 34); // Return an integer |
| 197 | + return Py_BuildValue("d", 3.4); // Return a double |
| 198 | + return Py_BuildValue("s", "Hello"); // Null-terminated UTF-8 string |
| 199 | + return Py_BuildValue("(ii)", 3, 4); // Tuple (3, 4) |
| 200 | + |
| 201 | +在扩展模块底部,你会发现一个函数表,比如本节中的 ``SampleMethods`` 表。 |
| 202 | +这个表可以列出C函数、Python中使用的名字、文档字符串。 |
| 203 | +所有模块都需要指定这个表,因为它在模块初始化时要被使用到。 |
| 204 | + |
| 205 | +最后的函数 ``PyInit_sample()`` 是模块初始化函数,但该模块第一次被导入时执行。 |
| 206 | +这个函数的主要工作是在解释器中注册模块对象。 |
| 207 | + |
| 208 | +最后一个要点需要提出来,使用C函数来扩展Python要考虑的事情还有很多,本节只是一小部分。 |
| 209 | +(实际上,C API包含了超过500个函数)。你应该将本节当做是一个入门篇。 |
| 210 | +更多高级内容,可以看看 ``PyArg_ParseTuple()`` 和 ``Py_BuildValue()`` 函数的文档, |
| 211 | +然后进一步扩展开。 |
0 commit comments