Skip to content

在 Cython 模块之间共享声明

原文: http://docs.cython.org/en/latest/src/userguide/sharing_declarations.html

本节描述如何让 Cython 模块中的 C 声明,函数和扩展类型可以被另一个 Cython 模块使用。这些机理以 Python 导入机制为模型,可以被认为是它的编译时版本。

定义和实现文件

Cython 模块可以分为两部分:带有 .pxd 后缀的定义文件,包含可供其他 Cython 模块使用的 C 声明,以及带有 .pyx / .py 后缀的实现文件,其中包含其他所有内容。当模块想要使用在另一个模块的定义文件中声明的内容时,它会使用 cimport 语句导入它。

仅包含 extern 声明的 .pxd 文件不需要与实际的 .pyx / .py 文件或 Python 模块相对应。可以在里面写一些公用的声明,比如,想在多个模块中使用的从外部库中声明的函数。

定义文件内容

定义文件可以包含:

  • 任何类型的 C 类型声明。
  • extern C 函数或变量声明。
  • 模块中定义的 C 函数声明。
  • 扩展类型的定义部分(见下文)。

它不能包含任何 C 或 Python 函数的实现,也不能包含任何 Python 类定义或任何可执行语句。当想要访问 cdef / @cfunc 属性和方法,或从本模块中定义的 cdef / @cclass 类继承时,需要这个定义文件。

注意:您不需要(也不应该)- 为了在其他 Cython 模块使用 - 在声明文件中声明任何内容为公开的(public); 它只要存在于定义文件中即可。仅在您需要在外部 C 代码中使用时需要声明为公开。

实现文件内容

实现文件可以包含任何类型的 Cython 语句,但是如果相应的定义文件也定义了该类型,则对扩展类型的实现部分会受限。如果你不需要从这个模块 cimport 任何东西 ,那么这是唯一需要的文件(不需要定义文件)。

cimport 声明

cimport 语句用于定义或实现文件中,以获取对在另一个定义文件中声明的名称的访问。它的语法与普通的 Python import 语句完全相同:

# 经典风格
cimport module [, module...]

from module cimport name [as name] [, name [as name] ...]

# 纯Python风格
from cython.cimports.module import name [as name][, name [as name] ...]

这是一个例子。 dishes.pxd是导出 C 数据类型的定义文件。 restaurant.pyx是一个导入并使用它的实现文件。

# `dishes.pxd`
cdef enum otherstuff:
    sausage, eggs, lettuce

cdef struct spamdish:
    int oz_of_spam
    otherstuff filler

# 经典风格
# `dishes.pyx`:
from __future__ import print_function
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}')

# 纯Python风格
# `dishes.py`
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 语句只能用于导入 C 数据类型,C 函数和变量以及扩展类型。它不能用于导入任何 Python 对象,并且在运行时(有一个例外)也不意味着会执行任何Python引入。如果要引用已经 cimported 的模块中的任何 Python 名称,则还必须为其包含常规 import 语句。

例外情况是,当您使用 cimport 导入扩展类型时,其类型对象将在运行时导入,并由您导入它的名称提供。使用 cimport 导入扩展类型将在下面详细介绍。

如果 .pxd 文件发生变化,则任何 cimport 它的模块都需要重新编译(译者注:类似于Java)。 Cython.Build.cythonize 可以为您解决此问题。

定义文件的搜索路径

当您 cimport 一个名为 modulename 的模块时,Cython 编译器会搜索名为 modulename.pxd 的文件。它沿着包含文件的路径搜索此文件(由 -I 命令行选项或 cythonize()include_path 选项指定)以及 sys.path

使用 package_datasetup.py 脚本中安装 .pxd 文件允许其他软件包将模块中的项目作为依赖项。

此外,每当编译文件 modulename.pyx/modulename.py 时,首先沿着包含路径搜索相应的定义文件 modulename.pxd(但不是 sys.path ),如果找到,则在处理 .pyx 文件之前对其进行处理。

使用 cimport 解决命名冲突

cimport 机制提供了一种简洁明了的方法来解决使用相同名称的 Python 函数包装外部 C 函数的问题。您需要做的就是将外部 C 声明放入假想模块的.pxd文件中,将 cimport 放入该模块。然后,您可以通过使用模块名称对它们进行限定来引用 C 函数。这是一个例子:

c_lunch.pxd

cdef extern from "lunch.h":
    void eject_tomato(float)

# 经典风格
# `lunch.pyx`
cimport c_lunch

