C 函数的调用

这个教程简要讲述了如何使用 Cython 调用 C 库函数。 如果想了解更多关于 C 库函数调用的内容,请参考C 函数的调用

简单来说,我们先以一个 C 标准库中的函数为例。 你不需要向你的代码中引入 额外的依赖,Cython 都已经帮你定义好了这些函数。所以你可以将这些函数直接 cimport 进来并使用。

举个例子,比如说当你想用最简单的方法将char*类型的值转化为一个整型值时, 你可以使用atoi() 函数,这个函数是在stdlib.h 头文件中定义的。我们可以这样来写:

  from libc.stdlib cimport atoi

  cdef parse_charptr_to_py_int(char* s):
      assert s is not NULL, "byte string value is NULL"
      return atoi(s)   # note: atoi() has no error detection!

你可以在 Cython 的源代码包Cython/Includes/ <https://github.com/cython/cython/tree/master/Cython/Includes>_. 中找到所有的标准 cimport 文件。这些文件保存在.pxd 文件中,这是一种标准再模块间共享 Cython 函数声明的方法( 见:ref:sharing-declarations)。

Cython 也有一整套的 Cython 的C-API 函数声明集。 例如,为了测试你的 Cython 代码的 C 编译时间,你可以这样做:

  from cpython.version cimport PY_VERSION_HEX

  # Python version >= 3.2 final ?
  print PY_VERSION_HEX >= 0x030200F0

Cython 也提供了 C math 库的声明:

  from libc.math cimport sin

  cdef double f(double x):
      return sin(x*x)

动态链接(Dynamic linking)

在一些类 Unix 系统(例如 linux)中,默认不提供libc math 库。 所以除了 cimport函数声明外,你还必须配置你的编译器以链接共享库m。 对于 distutils来说,在Extension()安装变量libraries 中将其添加进来就可以了。

  from distutils.core import setup
  from distutils.extension import Extension
  from Cython.Build import cythonize

  ext_modules=[
      Extension("demo",
                sources=["demo.pyx"],
                libraries=["m"] # Unix-like specific
      )
  ]

  setup(
    name = "Demos",
    ext_modules = cythonize(ext_modules)
  )

外部声明(External declarations)

如果你想调用一个 Cython 中没有定义的函数声明,那么你必须自己进行声明。例如,上文中的 sin()函数就是这样定义的:

  cdef extern from "math.h":
      double sin(double x)

此处声明了sin()函数,这时我们便可在 Cython 代码中使用这个函数,并且让 Cython 生成一份包括math.h 头文件的 C 代码。C 编译器在编译时能够在math.h 中找到原始的函数声明。但是 Cython 不能解析math.h 并需要一个单独的定义。

正如math 库中的sin()函数一样,只要 Cython 生成的模块正确的链接了共享库或静态库,我们就可以声明并调用任意的 C 库函数。

注意,只要简单地通过cpdef 声明,你就能从 Cython 模块中导出一个外部 C 函数。而且生成了一个 Python 扩展,使得我们可以在 Python 代码中直接访问到 C 函数sin()

  >>> sin(0)
  0.0
  cdef extern from "math.h":
      cpdef double sin(double x)

在属于 Cython 模块的.pxd文件中( 一般与模块名一致,见:ref:sharing-declaration)声明函数时, 你也可以达到同样的效果。 这使得其他 Cython 模块可以复用某个 C 声明。然而还是会在 Cython 模块中产生一份自动生成的 python 扩展。

变量的命名(Naming parameters)

C 和 Cython 都支持没有参数明的signature declarations:

  cdef extern from "string.h":
      char* strstr(const char*, const char*)

然而,这样的话 Cython 代码将不能通过关键字参数来调用这个函数(由Cython 0.19及以后的版本所支持)。所以,我们最好这样去声明一个函数:

  cdef extern from "string.h":
      char* strstr(const char *haystack, const char *needle)

这会让清楚地知道你所调用了哪两个参数,从而能够避免二义性并增强你的代码的可读性:

  cdef char* data = "hfvcakdfagbcffvschvxcdfgccbcfhvgcsnfxjh"

  pos = strstr(needle='akd', haystack=data)
  print pos != NULL

注意,正如 Python 代码一样,对已有参数名的修改是不向后兼容的。那么, 如果你为外部的 C 或 C++ 函数进行了自己的声明,通常花一点时间去 将参数名命名的更好是非常值得的。