调用 C 函数¶
注意
此页面使用两种不同的语法变体
Cython 特定的
cdef
语法,旨在使类型声明简洁,并易于从 C/C++ 的角度阅读。纯 Python 语法,允许在 纯 Python 代码 中进行静态 Cython 类型声明,遵循 PEP-484 类型提示和 PEP 526 变量注释。
要在 Python 语法中使用 C 数据类型,您需要在要编译的 Python 模块中导入特殊的
cython
模块,例如import cython
如果您使用纯 Python 语法,我们强烈建议您使用最新的 Cython 3 版本,因为与 0.29.x 版本相比,这里已经进行了重大改进。
本教程简要介绍了从 Cython 代码调用 C 库函数所需的知识。有关使用外部 C 库、包装它们和处理错误的更长、更全面的教程,请参阅 使用 C 库。
为简单起见,让我们从标准 C 库中的一个函数开始。这不会给您的代码添加任何依赖项,并且它还有一个额外的好处,即 Cython 已经为您定义了许多这样的函数。因此,您只需 cimport 并使用它们即可。
例如,假设您需要一种低级方法来从 char*
值解析数字。您可以使用 atoi()
函数,如 stdlib.h
头文件所定义。这可以通过以下方式完成
from cython.cimports.libc.stdlib import atoi
@cython.cfunc
def parse_charptr_to_py_int(s: cython.p_char):
assert s is not cython.NULL, "byte string value is NULL"
return atoi(s) # note: atoi() has no error detection!
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/ 中找到这些标准 cimport 文件的完整列表。它们存储在 .pxd
文件中,这是提供可重用 Cython 声明的标准方式,这些声明可以在模块之间共享(请参阅 在 Cython 模块之间共享声明)。
Cython 还有一套完整的 CPython C-API 声明。例如,要在 C 编译时测试您的代码正在编译的 CPython 版本,您可以执行以下操作
from cython.cimports.cpython.version import PY_VERSION_HEX
# Python version >= 3.2 final ?
print(PY_VERSION_HEX >= 0x030200F0)
from cpython.version cimport PY_VERSION_HEX
# Python version >= 3.2 final ?
print(PY_VERSION_HEX >= 0x030200F0)
Cython 还提供了 C 数学库的声明
from cython.cimports.libc.math import sin
@cython.cfunc
def f(x: cython.double) -> cython.double:
return sin(x * x)
from libc.math cimport sin
cdef double f(double x):
return sin(x * x)
动态链接¶
libc 数学库很特殊,因为它不是在某些类 Unix 系统(如 Linux)上默认链接的。除了 cimport 声明之外,您还必须配置构建系统以链接到共享库 m
。对于 setuptools,将其添加到 Extension()
设置的 libraries
参数中就足够了
from setuptools import Extension, setup
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))
外部声明¶
如果您想访问 Cython 未提供现成声明的 C 代码,则必须自己声明它们。例如,上面的 sin()
函数定义如下
cdef extern from "math.h":
double sin(double x)
这声明了 sin()
函数,使其可用于 Cython 代码,并指示 Cython 生成包含 math.h
头文件的 C 代码。C 编译器将在编译时看到 math.h
中的原始声明,但 Cython 不会解析“math.h”并需要单独的定义。
就像来自数学库的 sin()
函数一样,只要 Cython 生成的模块与共享或静态库正确链接,就可以声明并调用任何 C 库。
请注意,您可以通过将外部 C 函数声明为 cpdef
从您的 Cython 模块轻松导出它。这将为它生成一个 Python 包装器并将其添加到模块字典中。以下是一个 Cython 模块,它为 Python 代码提供了对 C sin()
函数的直接访问。
"""
>>> sin(0)
0.0
"""
cdef extern from "math.h":
cpdef double sin(double x)
当此声明出现在属于 Cython 模块的 .pxd
文件中时,您将获得相同的结果(即具有相同名称,请参阅 在 Cython 模块之间共享声明)。这允许在其他 Cython 模块中重用 C 声明,同时在此特定模块中仍然提供自动生成的 Python 包装器。
注意
外部声明必须放在纯 Python 模式下的 .pxd
文件中。
命名参数¶
C 和 Cython 都支持没有参数名称的签名声明,如下所示
cdef extern from "string.h":
char* strstr(const char*, const char*)
但是,这会阻止 Cython 代码使用关键字参数调用它。因此,最好改为这样编写声明
cdef extern from "string.h":
char* strstr(const char *haystack, const char *needle)
现在您可以清楚地表明在您的调用中两个参数中的哪一个做什么,从而避免任何歧义,并且通常使您的代码更具可读性。
from cython.cimports.strstr import strstr
def main():
data: cython.p_char = "hfvcakdfagbcffvschvxcdfgccbcfhvgcsnfxjh"
pos = strstr(needle='akd', haystack=data)
print(pos is not cython.NULL)
cdef extern from "string.h":
char* strstr(const char *haystack, const char *needle)
cdef extern from "string.h":
char* strstr(const char *haystack, const char *needle)
cdef char* data = "hfvcakdfagbcffvschvxcdfgccbcfhvgcsnfxjh"
cdef char* pos = strstr(needle='akd', haystack=data)
print(pos is not NULL)
请注意,以后更改现有参数名称是向后不兼容的 API 修改,就像 Python 代码一样。因此,如果您为外部 C 或 C++ 函数提供自己的声明,通常值得付出额外的努力来很好地选择其参数的名称。