Skip to content

Commit ee46128

Browse files
committed
15.10小节完成
1 parent 483fca3 commit ee46128

File tree

1 file changed

+134
-132
lines changed

1 file changed

+134
-132
lines changed

source/c15/p10_wrap_existing_c_code_with_cython.rst

+134-132
Original file line numberDiff line numberDiff line change
@@ -208,135 +208,137 @@ Cython会生成包装代码来正确的转换参数和返回值。
208208
raise ValueError("y must be > 0")
209209
return csample.gcd(x,y)
210210

211-
The declaration of in_mandel() in the csample.pxd file has an interesting, but subtle
212-
definition. In that file, the function is declared as returning a bint instead of an int.
213-
This causes the function to create a proper Boolean value from the result instead of a
214-
simple integer. So, a return value of 0 gets mapped to False and 1 to True.
215-
``in_mandel()`` 的声明
216-
217-
Within the Cython wrappers, you have the option of declaring C data types in addition
218-
to using all of the usual Python objects. The wrapper for divide() shows an example
219-
of this as well as how to handle a pointer argument.
220-
221-
def divide(x,y):
222-
cdef int rem
223-
quot = csample.divide(x,y,&rem)
224-
return quot, rem
225-
226-
Here, the rem variable is explicitly declared as a C int variable. When passed to the
227-
underlying divide() function, &rem makes a pointer to it just as in C.
228-
The code for the avg() function illustrates some more advanced features of Cython.
229-
First the declaration def avg(double[:] a) declares avg() as taking a one-dimensional
230-
memoryview of double values. The amazing part about this is that the resulting function
231-
will accept any compatible array object, including those created by libraries such as
232-
numpy. For example:
233-
>>> import array
234-
>>> a = array.array('d',[1,2,3])
235-
>>> import numpy
236-
>>> b = numpy.array([1., 2., 3.])
237-
>>> import sample
238-
>>> sample.avg(a)
239-
2.0
240-
>>> sample.avg(b)
241-
2.0
242-
>>>
243-
244-
In the wrapper, a.size and &a[0] refer to the number of array items and underlying
245-
pointer, respectively. The syntax <double *> &a[0] is how you type cast pointers to a
246-
different type if necessary. This is needed to make sure the C avg() receives a pointer
247-
of the correct type. Refer to the next recipe for some more advanced usage of Cython
248-
memoryviews.
249-
In addition to working with general arrays, the avg() example also shows how to work
250-
with the global interpreter lock. The statement with nogil: declares a block of code as
251-
executing without the GIL. Inside this block, it is illegal to work with any kind of normal
252-
Python object—only objects and functions declared as cdef can be used. In addition to
253-
that, external functions must explicitly declare that they can execute without the GIL.
254-
Thus, in the csample.pxd file, the avg() is declared as double avg(double *, int)
255-
nogil.
256-
The handling of the Point structure presents a special challenge. As shown, this recipe
257-
treats Point objects as opaque pointers using capsule objects, as described in
258-
Recipe 15.4. However, to do this, the underlying Cython code is a bit more complicated.
259-
First, the following imports are being used to bring in definitions of functions from the
260-
C library and Python C API:
261-
262-
from cpython.pycapsule cimport *
263-
from libc.stdlib cimport malloc, free
264-
265-
The function del_Point() and Point() use this functionality to create a capsule object
266-
that wraps around a Point * pointer. The declaration cdef del_Point() declares
267-
del_Point() as a function that is only accessible from Cython and not Python. Thus,
268-
this function will not be visible to the outside—instead, it’s used as a callback function
269-
to clean up memory allocated by the capsule. Calls to functions such as PyCap
270-
sule_New(), PyCapsule_GetPointer() are directly from the Python C API and are used
271-
in the same way.
272-
The distance() function has been written to extract pointers from the capsule objects
273-
created by Point(). One notable thing here is that you simply don’t have to worry about
274-
exception handling. If a bad object is passed, PyCapsule_GetPointer() raises an ex‐
275-
ception, but Cython already knows to look for it and propagate it out of the dis
276-
tance() function if it occurs.
277-
A downside to the handling of Point structures is that they will be completely opaque
278-
in this implementation. You won’t be able to peek inside or access any of their attributes.
279-
There is an alternative approach to wrapping, which is to define an extension type, as
280-
shown in this code:
281-
282-
# sample.pyx
283-
284-
cimport csample
285-
from libc.stdlib cimport malloc, free
286-
...
287-
288-
cdef class Point:
289-
cdef csample.Point *_c_point
290-
def __cinit__(self, double x, double y):
291-
self._c_point = <csample.Point *> malloc(sizeof(csample.Point))
292-
self._c_point.x = x
293-
self._c_point.y = y
294-
295-
def __dealloc__(self):
296-
free(self._c_point)
297-
298-
property x:
299-
def __get__(self):
300-
return self._c_point.x
301-
def __set__(self, value):
302-
self._c_point.x = value
303-
304-
property y:
305-
def __get__(self):
306-
return self._c_point.y
307-
def __set__(self, value):
308-
self._c_point.y = value
309-
310-
def distance(Point p1, Point p2):
311-
return csample.distance(p1._c_point, p2._c_point)
312-
313-
Here, the cdef class Point is declaring Point as an extension type. The class variable
314-
cdef csample.Point *_c_point is declaring an instance variable that holds a pointer
315-
to an underlying Point structure in C. The __cinit__() and __dealloc__() methods
316-
create and destroy the underlying C structure using malloc() and free() calls. The
317-
property x and property y declarations give code that gets and sets the underlying
318-
structure attributes. The wrapper for distance() has also been suitably modified to
319-
accept instances of the Point extension type as arguments, but pass the underlying
320-
pointer to the C function.
321-
Making this change, you will find that the code for manipulating Point objects is more
322-
natural:
323-
324-
>>> import sample
325-
>>> p1 = sample.Point(2,3)
326-
>>> p2 = sample.Point(4,5)
327-
>>> p1
328-
<sample.Point object at 0x100447288>
329-
>>> p2
330-
<sample.Point object at 0x1004472a0>
331-
>>> p1.x
332-
2.0
333-
>>> p1.y
334-
3.0
335-
>>> sample.distance(p1,p2)
336-
2.8284271247461903
337-
>>>
338-
339-
This recipe has illustrated many of Cython’s core features that you might be able to
340-
extrapolate to more complicated kinds of wrapping. However, you will definitely want
341-
to read more of the official documentation to do more.
342-
The next few recipes also illustrate a few additional Cython features.
211+
在csample.pxd文件中的``in_mandel()`` 声明有个很有趣但是比较难理解的定义。
212+
在这个文件中,函数被声明为然后一个bint而不是一个int。
213+
它会让函数创建一个正确的Boolean值而不是简单的整数。
214+
因此,返回值0表示False而1表示True。
215+
216+
在Cython包装器中,你可以选择声明C数据类型,也可以使用所有的常见Python对象。
217+
对于 ``divide()`` 的包装器展示了这样一个例子,同时还有如何去处理一个指针参数。
218+
219+
::
220+
221+
def divide(x,y):
222+
cdef int rem
223+
quot = csample.divide(x,y,&rem)
224+
return quot, rem
225+
226+
在这里,``rem`` 变量被显示的声明为一个C整型变量。
227+
当它被传入 ``divide()`` 函数的时候,``&rem`` 创建一个跟C一样的指向它的指针。
228+
``avg()`` 函数的代码演示了Cython更高级的特性。
229+
首先 ``def avg(double[:] a)`` 声明了 ``avg()`` 接受一个一维的双精度内存视图。
230+
最惊奇的部分是返回的结果函数可以接受任何兼容的数组对象,包括被numpy创建的。例如:
231+
232+
::
233+
234+
>>> import array
235+
>>> a = array.array('d',[1,2,3])
236+
>>> import numpy
237+
>>> b = numpy.array([1., 2., 3.])
238+
>>> import sample
239+
>>> sample.avg(a)
240+
2.0
241+
>>> sample.avg(b)
242+
2.0
243+
>>>
244+
245+
在此包装器中,``a.size0`` 和 ``&a[0]`` 分别引用数组元素个数和底层指针。
246+
语法 ``<double *> &a[0]`` 教你怎样将指针转换为不同的类型。
247+
前提是C中的 ``avg()`` 接受一个正确类型的指针。
248+
参考下一节关于Cython内存视图的更高级讲述。
249+
250+
除了处理通常的数组外,``avg()`` 的这个例子还展示了如何处理全局解释器锁。
251+
语句 ``with nogil:`` 声明了一个不需要GIL就能执行的代码块。
252+
在这个块中,不能有任何的普通Python对象——只能使用被声明为 ``cdef`` 的对象和函数。
253+
另外,外部函数必须现实的声明它们能不依赖GIL就能执行。
254+
因此,在csample.pxd文件中,``avg()`` 被声明为 ``double avg(double *, int) nogil`` .
255+
256+
对Point结构体的处理是一个挑战。本节使用胶囊对象将Point对象当做隐形指针来处理,这个在15.4小节介绍过。
257+
要这样做的话,底层Cython代码稍微有点复杂。
258+
首先,下面的导入被用来引入C函数库和Python C API中定义的函数:
259+
260+
::
261+
262+
from cpython.pycapsule cimport *
263+
from libc.stdlib cimport malloc, free
264+
265+
函数 ``del_Point()`` 和 ``Point()`` 使用这个功能来创建一个胶囊对象,
266+
它会包装一个 ``Point *`` 指针。``cdef del_Point()`` 将 ``del_Point()`` 声明为一个函数,
267+
只能通过Cython访问,而不能从Python中访问。
268+
因此,这个函数对外部是不可见的——它被用来当做一个回调函数来清理胶囊分配的内存。
269+
函数调用比如 ``PyCapsule_New()`` 、``PyCapsule_GetPointer()``
270+
直接来自Python C API并且以同样的方式被使用。
271+
272+
``distance`` 函数从 ``Point()`` 创建的胶囊对象中提取指针。
273+
这里要注意的是你不需要担心异常处理。
274+
如果一个错误的对象被传进来,``PyCapsule_GetPointer()`` 会抛出一个异常,
275+
但是Cython已经知道怎么查找到它,并将它从 ``distance()`` 传递出去。
276+
277+
处理Point结构体一个缺点是它的实现是不可见的。
278+
你不能访问任何属性来查看它的内部。
279+
这里有另外一种方法去包装它,就是定义一个扩展类型,如下所示:
280+
281+
::
282+
283+
# sample.pyx
284+
285+
cimport csample
286+
from libc.stdlib cimport malloc, free
287+
...
288+
289+
cdef class Point:
290+
cdef csample.Point *_c_point
291+
def __cinit__(self, double x, double y):
292+
self._c_point = <csample.Point *> malloc(sizeof(csample.Point))
293+
self._c_point.x = x
294+
self._c_point.y = y
295+
296+
def __dealloc__(self):
297+
free(self._c_point)
298+
299+
property x:
300+
def __get__(self):
301+
return self._c_point.x
302+
def __set__(self, value):
303+
self._c_point.x = value
304+
305+
property y:
306+
def __get__(self):
307+
return self._c_point.y
308+
def __set__(self, value):
309+
self._c_point.y = value
310+
311+
def distance(Point p1, Point p2):
312+
return csample.distance(p1._c_point, p2._c_point)
313+
314+
在这里,cdif类 ``Point`` 将Point声明为一个扩展类型。
315+
类属性 ``cdef csample.Point *_c_point`` 声明了一个实例变量,
316+
拥有一个指向底层Point结构体的指针。
317+
``__cinit__()`` 和 ``__dealloc__()`` 方法通过 ``malloc()`` 和 ``free()`` 创建并销毁底层C结构体。
318+
x和y属性的声明让你获取和设置底层结构体的属性值。
319+
``distance()`` 的包装器还可以被修改,使得它能接受 ``Point`` 扩展类型实例作为参数,
320+
而传递底层指针给C函数。
321+
322+
做了这个改变后,你会发现操作Point对象就显得更加自然了:
323+
324+
::
325+
326+
>>> import sample
327+
>>> p1 = sample.Point(2,3)
328+
>>> p2 = sample.Point(4,5)
329+
>>> p1
330+
<sample.Point object at 0x100447288>
331+
>>> p2
332+
<sample.Point object at 0x1004472a0>
333+
>>> p1.x
334+
2.0
335+
>>> p1.y
336+
3.0
337+
>>> sample.distance(p1,p2)
338+
2.8284271247461903
339+
>>>
340+
341+
本节已经演示了很多Cython的核心特性,你可以以此为基准来构建更多更高级的包装。
342+
不过,你最好先去阅读下官方文档来了解更多信息。
343+
344+
接下来几节还会继续演示一些Cython的其他特性。

0 commit comments

Comments
 (0)