|
101 | 101 | distance.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point))
|
102 | 102 | distance.restype = ctypes.c_double
|
103 | 103 |
|
104 |
| -If all goes well, you should be able to load the module and use the resulting C functions. |
105 |
| -For example: |
106 | 104 | 如果一切正常,你就可以加载并使用里面定义的C函数了。例如:
|
107 | 105 |
|
108 | 106 | ::
|
@@ -154,163 +152,175 @@ For example:
|
154 | 152 | 一旦你知道了C函数库的位置,那么就可以像下面这样使用 ``ctypes.cdll.LoadLibrary()`` 来加载它,
|
155 | 153 | 其中 ``_path`` 是标准库的全路径:
|
156 | 154 |
|
| 155 | +.. code-block:: python |
| 156 | +
|
| 157 | + _mod = ctypes.cdll.LoadLibrary(_path) |
| 158 | +
|
| 159 | +函数库被加载后,你需要编写几个语句来提取特定的符号并指定它们的类型。 |
| 160 | +就像下面这个代码片段一样: |
| 161 | + |
| 162 | +.. code-block:: python |
| 163 | +
|
| 164 | + # int in_mandel(double, double, int) |
| 165 | + in_mandel = _mod.in_mandel |
| 166 | + in_mandel.argtypes = (ctypes.c_double, ctypes.c_double, ctypes.c_int) |
| 167 | + in_mandel.restype = ctypes.c_int |
| 168 | +
|
| 169 | +在这段代码中,``.argtypes`` 属性是一个元组,包含了某个函数的输入按时, |
| 170 | +而 ``.restype`` 就是相应的返回类型。 |
| 171 | +``ctypes`` 定义了大量的类型对象(比如c_double, c_int, c_short, c_float等), |
| 172 | +代表了对应的C数据类型。如果你想让Python能够传递正确的参数类型并且正确的转换数据的话, |
| 173 | +那么这些类型签名的绑定是很重要的一步。如果你没有这么做,不但代码不能正常运行, |
| 174 | +还可能会导致整个解释器进程挂掉。 |
| 175 | +使用ctypes有一个麻烦点的地方是原生的C代码使用的术语可能跟Python不能明确的对应上来。 |
| 176 | +``divide()`` 函数是一个很好的例子,它通过一个参数除以另一个参数返回一个结果值。 |
| 177 | +尽管这是一个很常见的C技术,但是在Python中却不知道怎样清晰的表达出来。 |
| 178 | +例如,你不能像下面这样简单的做: |
| 179 | + |
| 180 | +:: |
| 181 | + |
| 182 | + >>> divide = _mod.divide |
| 183 | + >>> divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int)) |
| 184 | + >>> x = 0 |
| 185 | + >>> divide(10, 3, x) |
| 186 | + Traceback (most recent call last): |
| 187 | + File "<stdin>", line 1, in <module> |
| 188 | + ctypes.ArgumentError: argument 3: <class 'TypeError'>: expected LP_c_int |
| 189 | + instance instead of int |
| 190 | + >>> |
| 191 | + |
| 192 | +就算这个能正确的工作,它会违反Python对于整数的不可更改原则,并且可能会导致整个解释器陷入一个黑洞中。 |
| 193 | +对于涉及到指针的参数,你通常需要先构建一个相应的ctypes对象并像下面这样传进去: |
| 194 | + |
| 195 | +:: |
| 196 | + |
| 197 | + >>> x = ctypes.c_int() |
| 198 | + >>> divide(10, 3, x) |
| 199 | + 3 |
| 200 | + >>> x.value |
| 201 | + 1 |
| 202 | + >>> |
| 203 | + |
| 204 | +在这里,一个 ``ctypes.c_int`` 实例被创建并作为一个指针被传进去。 |
| 205 | +跟普通Python整形不同的是,一个 ``c_int`` 对象是可以被修改的。 |
| 206 | +``.value`` 属性可被用来获取或更改这个值。 |
| 207 | + |
| 208 | +对于那些不像Python的C调用,通常可以写一个小的包装函数。 |
| 209 | +这里,我们让 ``divide()`` 函数通过元组来返回两个结果: |
| 210 | + |
| 211 | +.. code-block:: python |
| 212 | +
|
| 213 | + # int divide(int, int, int *) |
| 214 | + _divide = _mod.divide |
| 215 | + _divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int)) |
| 216 | + _divide.restype = ctypes.c_int |
| 217 | +
|
| 218 | + def divide(x, y): |
| 219 | + rem = ctypes.c_int() |
| 220 | + quot = _divide(x,y,rem) |
| 221 | + return quot, rem.value |
| 222 | +
|
| 223 | +``avg()`` 函数又是一个新的挑战。C代码期望接受到一个指针和一个数组的长度值。 |
| 224 | +但是,在Python中,我们必须考虑这个问题:数组是啥?它是一个列表?一个元组? |
| 225 | +还是 ``array`` 模块中的一个数组?还是一个 ``numpy`` 数组?还是说所有都是? |
| 226 | +实际上,一个Python“数组”有多种形式,你可能想要支持多种可能性。 |
| 227 | + |
| 228 | +``DoubleArrayType`` 演示了怎样处理这种情况。 |
| 229 | +在这个类中定义了一个单个方法 ``from_param()`` 。 |
| 230 | +这个方法的角色是接受一个单个参数然后将其向下转换为一个合适的ctypes对象 |
| 231 | +(本例中是一个 ``ctypes.c_double`` 的指针)。 |
| 232 | +在 ``from_param()`` 中,你可以做任何你想做的事。 |
| 233 | +参数的类型名被提取出来并被用于分发到一个更具体的方法中去。 |
| 234 | +例如,如果一个列表被传递过来,那么 ``typename`` 就是 ``list`` , |
| 235 | +然后 ``from_list`` 方法被调用。 |
| 236 | + |
| 237 | +对于列表和元组,``from_list`` 方法将其转换为一个 ``ctypes`` 的数组对象。 |
| 238 | +这个看上去有点奇怪,下面我们使用一个交互式例子来将一个列表转换为一个 ``ctypes`` 数组: |
| 239 | + |
| 240 | +:: |
| 241 | + |
| 242 | + >>> nums = [1, 2, 3] |
| 243 | + >>> a = (ctypes.c_double * len(nums))(*nums) |
| 244 | + >>> a |
| 245 | + <__main__.c_double_Array_3 object at 0x10069cd40> |
| 246 | + >>> a[0] |
| 247 | + 1.0 |
| 248 | + >>> a[1] |
| 249 | + 2.0 |
| 250 | + >>> a[2] |
| 251 | + 3.0 |
| 252 | + >>> |
| 253 | + |
| 254 | +对于数组对象,``from_array()`` 提取底层的内存指针并将其转换为一个 ``ctypes`` 指针对象。例如: |
| 255 | + |
| 256 | +:: |
| 257 | + |
| 258 | + >>> import array |
| 259 | + >>> a = array.array('d',[1,2,3]) |
| 260 | + >>> a |
| 261 | + array('d', [1.0, 2.0, 3.0]) |
| 262 | + >>> ptr_ = a.buffer_info() |
| 263 | + >>> ptr |
| 264 | + 4298687200 |
| 265 | + >>> ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double)) |
| 266 | + <__main__.LP_c_double object at 0x10069cd40> |
| 267 | + >>> |
| 268 | + |
| 269 | +``from_ndarray()`` 演示了对于 ``numpy`` 数组的转换操作。 |
| 270 | +通过定义 ``DoubleArrayType`` 类并在 ``avg()`` 类型签名中使用它, |
| 271 | +那么这个函数就能接受多个不同的类数组输入了: |
| 272 | + |
| 273 | +:: |
| 274 | + |
| 275 | + >>> import sample |
| 276 | + >>> sample.avg([1,2,3]) |
| 277 | + 2.0 |
| 278 | + >>> sample.avg((1,2,3)) |
| 279 | + 2.0 |
| 280 | + >>> import array |
| 281 | + >>> sample.avg(array.array('d',[1,2,3])) |
| 282 | + 2.0 |
| 283 | + >>> import numpy |
| 284 | + >>> sample.avg(numpy.array([1.0,2.0,3.0])) |
| 285 | + 2.0 |
| 286 | + >>> |
| 287 | + |
| 288 | +本节最后一部分向你演示了怎样处理一个简单的C结构。 |
| 289 | +对于结构体,你只需要像下面这样简单的定义一个类,包含相应的字段和类型即可: |
| 290 | + |
| 291 | +.. code-block:: python |
| 292 | +
|
| 293 | + class Point(ctypes.Structure): |
| 294 | + _fields_ = [('x', ctypes.c_double), |
| 295 | + ('y', ctypes.c_double)] |
| 296 | +
|
| 297 | +一旦类被定义后,你就可以在类型签名中或者是需要实例化结构体的代码中使用它。例如: |
| 298 | + |
| 299 | +:: |
| 300 | + |
| 301 | + >>> p1 = sample.Point(1,2) |
| 302 | + >>> p2 = sample.Point(4,5) |
| 303 | + >>> p1.x |
| 304 | + 1.0 |
| 305 | + >>> p1.y |
| 306 | + 2.0 |
| 307 | + >>> sample.distance(p1,p2) |
| 308 | + 4.242640687119285 |
| 309 | + >>> |
| 310 | + |
| 311 | +最后一些小的提示:如果你想在Python中访问一些小的C函数,那么 ``ctypes`` 是一个很有用的函数库。 |
| 312 | +尽管如此,如果你想要去访问一个很大的库,那么可能就需要其他的方法了,比如 ``Swig`` (15.9节会讲到) 或 |
| 313 | +Cython(15.10节)。 |
| 314 | + |
| 315 | +对于大型库的访问有个主要问题,由于ctypes并不是完全自动化, |
| 316 | +那么你就必须花费大量时间来编写所有的类型签名,就像例子中那样。 |
| 317 | +如果函数库够复杂,你还得去编写很多小的包装函数和支持类。 |
| 318 | +另外,除非你已经完全精通了所有底层的C接口细节,包括内存分配和错误处理机制, |
| 319 | +通常一个很小的代码缺陷、访问越界或其他类似错误就能让Python程序奔溃。 |
| 320 | + |
| 321 | +作为 ``ctypes`` 的一个替代,你还可以考虑下CFFI。CFFI提供了很多类似的功能, |
| 322 | +但是使用C语法并支持更多高级的C代码类型。 |
| 323 | +到写这本书为止,CFFI还是一个相对较新的工程, |
| 324 | +但是它的流行度正在快速上升。 |
| 325 | +甚至还有在讨论在Python将来的版本中将它包含进去。因此,这个真的值得一看。 |
157 | 326 |
|
158 |
| -_mod = ctypes.cdll.LoadLibrary(_path) |
159 |
| - |
160 |
| -Once a library has been loaded, you need to write statements that extract specific sym‐ |
161 |
| -bols and put type signatures on them. This is what’s happening in code fragments such |
162 |
| -as this: |
163 |
| - |
164 |
| -# int in_mandel(double, double, int) |
165 |
| -in_mandel = _mod.in_mandel |
166 |
| -in_mandel.argtypes = (ctypes.c_double, ctypes.c_double, ctypes.c_int) |
167 |
| -in_mandel.restype = ctypes.c_int |
168 |
| - |
169 |
| -In this code, the .argtypes attribute is a tuple containing the input arguments to a |
170 |
| -function, and .restype is the return type. ctypes defines a variety of type objects (e.g., |
171 |
| -c_double, c_int, c_short, c_float, etc.) that represent common C data types. Attach‐ |
172 |
| -ing the type signatures is critical if you want to make Python pass the right kinds of |
173 |
| -arguments and convert data correctly (if you don’t do this, not only will the code not |
174 |
| -work, but you might cause the entire interpreter process to crash). |
175 |
| -A somewhat tricky part of using ctypes is that the original C code may use idioms that |
176 |
| -don’t map cleanly to Python. The divide() function is a good example because it returns |
177 |
| -a value through one of its arguments. Although that’s a common C technique, it’s often |
178 |
| -not clear how it’s supposed to work in Python. For example, you can’t do anything |
179 |
| -straightforward like this: |
180 |
| - |
181 |
| ->>> divide = _mod.divide |
182 |
| ->>> divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int)) |
183 |
| ->>> x = 0 |
184 |
| ->>> divide(10, 3, x) |
185 |
| -Traceback (most recent call last): |
186 |
| - File "<stdin>", line 1, in <module> |
187 |
| -ctypes.ArgumentError: argument 3: <class 'TypeError'>: expected LP_c_int |
188 |
| -instance instead of int |
189 |
| ->>> |
190 |
| - |
191 |
| -Even if this did work, it would violate Python’s immutability of integers and probably |
192 |
| -cause the entire interpreter to be sucked into a black hole. For arguments involving |
193 |
| -pointers, you usually have to construct a compatible ctypes object and pass it in like |
194 |
| -this: |
195 |
| - |
196 |
| ->>> x = ctypes.c_int() |
197 |
| ->>> divide(10, 3, x) |
198 |
| -3 |
199 |
| ->>> x.value |
200 |
| -1 |
201 |
| ->>> |
202 |
| - |
203 |
| -Here an instance of a ctypes.c_int is created and passed in as the pointer object. Unlike |
204 |
| -a normal Python integer, a c_int object can be mutated. The .value attribute can be |
205 |
| -used to either retrieve or change the value as desired. |
206 |
| - |
207 |
| -For cases where the C calling convention is “un-Pythonic,” it is common to write a small |
208 |
| -wrapper function. In the solution, this code makes the divide() function return the |
209 |
| -two results using a tuple instead: |
210 |
| -# int divide(int, int, int *) |
211 |
| -_divide = _mod.divide |
212 |
| -_divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int)) |
213 |
| -_divide.restype = ctypes.c_int |
214 |
| -
|
215 |
| -def divide(x, y): |
216 |
| - rem = ctypes.c_int() |
217 |
| - quot = _divide(x,y,rem) |
218 |
| - return quot, rem.value |
219 |
| - |
220 |
| -The avg() function presents a new kind of challenge. The underlying C code expects |
221 |
| -to receive a pointer and a length representing an array. However, from the Python side, |
222 |
| -we must consider the following questions: What is an array? Is it a list? A tuple? An |
223 |
| -array from the array module? A numpy array? Is it all of these? In practice, a Python |
224 |
| -“array” could take many different forms, and maybe you would like to support multiple |
225 |
| -possibilities. |
226 |
| -The DoubleArrayType class shows how to handle this situation. In this class, a single |
227 |
| -method from_param() is defined. The role of this method is to take a single parameter |
228 |
| -and narrow it down to a compatible ctypes object (a pointer to a ctypes.c_double, in |
229 |
| -the example). Within from_param(), you are free to do anything that you wish. In the |
230 |
| -solution, the typename of the parameter is extracted and used to dispatch to a more |
231 |
| -specialized method. For example, if a list is passed, the typename is list and a method |
232 |
| -from_list() is invoked. |
233 |
| -For lists and tuples, the from_list() method performs a conversion to a ctypes array |
234 |
| -object. This looks a little weird, but here is an interactive example of converting a list to |
235 |
| -a ctypes array: |
236 |
| - |
237 |
| ->>> nums = [1, 2, 3] |
238 |
| ->>> a = (ctypes.c_double * len(nums))(*nums) |
239 |
| ->>> a |
240 |
| -<__main__.c_double_Array_3 object at 0x10069cd40> |
241 |
| ->>> a[0] |
242 |
| -1.0 |
243 |
| ->>> a[1] |
244 |
| -2.0 |
245 |
| ->>> a[2] |
246 |
| -3.0 |
247 |
| ->>> |
248 |
| - |
249 |
| -For array objects, the from_array() method extracts the underlying memory pointer |
250 |
| -and casts it to a ctypes pointer object. For example: |
251 |
| - |
252 |
| ->>> import array |
253 |
| ->>> a = array.array('d',[1,2,3]) |
254 |
| ->>> a |
255 |
| -array('d', [1.0, 2.0, 3.0]) |
256 |
| ->>> ptr_ = a.buffer_info() |
257 |
| ->>> ptr |
258 |
| -4298687200 |
259 |
| ->>> ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double)) |
260 |
| -<__main__.LP_c_double object at 0x10069cd40> |
261 |
| ->>> |
262 |
| - |
263 |
| -The from_ndarray() shows comparable conversion code for numpy arrays. |
264 |
| -By defining the DoubleArrayType class and using it in the type signature of avg(), as |
265 |
| -shown, the function can accept a variety of different array-like inputs: |
266 |
| - |
267 |
| ->>> import sample |
268 |
| ->>> sample.avg([1,2,3]) |
269 |
| -2.0 |
270 |
| ->>> sample.avg((1,2,3)) |
271 |
| -2.0 |
272 |
| ->>> import array |
273 |
| ->>> sample.avg(array.array('d',[1,2,3])) |
274 |
| -2.0 |
275 |
| ->>> import numpy |
276 |
| ->>> sample.avg(numpy.array([1.0,2.0,3.0])) |
277 |
| -2.0 |
278 |
| ->>> |
279 |
| - |
280 |
| -The last part of this recipe shows how to work with a simple C structure. For structures, |
281 |
| -you simply define a class that contains the appropriate fields and types like this: |
282 |
| - |
283 |
| -class Point(ctypes.Structure): |
284 |
| - _fields_ = [('x', ctypes.c_double), |
285 |
| - ('y', ctypes.c_double)] |
286 |
| - |
287 |
| -Once defined, you can use the class in type signatures as well as in code that needs to |
288 |
| -instantiate and work with the structures. For example: |
289 |
| - |
290 |
| ->>> p1 = sample.Point(1,2) |
291 |
| ->>> p2 = sample.Point(4,5) |
292 |
| ->>> p1.x |
293 |
| -1.0 |
294 |
| ->>> p1.y |
295 |
| -2.0 |
296 |
| ->>> sample.distance(p1,p2) |
297 |
| -4.242640687119285 |
298 |
| ->>> |
299 |
| - |
300 |
| -A few final comments: ctypes is a useful library to know about if all you’re doing is |
301 |
| -accessing a few C functions from Python. However, if you’re trying to access a large |
302 |
| -library, you might want to look at alternative approaches, such as Swig (described in |
303 |
| -Recipe 15.9) or Cython (described in Recipe 15.10). |
304 |
| - |
305 |
| -The main problem with a large library is that since ctypes isn’t entirely automatic, you’ll |
306 |
| -have to spend a fair bit of time writing out all of the type signatures, as shown in the |
307 |
| -example. Depending on the complexity of the library, you might also have to write a |
308 |
| -large number of small wrapper functions and supporting classes. Also, unless you fully |
309 |
| -understand all of the low-level details of the C interface, including memory management |
310 |
| -and error handling, it is often quite easy to make Python catastrophically crash with a |
311 |
| -segmentation fault, access violation, or some similar error. |
312 |
| -As an alternative to ctypes, you might also look at CFFI. CFFI provides much of the |
313 |
| -same functionality, but uses C syntax and supports more advanced kinds of C code. As |
314 |
| -of this writing, CFFI is still a relatively new project, but its use has been growing rapidly. |
315 |
| -There has even been some discussion of including it in the Python standard library in |
316 |
| -some future release. Thus, it’s definitely something to keep an eye on. |
|
0 commit comments