def eject_tomato(float speed):
    c_lunch.eject_tomato(speed)

# 纯Python风格
# `lunch.py`
import cython
from cython.cimports.c_lunch import eject_tomato as c_eject_tomato

def eject_tomato(speed: cython.float):
    c_eject_tomato(speed)

您不需要任何 c_lunch.pyx/c_lunch.py 文件,因为 c_lunch.pxd 中定义的唯一内容是外部 C 实体。在运行时不会有任何实际的 c_lunch 模块,但这无关紧要; c_lunch.pxd 文件完成了在编译时提供额外命名空间的工作。

共享 C 函数

通过把定义在模块顶层的C函数添加到 .pxd 文件中可以允许 cimport 来引用这些函数。例如:

volume.pxd

cdef float cube(float)

# 经典风格
# `volume.pyx`:
cdef float cube(float x):
    return x * x * x

# 纯Python风格
# volume.py
def cube(x):
    return x * x * x
# 经典风格
`spammery.pyx`:
from __future__ import print_function

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)

# 纯python风格
# spammetry.py
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)

注明:对于纯Python风格而言,volume.py 中的 cube() 函数的类型定义是没有的,因为它们包含在 .pxd 定义文件中。关于这个问题可以阅读 .pxd 增强 和 Github Issue #4388

共享扩展类型

可以通过 cimport 将扩展类型分为两部分,一部分在定义文件中,另一部分在相应的实现文件中。

扩展类型的定义部分只能声明 C 属性和 C 方法,而不能声明 Python 方法,并且它必须声明所有该类型的 C 属性和 C 方法。

实现部分必须实现定义部分中声明的所有 C 方法,且不可以添加任何其他 C 属性。还可以定义 Python 方法。

以下是定义和导出扩展类型的模块示例,以及在另一个模块中使用它:

# `shrubbing.pxd`:
cdef class Shrubbery:
    cdef int width
    cdef int length

# 经典风格
# `shrubbing.pyx`:
cdef class Shrubbery:
    def __cinit__(self, int w, int l):
        self.width = w
        self.length = l

def standard_shrubbery():
    return Shrubbery(3, 7)

# 纯Python风格
# shrubbing.py
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)
# 经典风格
# `landscaping.pyx`:
cimport shrubbing
import shrubbing

def main():
    cdef shrubbing.Shrubbery sh
    sh = shrubbing.standard_shrubbery()
    print("Shrubbery size is", sh.width, 'x', sh.length)

# 纯Python风格
# landscaping.py
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)

然后,你需要编译这两个模块,例如运用

setup.py

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

# 纯Python风格则这里是 "landscaping.py"
setup(ext_modules=cythonize(["landscaping.pyx", "shrubbing.pyx"]))

有关此示例的一些注意事项: * Shrubbing.pxdShrubbing.pyx 中都有 cdef / @cclass Shrubbery类声明。编译 Shrubbing 模块时,这两个声明合并为一个。 * 在 Landscaping.pyx/Landscaping.py 中, cimport Shrubbing 声明允许我们将Shrubbery类型称为 Shrubbing.Shrubbery 。但它在运行时没有绑定 Landscaping 模块命名空间中的名称 Shrubbing,因此要访问Shrubbing.standard_shrubbery()我们还需要import Shrubbing。 * 如果您使用 setuptools 而不是 distutils,则需要注意,运行 python setup.py install 时的默认操作是创建一个压缩的 egg 文件,当您尝试从依赖包中使用它们时,这些文件无法与 pxd 文件一起用于 pxd 文件。为防止这种情况,请在 setup() 的参数中包含 zip_safe=False

注明版本

.pxd 文件可以在文件名称的一部分中标明一个最低的Cython版本号,类似于 PEP-3149 中提到的 .so 文件的版本标签。举例来说,一个叫 shrubbing.cython-30.pxd 的文件只能在 Cython3.0或者更高版本中通过 cimport shrubbing 来找到。Cython会使用带有兼容版本号最高的文件。

注意发布在不同目录的不同带版本信息的文件不会都被找到。只有出现在Python模块搜索路径中的包含 第一个找到 .pxd 文件的目录会被考虑。

这个特性的目的是允许第三方库在它们的库中发布Cython接口以便利用最新版本的Cython特性,而不打破使用旧版本Cython用户的兼容使用。在项目中只准备使用 .pxd 文件的用户不需要输出这些带版本标签的文件



回到顶部