在 Cython 模块之间共享声明¶
注意
此页面使用两种不同的语法变体
Cython 特定的
cdef
语法,旨在使类型声明简洁且易于从 C/C++ 的角度阅读。纯 Python 语法,允许在 纯 Python 代码 中进行静态 Cython 类型声明,遵循 PEP-484 类型提示和 PEP 526 变量注释。
要在 Python 语法中使用 C 数据类型,您需要在要编译的 Python 模块中导入特殊的
cython
模块,例如import cython
如果您使用纯 Python 语法,我们强烈建议您使用最新的 Cython 3 版本,因为与 0.29.x 版本相比,这里已经进行了重大改进。
本节介绍如何在另一个 Cython 模块中使用一个 Cython 模块中的 C 声明、函数和扩展类型。这些功能与 Python 导入机制非常相似,可以看作是它的编译时版本。
定义和实现文件¶
一个 Cython 模块可以分为两个部分:一个带有 .pxd
后缀的定义文件,包含要提供给其他 Cython 模块的 C 声明,以及一个带有 .pyx
/.py
后缀的实现文件,包含所有其他内容。当一个模块想要使用另一个模块的定义文件中声明的内容时,它使用 cimport
语句或使用特殊的 cython.cimports
包来导入它。
一个仅包含 extern 声明的 .pxd
文件不需要对应于实际的 .pyx
/.py
文件或 Python 模块。这可以使其成为放置通用声明的便捷位置,例如来自 外部库 的函数声明,这些函数需要在多个模块中使用。
定义文件包含什么¶
定义文件可以包含
任何类型的 C 类型声明。
extern C 函数或变量声明。
在模块中定义的 C 函数的声明。
扩展类型的定义部分(见下文)。
它不能包含任何 C 或 Python 函数的实现,也不能包含任何 Python 类定义或任何可执行语句。当需要访问 cdef
/@cfunc
属性和方法,或继承自 cdef
/@cclass
类时,需要使用定义文件。
注意
您不需要(也不应该)在声明文件中声明任何内容为公共的,以使其可供其他 Cython 模块使用;它在定义文件中的存在就足以实现这一点。只有当您想要使某些内容可供外部 C 代码使用时,才需要公共声明。
实现文件包含的内容¶
实现文件可以包含任何类型的 Cython 语句,尽管如果相应的定义文件也定义了该类型,则对扩展类型的实现部分有一些限制(见下文)。如果不需要从该模块中cimport
任何内容,那么这就是唯一需要的文件。
cimport 语句¶
在定义文件或实现文件中使用cimport
语句来访问在另一个定义文件中声明的名称。它的语法与普通的 Python import 语句完全一致。当使用纯 Python 语法时,可以通过从特殊的cython.cimports
包中导入来实现相同的效果。在后面的文本中,cimport
是指使用cimport
语句或cython.cimports
包。
from cython.cimports.module import name [as name][, name [as name] ...]
cimport module [, module...]
from module cimport name [as name] [, name [as name] ...]
以下是一个例子。 dishes.pxd
是一个定义文件,它导出一个 C 数据类型。 restaurant.pyx
/restaurant.py
是一个实现文件,它导入并使用它。
cdef enum otherstuff:
sausage, eggs, lettuce
cdef struct spamdish:
int oz_of_spam
otherstuff filler
import cython
from cython.cimports.dishes import spamdish, sausage
@cython.cfunc
def prepare(d: cython.pointer(spamdish)) -> cython.void:
d.oz_of_spam = 42
d.filler = sausage
def serve():
d: spamdish
prepare(cython.address(d))
print(f'{d.oz_of_spam} oz spam, filler no. {d.filler}')
cimport dishes
from dishes cimport spamdish
cdef void prepare(spamdish *d):
d.oz_of_spam = 42
d.filler = dishes.sausage
def serve():
cdef spamdish d
prepare(&d)
print(f'{d.oz_of_spam} oz spam, filler no. {d.filler}')
重要的是要理解cimport
语句只能用于导入 C 数据类型、C 函数和变量以及扩展类型。它不能用于导入任何 Python 对象,并且(除一个例外)它在运行时不隐含任何 Python 导入。如果要从已 cimport 的模块中引用任何 Python 名称,则还必须为其包含一个常规的 import 语句。
例外是,当使用cimport
导入扩展类型时,它的类型对象会在运行时导入,并通过导入它的名称提供。使用cimport
导入扩展类型将在下面更详细地介绍。
如果一个.pxd
文件发生更改,任何从该文件cimport
的模块可能需要重新编译。 Cython.Build.cythonize
实用程序可以为你处理这个问题。
定义文件的搜索路径¶
当cimport
一个名为modulename
的模块时,Cython 编译器会搜索一个名为modulename.pxd
的文件。它会在包含文件路径(由-I
命令行选项或cythonize()
的include_path
选项指定)以及sys.path
中搜索该文件。
在你的setup.py
脚本中使用package_data
来安装.pxd
文件,允许其他包将你的模块中的项目作为依赖项 cimport。
此外,每当你编译一个文件modulename.pyx
/modulename.py
时,首先会在包含路径(但不是sys.path
)中搜索相应的定义文件modulename.pxd
,如果找到,则在处理.pyx
文件之前处理它。
使用 cimport 解决命名冲突¶
cimport
机制提供了一种干净简洁的方法来解决用相同名称的 Python 函数包装外部 C 函数的问题。您只需将 extern C 声明放入一个名为 .pxd
文件的虚拟模块中,然后 cimport
该模块。然后,您可以通过使用模块名称限定 C 函数来引用它们。以下是一个示例
cdef extern from "lunch.h":
void eject_tomato(float)
import cython
from cython.cimports.c_lunch import eject_tomato as c_eject_tomato
def eject_tomato(speed: cython.float):
c_eject_tomato(speed)
cimport c_lunch
def eject_tomato(float speed):
c_lunch.eject_tomato(speed)
您不需要任何 c_lunch.pyx
/c_lunch.py
文件,因为 c_lunch.pxd
中定义的唯一内容是 extern C 实体。运行时不会有实际的 c_lunch
模块,但这并不重要;c_lunch.pxd
文件已经完成了在编译时提供额外命名空间的任务。
共享 C 函数¶
在模块的顶层定义的 C 函数可以通过 cimport
使用,方法是在 .pxd
文件中添加它们的标题,例如
cdef float cube(float x)
def cube(x):
return x * x * x
import cython
from cython.cimports.volume import cube
def menu(description, size):
print(description, ":", cube(size),
"cubic metres of spam")
menu("Entree", 1)
menu("Main course", 3)
menu("Dessert", 2)
cdef float cube(float x):
return x * x * x
from volume cimport cube
def menu(description, size):
print(description, ":", cube(size),
"cubic metres of spam")
menu("Entree", 1)
menu("Main course", 3)
menu("Dessert", 2)
共享扩展类型¶
可以通过 cimport
使用扩展类型,方法是将其定义拆分为两个部分,一部分在定义文件中,另一部分在相应的实现文件中。
扩展类型的定义部分只能声明 C 属性和 C 方法,不能声明 Python 方法,并且必须声明该类型的所有 C 属性和 C 方法。
实现部分必须实现定义部分中声明的所有 C 方法,并且不能添加任何其他 C 属性。它也可以定义 Python 方法。
以下是一个定义和导出扩展类型的模块以及使用它的另一个模块的示例
cdef class Shrubbery:
cdef int width
cdef int length
import cython
@cython.cclass
class Shrubbery:
def __cinit__(self, w: cython.int, l: cython.int):
self.width = w
self.length = l
def standard_shrubbery():
return Shrubbery(3, 7)
from cython.cimports.shrubbing import Shrubbery
import shrubbing
def main():
sh: Shrubbery
sh = shrubbing.standard_shrubbery()
print("Shrubbery size is", sh.width, 'x', sh.length)
然后,您需要编译这两个模块,例如使用
from setuptools import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize(["landscaping.py", "shrubbing.py"]))
cdef class Shrubbery:
def __init__(self, int w, int l):
self.width = w
self.length = l
def standard_shrubbery():
return Shrubbery(3, 7)
cimport shrubbing
import shrubbing
def main():
cdef shrubbing.Shrubbery sh
sh = shrubbing.standard_shrubbery()
print("Shrubbery size is", sh.width, 'x', sh.length)
然后,您需要编译这两个模块,例如使用
from setuptools import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize(["landscaping.pyx", "shrubbing.pyx"]))
关于此示例需要注意的一些事项
在
shrubbing.pxd
和shrubbing.pyx
中都有一个cdef
/@cclass
类 Shrubbery 声明。当编译 shrubbing 模块时,这两个声明将合并为一个。在
landscaping.pyx
/landscaping.py
中,cimport
shrubbing 声明允许我们将 Shrubbery 类型引用为shrubbing.Shrubbery
。但它不会在运行时将 shrubbing 名称绑定到 landscaping 模块的命名空间中,因此要访问shrubbing.standard_shrubbery()
,我们还需要import shrubbing
。
版本控制¶
.pxd
文件可以用最小 Cython 版本进行标记,作为其文件名的一部分,类似于 PEP 3149 中对 .so
文件的版本标记。例如,名为 shrubbing.cython-30.pxd
的文件只有在 Cython 3.0 及更高版本上才能被 cimport shrubbing
找到。Cython 将使用标记为最高兼容版本号的文件。
请注意,分布在不同目录中的版本化文件将无法找到。只有 Python 模块搜索路径中第一个找到匹配的 .pxd
文件的目录才会被考虑。
此功能的目的是允许第三方软件包发布其软件包的 Cython 接口,这些接口利用了最新的 Cython 功能,同时不会破坏使用旧版本 Cython 的用户的兼容性。打算在其项目中单独使用 .pxd
文件的用户无需生成这些标记文件。