📜  Cython 包装现有 C 代码

📅  最后修改于: 2022-05-13 01:55:45.319000             🧑  作者: Mango

Cython 包装现有 C 代码

什么是赛通?
它是Python编程语言和扩展 Cython 编程语言的优化静态编译器。它用于简化为Python编写 C 扩展,就像Python本身一样简单。

它提供了许多有用的功能

  • 编写在 C/C++ 代码之间来回调用的Python代码。
  • 通过添加静态类型声明,轻松将可读的Python代码调整为纯 C 性能。
  • 使用组合源代码级调试来查找给定Python、Cython 和 C 代码中的错误。
  • 与大型数据集的高效交互,例如使用多维 NumPy 数组。
  • 与来自低级或高性能库和应用程序的现有代码和数据集成。

使用 Cython 进行扩展是一项棘手的任务。这样做,需要创建一组包装函数。假设显示的工作代码已编译到名为libwork的 C 库中。下面的代码将创建一个名为csample.pxd的文件。

代码#1:

# cwork.pxd
#
# Declarations of "external" C 
# functions and structures
  
cdef extern from "work.h":
   
    int gcd(int, int)
    int divide(int, int, int *)
    double avg(double *, int) nogil
      
    ctypedef struct Point:
        double x
        double y
          
    double distance(Point *, Point *)

在 Cython 中,上面的代码将作为 C 头文件工作。 "work.h"中的初始声明cdef extern声明了所需的 C 头文件。后面的声明取自标题。该文件的名称是cwork.pxd 。下一个目标是创建一个work.pyx文件,该文件将定义将Python解释器连接到cwork.pxd文件中声明的底层 C 代码的包装器。

代码#2:

# work.pyx
# Import the low-level C declarations
        
cimport cwork
# Importing functionalities from Python
# and the C stdlib
from cpython.pycapsule cimport * 
from libc.stdlib cimport malloc, free
  
# Wrappers
def gcd(unsigned int x, unsigned int y):
    return cwork.gcd(x, y)
  
def divide(x, y):
    cdef int rem
    quot = cwork.divide(x, y, &rem)
    return quot, rem
  
def avg(double[:] a):
    cdef:
        int sz
        double result
  
    sz = a.size
  
    with nogil:
        result = cwork.avg( &a[0], sz)
  
    return result


代码#3:

# Destructor for cleaning up Point objects
cdef del_Point(object obj):
    pt =  PyCapsule_GetPointer(obj, "Point")
    free( pt)
      
# Create a Point object and return as a capsule
def Point(double x, double y):
    cdef csample.Point * p
    p =  malloc(sizeof(csample.Point))
      
    if p == NULL:
        raise MemoryError("No memory to make a Point")
          
    p.x = x
    p.y = y
      
    return PyCapsule_New(p, "Point",
                         del_Point)
  
def distance(p1, p2):
    pt1 =  PyCapsule_GetPointer(p1, "Point")
    pt2 =  PyCapsule_GetPointer(p2, "Point")
      
    return csample.distance(pt1, pt2)


最后,要构建扩展模块,创建一个work.py文件。

代码 #4:

# importing libraries
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
  
ext_modules = [Extension('work', 
                         ['work.pyx'], 
                         libraries=['work'], 
                         library_dirs=['.'])]
  
setup(name = 'work extension module',
      cmdclass = {'build_ext': build_ext},
      ext_modules = ext_modules)


代码#5:构建结果模块进行实验。

bash % python3 setup.py build_ext --inplace
running build_ext
  
cythoning work.pyx to work.c
building 'work' extension
  
gcc -fno-strict-aliasing -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes
-I/usr/local/include/python3.3m -c work.c
-o build/temp.macosx-10.6-x86_64-3.3/work.o
  
gcc -bundle -undefined dynamic_lookup build/temp.macosx-10.6-x86_64-3.3/work.o
-L. -lwork -o work.so
bash %

现在,我们有一个扩展模块work.so 。让我们看看它是如何工作的。

代码#6:

import sample
print ("GCD : ", sample.gcd(12, 8))
  
print ("\nDivision : ", sample.divide(42,10))
  
import array
arr = array.array('d',[1,2,3])
print ("\nAverage  : ", sample.avg(a)
  
pt1 = sample.Point(2,3)
pt2 = sample.Point(4,5)
  
print ("\npt1 : ", pt1)
print ("\npt2 : ", pt2)
  
print ("\nDistance between the two points : ", 
       sample.distance(pt1, pt2))

输出 :

GCD : 4

Division : (4, 2)

Average : 2.0

pt1 : 

pt2 : 

Distance between the two points : 2.8284271247461903

在高层次上,使用 Cython 是在 C 之后建模的。.pxd文件仅包含 C 定义(类似于.h文件),而.pyx文件包含实现(类似于.c文件)。 Cython 使用cimport语句从.pxd文件导入定义。这与使用普通的Python导入语句不同,后者将加载常规的Python模块。