源文件和编译

Cython 源文件名由模块名后缀 .pyx 扩展名组成,例如名为 primes 的模块将有一个名为 primes.pyx 的源文件。

与 Python 不同,Cython 代码必须编译。这分为两个阶段

  • 一个 .pyx(或 .py)文件由 Cython 编译成一个 .c 文件。

  • .c 文件由 C 编译器编译成一个 .so 文件(或 Windows 上的 .pyd 文件)

编写完 .pyx/.py 文件后,有几种方法可以将其转换为扩展模块。

以下小节描述了几种构建扩展模块的方法,以及如何将指令传递给 Cython 编译器。

除了 Cython 之外,还有许多工具可以处理 .pyx 文件,例如:

从命令行编译

从命令行编译有两种方法。

  • cython 命令接受一个 .py.pyx 文件并将其编译成一个 C/C++ 文件。

  • cythonize 命令接受一个 .py.pyx 文件并将其编译成一个 C/C++ 文件。然后,它将 C/C++ 文件编译成一个扩展模块,该模块可以直接从 Python 导入。

使用 cython 命令编译

一种方法是使用 Cython 编译器手动编译它,例如:

$ cython primes.pyx

这将生成一个名为 primes.c 的文件,然后需要使用 C 编译器使用平台上生成扩展模块的适当选项进行编译。有关这些选项,请查看官方 Python 文档。

另一种方法,可能是更好的方法,是使用 Cython 提供的 setuptools 扩展。这种方法的好处是它将提供平台特定的编译选项,就像一个简化的 autotools。

使用 cythonize 命令编译

使用您的选项和 .pyx 文件列表运行 cythonize 编译器命令以生成扩展模块。例如

$ cythonize -a -i yourmod.pyx

这将创建一个 yourmod.c 文件(或 C++ 模式下的 yourmod.cpp),编译它,并将生成的扩展模块(.so.pyd,取决于您的平台)放在源文件旁边以供直接导入(-i 在“原地”构建)。-a 开关还会生成源代码的带注释的 html 文件。

命令 cythonize 接受多个源文件和通配符模式(如 **/*.pyx)作为参数,并理解常用的 -j 选项以运行多个并行构建作业。在没有其他选项的情况下调用时,它只会将源文件转换为 .c.cpp 文件。传递 -h 标志以获取支持选项的完整列表。

更简单的命令行工具 cython 只调用源代码翻译器。

在手动编译的情况下,如何编译你的 .c 文件将取决于你的操作系统和编译器。Python 文档中关于编写扩展模块的部分应该包含一些关于你系统的详细信息。例如,在 Linux 系统上,它可能类似于以下内容

$ gcc -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing \
      -I/usr/include/python3.5 -o yourmod.so yourmod.c

(gcc 需要包含你包含的头文件路径和你要链接的库的路径。)

编译后,一个 yourmod.so (yourmod.pyd 用于 Windows) 文件将被写入目标目录,你的模块 yourmod 将可供你导入,就像任何其他 Python 模块一样。请注意,如果你不依赖于 cythonize 或 setuptools,你将不会自动受益于 CPython 为消除歧义而生成的特定于平台的文件扩展名,例如在 CPython 3.5 的常规 64 位 Linux 安装中,yourmod.cpython-35m-x86_64-linux-gnu.so

基本 setup.py

Cython 提供的 setuptools 扩展允许你将 .pyx 文件直接传递给你的 setup 文件中的 Extension 构造函数。

如果你有一个你想要转换为已编译扩展的单个 Cython 文件,比如文件名 example.pyx,那么相关的 setup.py 将是

from setuptools import setup
from Cython.Build import cythonize

setup(
    ext_modules = cythonize("example.pyx")
)

如果你的构建以这种方式直接依赖于 Cython,那么你可能还想告知 pip Cython 是执行 setup.py 所必需的,遵循 PEP 518,创建一个包含至少以下内容的 pyproject.toml 文件

[build-system]
requires = ["setuptools", "wheel", "Cython"]

要更全面地了解 setup.py,请查看官方的 setuptools 文档。要编译扩展以在当前目录中使用,请使用

$ python setup.py build_ext --inplace

配置 C 构建

注意

有关构建使用 cimport numpy 的 Cython 模块的更多详细信息,请参阅用户指南的 Numpy 部分

如果你有 Cython 包含文件Cython 定义文件 位于非标准位置,你可以将 include_path 参数传递给 cythonize

from setuptools import setup
from Cython.Build import cythonize

setup(
    name="My hello app",
    ext_modules=cythonize("src/*.pyx", include_path=[...]),
)

如果你需要指定编译器选项、要链接的库或其他链接器选项,你需要手动创建 Extension 实例(请注意,通配符语法仍然可以用于在一行中指定多个扩展)

from setuptools import Extension, setup
from Cython.Build import cythonize

extensions = [
    Extension("primes", ["primes.pyx"],
        include_dirs=[...],
        libraries=[...],
        library_dirs=[...]),
    # Everything but primes.pyx is included here.
    Extension("*", ["*.pyx"],
        include_dirs=[...],
        libraries=[...],
        library_dirs=[...]),
]
setup(
    name="My hello app",
    ext_modules=cythonize(extensions),
)

一些有用的选项包括

  • include_dirs - 用于搜索 C/C++ 头文件的目录列表(以 Unix 格式表示,以确保可移植性),

  • libraries - 要链接的库名称列表(不是文件名或路径),

  • library_dirs - 用于在链接时搜索 C/C++ 库的目录列表。

请注意,在使用 setuptools 时,您应该在 Cython 之前导入它,否则两者可能会对这里要使用的类产生分歧。

通常,提供 C 级 API 的 Python 包会提供一种方法来查找必要的 C 头文件。

from setuptools import Extension, setup
from Cython.Build import cythonize

extensions = [
    Extension("*", ["*.pyx"],
        include_dirs=["/usr/local/include"]),
]
setup(
    name="My hello app",
    ext_modules=cythonize(extensions),
)

如果您的选项是静态的(例如,您不需要调用像 pkg-config 这样的工具来确定它们),您也可以在您的 .pyx 或 .pxd 源文件中使用文件开头的特殊注释块直接提供它们。

# distutils: libraries = spam eggs
# distutils: include_dirs = /opt/food/include

如果您 cimport 了多个定义库的 .pxd 文件,那么 Cython 会合并库列表,因此这将按预期工作(类似于其他选项,例如上面的 include_dirs)。

如果您有一些用 Cython 包装的 C 文件,并且您想将它们编译到您的扩展中,您可以定义 setuptools 的 sources 参数。

# distutils: sources = [helper.c, another_helper.c]

请注意,这些源代码将被添加到当前扩展模块的源代码列表中。在 setup.py 文件中详细说明如下。

from setuptools import Extension, setup
from Cython.Build import cythonize

sourcefiles = ['example.pyx', 'helper.c', 'another_helper.c']

extensions = [Extension("example", sourcefiles)]

setup(
    ext_modules=cythonize(extensions)
)

The Extension 类接受许多选项,更详细的解释可以在 setuptools 文档 中找到。

有时这还不够,您需要对 setuptools 的 Extension 进行更精细的自定义。为此,您可以提供一个自定义函数 create_extension 来在 Cython 处理完源代码、依赖项和 # distutils 指令后,但在实际 Cython 化文件之前创建最终的 Extension 对象。此函数接受 2 个参数 templatekwds,其中 template 是作为输入传递给 Cython 的 Extension 对象,而 kwds 是一个 dict,其中包含用于创建 Extension 的所有关键字。函数 create_extension 必须返回一个 2 元组 (extension, metadata),其中 extension 是创建的 Extension,而 metadata 是元数据,它将作为 JSON 写入生成的 C 文件的顶部。此元数据仅用于调试目的,因此您可以在其中放置任何您想要的内容(只要它可以转换为 JSON)。默认函数(在 Cython.Build.Dependencies 中定义)是

def default_create_extension(template, kwds):
    if 'depends' in kwds:
        include_dirs = kwds.get('include_dirs', []) + ["."]
        depends = resolve_depends(kwds['depends'], include_dirs)
        kwds['depends'] = sorted(set(depends + template.depends))

    t = template.__class__
    ext = t(**kwds)
    if hasattr(template, "py_limited_api"):
        ext.py_limited_api = template.py_limited_api
    metadata = dict(distutils=kwds, module_name=kwds['name'])
    return ext, metadata

如果您将字符串而不是 Extension 传递给 cythonize(),则 template 将是一个没有源代码的 Extension。例如,如果您执行 cythonize("*.pyx"),则 template 将是 Extension(name="*.pyx", sources=[])

举个例子,这将 mylib 作为库添加到每个扩展中。

from Cython.Build.Dependencies import default_create_extension

def my_create_extension(template, kwds):
    libs = kwds.get('libraries', []) + ["mylib"]
    kwds['libraries'] = libs
    return default_create_extension(template, kwds)

ext_modules = cythonize(..., create_extension=my_create_extension)

注意

如果您并行 Cython 化(使用 nthreads 参数),那么传递给 create_extension 的参数必须是可腌制的。特别是,它不能是 lambda 函数。

Cythonize 参数

函数 cythonize() 可以接受额外的参数,这些参数将允许您自定义构建。

Cython.Build.cythonize(module_list, exclude=None, nthreads=0, aliases=None, quiet=False, force=None, language=None, exclude_failures=False, show_all_warnings=False, **options)

将一组源模块编译成 C/C++ 文件,并返回它们的 distutils Extension 对象列表。

参数::
  • module_list – 作为模块列表,传递 glob 模式、glob 模式的列表或 Extension 对象的列表。后者允许您通过正常的 distutils 选项单独配置扩展。您也可以传递将 glob 模式作为其源的 Extension 对象。然后,cythonize 将解析模式并为每个匹配文件创建 Extension 的副本。

  • exclude – 当将 glob 模式作为 module_list 传递时,您可以通过将它们传递到 exclude 选项中来显式排除某些模块名称。

  • nthreads – 并行编译的并发构建数量(需要 multiprocessing 模块)。

  • aliases – 如果您想使用编译器指令,例如 # distutils: ...,但只能在编译时(运行 setup.py)知道要使用哪些值,您可以使用别名并在调用 cythonize() 时传递一个将这些别名映射到 Python 字符串的字典。例如,假设您想使用编译器指令 # distutils: include_dirs = ../static_libs/include/,但此路径并不总是固定的,您希望在运行 setup.py 时找到它。然后,您可以执行 # distutils: include_dirs = MY_HEADERS,在 setup.py 中找到 MY_HEADERS 的值,将其放入名为 foo 的 python 变量中作为字符串,然后调用 cythonize(..., aliases={'MY_HEADERS': foo})

  • quiet – 如果为 True,Cython 不会在编译期间打印错误、警告或状态消息。

  • force – 强制重新编译 Cython 模块,即使时间戳不表明需要重新编译。

  • 语言 – 要全局启用 C++ 模式,您可以传递 language='c++'。否则,这将根据编译器指令在每个文件级别确定。这仅影响根据文件名找到的模块。传递到 cythonize() 的扩展实例不会更改。建议使用编译器指令 # distutils: language = c++ 而不是此选项。

  • exclude_failures – 对于广泛的“尝试编译”模式,该模式忽略编译失败并简单地排除失败的扩展,请传递 exclude_failures=True。请注意,这仅对编译 .py 文件有意义,这些文件也可以在没有编译的情况下使用。

  • show_all_warnings – 默认情况下,不会打印所有 Cython 警告。设置为 true 以显示所有警告。

  • annotate – 如果为 True,将为每个编译的 .pyx.py 文件生成一个 HTML 文件。HTML 文件指示每个源代码行中存在多少 Python 交互,与纯 C 代码相比。它还允许您查看为每行 Cython 代码生成的 C/C++ 代码。此报告在优化函数速度和确定何时 释放 GIL 时非常宝贵:通常,nogil 块可能仅包含“白色”代码。请参阅 确定添加类型的位点素数 中的示例。

  • annotate-fullc – 如果为 True,将生成源代码的彩色 HTML 版本,其中包含完整的生成的 C/C++ 代码。

  • compiler_directives – 允许在 setup.py 中设置编译器指令,如下所示:compiler_directives={'embedsignature': True}。请参阅 编译器指令

  • depfile – 如果为 True,则为源代码生成依赖文件。

  • cache – 如果为 True,则使用默认路径启用缓存。如果该值是目录的路径,则该目录用于缓存生成的 .c/.cpp 文件。默认情况下,缓存被禁用。请参阅 Cython 缓存

包中的多个 Cython 文件

要自动编译多个 Cython 文件而不显式列出所有文件,您可以使用 glob 模式

setup(
    ext_modules = cythonize("package/*.pyx")
)

如果您通过 cythonize() 传递 Extension 对象,也可以在其中使用 glob 模式

extensions = [Extension("*", ["*.pyx"])]

setup(
    ext_modules = cythonize(extensions)
)

分发 Cython 模块

在分发工具链最近的改进之后,不建议在源代码分发中包含生成的 文件。相反,在构建时 require Cython 以生成 C/C++ 文件,如 PEP 518PEP 621 中所定义。请参阅 基本 setup.py

但是,可以将生成的 .c 文件与您的 Cython 源代码一起分发,以便用户无需安装 Cython 即可安装您的模块。

这样做允许您在分发的版本中使 Cython 编译成为可选。即使用户安装了 Cython,他们可能也不想仅仅为了安装您的模块而使用它。此外,安装的版本可能与您使用的版本不同,并且可能无法正确编译您的源代码。

这仅仅意味着您与之一起发布的 setup.py 文件将只是生成的 .c 文件上的一个普通的 setuptools 文件,对于基本示例,我们将改为

from setuptools import Extension, setup

setup(
    ext_modules = [Extension("example", ["example.c"])]
)

通过更改扩展模块源代码的文件扩展名,可以轻松地将它与 cythonize() 结合起来

from setuptools import Extension, setup

USE_CYTHON = ...   # command line option, try-import, ...

ext = '.pyx' if USE_CYTHON else '.c'

extensions = [Extension("example", ["example"+ext])]

if USE_CYTHON:
    from Cython.Build import cythonize
    extensions = cythonize(extensions)

setup(
    ext_modules = extensions
)

如果您有很多扩展并且想要避免声明中的额外复杂性,您可以使用其正常的 Cython 源代码声明它们,然后调用以下函数而不是 cythonize() 来调整 Extensions 中的源代码列表,前提是不使用 Cython

import os.path

def no_cythonize(extensions, **_ignore):
    for extension in extensions:
        sources = []
        for sfile in extension.sources:
            path, ext = os.path.splitext(sfile)
            if ext in ('.pyx', '.py'):
                if extension.language == 'c++':
                    ext = '.cpp'
                else:
                    ext = '.c'
                sfile = path + ext
            sources.append(sfile)
        extension.sources[:] = sources
    return extensions

如果您想公开库的 C 级接口,以便其他库可以从中 cimport,请使用 package_data 来安装 .pxd 文件,例如:

setup(
    package_data = {
        'my_package': ['*.pxd'],
        'my_package/sub_package': ['*.pxd'],
    },
    ...
)

这些 .pxd 文件不需要有相应的 .pyx 模块,如果它们只包含外部库的声明。

集成多个模块

在某些情况下,将多个 Cython 模块(或其他扩展模块)链接到单个二进制文件可能很有用,例如在将 Python 嵌入到另一个应用程序中时。这可以通过 CPython 的 inittab 导入机制来完成。

创建一个新的 C 文件来集成扩展模块,并将此宏添加到其中

#if PY_MAJOR_VERSION < 3
# define MODINIT(name)  init ## name
#else
# define MODINIT(name)  PyInit_ ## name
#endif

如果您只针对 Python 3.x,只需使用 PyInit_ 作为前缀。

然后,对于每个模块,声明其模块初始化函数,如下所示,将 some_module_name 替换为模块的名称

PyMODINIT_FUNC  MODINIT(some_module_name) (void);

在 C++ 中,将它们声明为 extern C

如果您不确定模块初始化函数的名称,请参考您生成的模块源文件,并查找以 PyInit_ 开头的函数名称。

接下来,在您使用 Py_Initialize() 从应用程序代码启动 Python 运行时之前,您需要使用 PyImport_AppendInittab() C-API 函数在运行时初始化模块,再次插入每个模块的名称

PyImport_AppendInittab("some_module_name", MODINIT(some_module_name));

这使嵌入式扩展模块能够进行正常的导入。

为了防止合并的二进制文件将所有模块初始化函数导出为公共符号,Cython 0.28 及更高版本可以在 C 编译模块 C 文件时定义宏 CYTHON_NO_PYINIT_EXPORT 来隐藏这些符号。

还可以查看 cython_freeze 工具。它可以生成将一个或多个模块链接到单个 Python 可执行文件所需的样板代码。

使用 pyximport 编译

为了在开发过程中构建 Cython 模块,而无需在每次更改后显式运行 setup.py,您可以使用 pyximport

>>> import pyximport; pyximport.install()
>>> import helloworld
Hello World

这使您能够在 Python 尝试导入的每个 .pyx 上自动运行 Cython。您应该仅在不需要额外 C 库和特殊构建设置的简单 Cython 构建中使用它。

还可以编译正在导入的新 .py 模块(包括标准库和已安装的包)。要使用此功能,只需告诉 pyximport

>>> pyximport.install(pyimport=True)

如果 Cython 无法编译 Python 模块,pyximport 将回退到加载源模块。

请注意,不建议让 pyximport 在最终用户端构建代码,因为它会挂钩到他们的导入系统。为最终用户提供服务的最佳方法是在 wheel 打包格式中提供预构建的二进制包。

参数

函数 pyximport.install() 可以接受多个参数来影响 Cython 或 Python 文件的编译。

pyximport.install(pyximport=True, pyimport=False, build_dir=None, build_in_temp=True, setup_args=None, reload_support=False, load_py_module_on_import_failure=False, inplace=False, language_level=None)

pyxinstall 的主要入口点。

调用此函数以在单个 Python 进程的元路径中安装 .pyx 导入钩子。如果您希望它在每次使用 Python 时都安装,请将其添加到您的 sitecustomize(如上所述)。

参数::
  • pyximport – 如果设置为 False,则不会尝试导入 .pyx 文件。

  • pyimport – 您可以传递 pyimport=True 以在元路径中安装 .py 导入钩子。但是,请注意,它处于实验阶段,对于某些 .py 文件和包根本无法工作,并且由于搜索和编译,会严重减慢您的导入速度。请自行承担风险使用。

  • build_dir – 默认情况下,编译后的模块将位于用户主目录中的 .pyxbld 目录中。传递不同的路径作为 build_dir 将覆盖此设置。

  • build_in_temp – 如果为 False,将本地生成 C 文件。使用复杂的依赖项和调试变得更加容易。这可能会主要干扰同名现有文件。

  • setup_args – Distribution 的参数字典。请参阅 distutils.core.setup()

  • reload_support – 启用对动态 reload(my_module) 的支持,例如在 Cython 代码更改后。如果先前加载的模块文件无法覆盖,则可能会因此出现额外的文件 <so_path>.reloadNN

  • load_py_module_on_import_failure – 如果 .py 文件的编译成功,但随后的导入由于某种原因失败,请使用正常的 .py 模块而不是编译后的模块重试导入。请注意,这可能会导致模块在导入期间更改系统状态时出现不可预测的结果,因为第二次导入将在编译后的模块导入失败后系统处于的任何状态下重新运行这些修改。

  • inplace – 将编译后的模块(Linux 和 Mac 的 .so / Windows 的 .pyd)安装到源文件旁边。

  • language_level – 要使用的源语言级别:2 或 3。默认情况下,对于 .py 文件使用当前 Python 运行时的语言级别,对于 .pyx 文件使用 Py2。

依赖项处理

由于 pyximport 在内部不使用 cythonize(),因此它目前需要不同的依赖项设置。可以声明您的模块依赖于多个文件(可能是 .h.pxd 文件)。如果您的 Cython 模块名为 foo,因此文件名是 foo.pyx,那么您应该在同一目录中创建另一个名为 foo.pyxdep 的文件。 modname.pyxdep 文件可以是文件名列表或“通配符”(如 *.pxdinclude/*.h)。每个文件名或通配符必须位于单独的行上。Pyximport 将检查每个文件的日期,然后决定是否重建模块。为了跟踪依赖项已处理的事实,Pyximport 会更新您的“.pyx”源文件的修改时间。未来的版本可能会做一些更复杂的事情,比如直接将依赖项通知 setuptools。

限制

pyximport 不使用 cythonize()。因此,无法执行诸如在 Cython 文件顶部使用编译器指令或将 Cython 代码编译为 C++ 之类的事情。

Pyximport 不会让您控制如何编译 Cython 文件。通常默认值就可以了。如果您想用一半 C 代码、一半 Cython 代码编写程序并将它们构建成一个库,可能会遇到问题。

Pyximport 不会隐藏导入过程中生成的 setuptools/GCC 警告和错误。可以说,如果出现问题,这将为您提供更好的反馈,以及出现问题的原因。如果没有问题,它会让您有一种温暖模糊的感觉,即 pyximport 确实按照预期重建了您的模块。

基本模块重新加载支持可通过选项 reload_support=True 获得。请注意,这将为每次构建生成一个新的模块文件名,因此最终会随着时间的推移将多个共享库加载到内存中。CPython 对重新加载共享库的支持有限,请参见 PEP 489

Pyximport 将您的 .c 文件和特定于平台的二进制文件都放在一个单独的构建目录中,通常是 $HOME/.pyxblx/。要将其复制回包层次结构(通常位于源文件旁边)以供手动重用,您可以传递选项 inplace=True

使用 cython.inline 编译

还可以以类似于 SciPy 的 weave.inline 的方式编译 Cython。例如

>>> import cython
>>> def f(a):
...     ret = cython.inline("return a+b", b=3)
...

未绑定的变量会自动从周围的局部和全局范围中提取,并且编译结果会被缓存以供高效重用。

使用 cython.compile 编译

Cython 支持使用 @cython.compile 装饰器透明地编译函数中的 cython 代码

@cython.compile
def plus(a, b):
    return a + b

装饰函数的参数不能有类型声明。它们的类型会自动从传递给函数的值中确定,从而导致一个或多个针对相应参数类型的专用编译函数。执行示例

import cython

@cython.compile
def plus(a, b):
    return a + b

print(plus('3', '5'))
print(plus(3, 5))

将产生以下输出

35
8

使用 Sage 编译

Sage 笔记本允许通过在单元格顶部键入 %cython 并对其进行评估来透明地编辑和编译 Cython 代码。在 Cython 单元格中定义的变量和函数会被导入到正在运行的会话中。有关详细信息,请查看 Sage 文档

您可以通过指定以下指令来调整 Cython 编译器的行为。

在 Jupyter Notebook 中编译

可以使用 Cython 在 Notebook 单元格中编译代码。为此,您需要加载 Cython 魔法命令

%load_ext cython

然后,您可以通过在单元格顶部编写 %%cython 来定义一个 Cython 单元格。例如:

%%cython

cdef int a = 0
for i in range(10):
    a += i
print(a)

请注意,每个单元格将被编译成一个单独的扩展模块。因此,如果您在 Cython 单元格中使用了一个包,您需要在同一个单元格中导入该包。仅仅在之前的单元格中导入该包是不够的。如果您不这样做,Cython 会在编译时告诉您存在“未定义的全局名称”。

单元格的全局名称(顶层函数、类、变量和模块)随后会被加载到 Notebook 的全局命名空间中。因此,最终的效果就像您执行了一个 Python 单元格一样。

Cython 魔法命令允许的额外参数列在下面。您也可以在 IPython 或 Jupyter Notebook 中输入 `%%cython? 来查看它们。

-a, –annotate

生成源代码的彩色 HTML 版本。

–annotate-fullc

生成源代码的彩色 HTML 版本,其中包含完整的生成的 C/C++ 代码。

-+, –cplus

输出 C++ 文件而不是 C 文件。

-f, –force

强制编译新的模块,即使源代码之前已经编译过。

-3

选择 Python 3 语法

-2

选择 Python 2 语法

-c=COMPILE_ARGS, –compile-args=COMPILE_ARGS

通过 extra_compile_args 传递给编译器的额外标志。

–link-args LINK_ARGS

通过 extra_link_args 传递给链接器的额外标志。

-l LIB, –lib LIB

添加一个库来链接扩展程序(可以多次指定)。

-L dir

向库目录列表添加路径(可以多次指定)。

-I INCLUDE, –include INCLUDE

向包含目录列表添加路径(可以多次指定)。

-S, –src

向源文件列表添加路径(可以多次指定)。

-n NAME, –name NAME

指定 Cython 模块的名称。

–pgo

在 C 编译器中启用配置文件引导优化。编译单元格两次,并在两次编译之间执行它以生成运行时配置文件。

–verbose

打印调试信息,例如生成的 .c/.cpp 文件位置和调用的确切 gcc/g++ 命令。

Cython 缓存

Cython 缓存用于存储经过 Cython 处理的 .c/.cpp 文件,以避免对之前经过 Cython 处理的文件运行 Cython 编译器。

注意

只有 .c/.cpp 文件会被缓存。C 编译器每次都会运行。为了避免执行 C 编译器,需要使用像 ccache 这样的工具。

Cython 缓存默认情况下是禁用的,但可以通过 cythonize()cache 参数启用。

from setuptools import setup, Extension
from Cython.Build import cythonize

extensions = [
    Extension("*", ["lib.pyx"]),
]

setup(
    name="hello",
    ext_modules=cythonize(extensions, cache=True)
)

默认情况下,缓存文件会在以下路径中按以下顺序搜索

  1. CYTHON_CACHE_DIR 环境变量中指定的路径,

  2. 在 MacOS 上的 ~/Library/Caches/Cython 和 posix 上的 XDG_CACHE_HOME/cython(如果定义了 XDG_CACHE_HOME 环境变量),

  3. 否则为 ~/.cython

编译器选项

编译器选项可以在调用 cythonize() 之前在 setup.py 中设置,例如:

from setuptools import setup

from Cython.Build import cythonize
from Cython.Compiler import Options

Options.docstrings = False

setup(
    name = "hello",
    ext_modules = cythonize("lib.pyx"),
)

以下是可用的选项

Cython.Compiler.Options.docstrings = True

是否在 Python 扩展程序中包含文档字符串。如果为 False,二进制文件的大小会更小,但任何类或函数的 __doc__ 属性将是一个空字符串。

Cython.Compiler.Options.embed_pos_in_docstring = False

将源代码位置嵌入函数和类的文档字符串中。

Cython.Compiler.Options.generate_cleanup_code = False

在每个模块退出时对全局变量进行 Decref 操作以进行垃圾回收。0:无,1+:内部对象,2+:cdef 全局变量,3+:类型对象。主要用于减少 Valgrind 中的噪音,因为它通常在进程退出时执行(此时所有内存都会被回收)。请注意,当启用相应级别时,直接或间接执行的清理代码如果使用全局变量或类型,可能不再安全,因为无法保证对象(引用计数)的清理顺序。由于活动引用和引用循环,顺序可能会发生变化。

Cython.Compiler.Options.clear_to_none = True

tp_clear() 是否应该将对象字段设置为 None 而不是将它们清除为 NULL?

Cython.Compiler.Options.annotate = False

为调试和优化目的生成输入源文件的带注释的 HTML 版本。这与 cythonize() 中的 annotate 参数具有相同的效果。

Cython.Compiler.Options.fast_fail = False

这将在发生第一个错误时中止编译,而不是尝试继续并打印更多错误消息。

Cython.Compiler.Options.warning_errors = False

将所有警告转换为错误。

Cython.Compiler.Options.error_on_unknown_names = True

将未知名称视为错误。Python 在运行时遇到未知名称时会引发 NameError,而此选项会将它们视为编译时错误。如果您希望完全兼容 Python,则应禁用此选项以及“cache_builtins”。

Cython.Compiler.Options.error_on_uninitialized = True

将未初始化的局部变量引用视为编译时错误。Python 在运行时会引发 UnboundLocalError,而此选项会将它们视为编译时错误。请注意,此选项仅影响“python 对象”类型的变量。

Cython.Compiler.Options.convert_range = True

i 是 C 整数类型并且可以确定方向(即步长的符号)时,这将把形式为 for i in range(...) 的语句转换为 for i from ...。警告:如果范围导致对 i 的赋值溢出,这可能会改变语义。具体来说,如果设置了此选项,将在进入循环之前引发错误,而没有此选项,循环将执行直到遇到溢出值。

Cython.Compiler.Options.cache_builtins = True

在模块初始化时仅执行一次内置名称的查找。这将阻止模块导入,如果在初始化期间找不到它使用的内置名称。默认值为 True。请注意,在 Python 3.x 中构建时,Cython 会自动将一些旧的内置函数从其 Python 2 名称重新映射到其 Python 3 名称,因此即使启用此选项,它们也不会妨碍。

Cython.Compiler.Options.gcc_branch_hints = True

生成分支预测提示以加速错误处理等。

Cython.Compiler.Options.lookup_module_cpdef = False

启用此选项以允许编写 your_module.foo = ... 来覆盖定义,如果 cpdef 函数 foo,则需要额外的字典查找才能调用。如果为 false,它只生成 Python 包装器,不进行覆盖检查。

Cython.Compiler.Options.embed = None

是否嵌入 Python 解释器,用于制作独立的可执行文件或从外部库调用。这将提供一个 C 函数,该函数初始化解释器并执行此模块的主体。有关具体示例,请参阅 此演示。如果为 true,则初始化函数为 C main() 函数,但此选项也可以设置为非空字符串以显式提供函数名称。默认值为 False。

Cython.Compiler.Options.cimport_from_pyx = False

允许从 pyx 文件 cimport,而无需 pxd 文件。

Cython.Compiler.Options.buffer_max_dims = 8

缓冲区的最大维度数 - 设置为低于 numpy 中的维度数,因为切片按值传递,并且涉及大量复制。

Cython.Compiler.Options.closure_freelist_size = 8

要保存在空闲列表中的函数闭包实例数(0:无空闲列表)

编译器指令

编译器指令是影响 Cython 代码行为的指令。以下是当前支持的指令列表

binding (True / False)

控制自由函数的行为更像 Python 的 CFunctions(例如 len()),或者,当设置为 True 时,更像 Python 的函数。启用后,函数将在作为类属性查找时绑定到实例(因此得名),并将模拟 Python 函数的属性,包括自省,如参数名称和注释。

默认值为 True。

Changed in version 3.0.0: 默认值从 False 更改为 True

boundscheck (True / False)

如果设置为 False,Cython 可以自由地假设代码中的索引操作([] 运算符)不会导致任何 IndexError 被抛出。列表、元组和字符串仅在索引可以确定为非负数(或 wraparound 为 False)时才会受到影响。如果设置为 False,则通常会触发 IndexError 的条件可能会导致段错误或数据损坏。默认值为 True。

wraparound (True / False)

在 Python 中,数组和序列可以相对于末尾进行索引。例如,A[-1] 索引列表的最后一个值。在 C 中,不支持负索引。如果设置为 False,则允许 Cython 不检查也不正确处理负索引,这可能会导致段错误或数据损坏。如果启用边界检查(默认值,请参见上面的 boundschecks),负索引通常会为 Cython 自身评估的索引引发 IndexError。但是,这些情况可能难以在用户代码中识别,以区分它们与由底层 Python 数组或序列对象评估的索引或切片,因此继续支持环绕索引。因此,最安全的方法是仅将此选项应用于根本不处理负索引的代码。默认值为 True。

initializedcheck (True / False)
如果设置为 True,Cython 会检查
  • 每当访问或分配内存视图的元素时,它是否已初始化。

  • 当访问 C++ 类时,它是否已初始化(仅当 cpp_locals 处于打开状态时)。

将其设置为 False 会禁用这些检查。默认值为 True。

nonecheck (True / False)

如果设置为 False,Cython 可以自由地假设对类型为扩展类型的变量的本机字段访问或对缓冲区变量的缓冲区访问永远不会在变量设置为 None 时发生。否则,将插入检查并引发相应的异常。出于性能原因,默认情况下这是关闭的。默认值为 False。

overflowcheck (True / False)

如果设置为 True,则在 C 整数算术运算溢出时引发错误。会产生适度的运行时开销,但比使用 Python 整数快得多。默认值为 False。

overflowcheck.fold (True / False)

如果设置为 True,并且 overflowcheck 为 True,则检查嵌套的、无副作用的算术表达式的溢出位一次,而不是在每一步都检查。根据编译器、体系结构和优化设置,这可能会提高或降低性能。可以在 Demos/overflow_perf.pyx 中找到一组简单的基准测试。默认值为 True。

embedsignature (True / False)

如果设置为 True,Cython 将在所有对 Python 可见的函数和类的文档字符串中嵌入调用签名的文本副本。因此,像 IPython 和 epydoc 这样的工具可以显示签名,否则在编译后无法检索。默认值为 False。

embedsignature.format (c / python / clinic)

如果设置为 c,Cython 将生成保留 C 类型声明和 Python 类型注释的签名。如果设置为 python,Cython 将尽力在嵌入式签名中使用纯 Python 类型注释。对于没有 Python 类型注释的参数,C 类型将映射到最接近的 Python 类型等效项(例如,C short 映射到 Python int 类型,C double 映射到 Python float 类型)。具体输出和类型映射是实验性的,可能会随着时间的推移而改变。 clinic 格式生成与 CPython 的 Argument Clinic 工具理解的签名兼容的签名。CPython 运行时从文档字符串中剥离这些签名,并将它们转换为 __text_signature__ 属性。这在使用 binding=False 时主要有用,因为使用 binding=True 生成的 Cython 函数没有(也不需要) __text_signature__ 属性。默认值为 c

cdivision (True / False)

如果设置为 False,Cython 将调整余数和商运算符的 C 类型以匹配 Python 整数的类型(当操作数符号相反时不同),并在右操作数为 0 时引发 ZeroDivisionError。这会导致高达 35% 的速度损失。如果设置为 True,则不执行任何检查。请参见 CEP 516。默认值为 False。

cdivision_warnings (True / False)

如果设置为 True,Cython 将在执行除法运算时使用负操作数发出运行时警告。请参见 CEP 516。默认值为 False。

cpow (True / False)

cpow 修改 a**b 的返回值类型,如下表所示

cpow 行为

a 的类型

b 的类型

cpow==True

cpow==False

C 整数

负整数编译时常量

返回值类型为 C double

返回值类型为 C double(特殊情况)

C 整数

C 整数(编译时已知 >= 0)

返回值类型为整数

返回值类型为整数

C 整数

C 整数(可能为负数)

返回值类型为整数

返回值类型为 C double(注意 Python 会动态选择 intfloat,而 Cython 不会)

C 浮点数

C 整数

返回值类型为浮点数

返回值类型为浮点数

C 浮点数(或 C 整数)

C 浮点数

返回值类型为浮点数,如果结果为复数则结果为 NaN

C 实数或复数,但会降低速度

cpow==True 的行为在很大程度上保持结果类型与操作数类型相同,而 cpow==False 的行为遵循 Python,根据输入返回灵活的类型。

在 Cython 3.0 中引入,默认值为 False;在此之前,行为与 cpow=True 版本相同。

always_allow_keywords (True / False)

禁用时,在构造接受零个或一个参数的函数/方法时使用 METH_NOARGSMETH_O 签名。对特殊方法和具有多个参数的函数没有影响。 METH_NOARGSMETH_O 签名提供稍微更快的调用约定,但禁止使用关键字。

c_api_binop_methods (True / False)

启用时,使特殊二元运算符方法(__add__ 等)的行为符合低级 C-API 插槽语义,即只有一个方法同时实现正常运算符和反向运算符。这曾经是 Cython 0.x 中的默认行为,现在已被 Python 语义取代,即 Cython 3.x 及更高版本中的默认行为为 False

profile (True / False)

将 Python 分析器的钩子写入编译后的 C 代码中。默认值为 False。

linetrace (True / False)

将 Python 分析器或覆盖率报告的行跟踪钩子写入编译后的 C 代码中。这也会启用分析。默认值为 False。请注意,生成的模块实际上不会使用行跟踪,除非您还将 C 宏定义 CYTHON_TRACE=1 传递给 C 编译器(例如,使用 setuptools 选项 define_macros)。定义 CYTHON_TRACE_NOGIL=1 以包含 nogil 函数和部分。

infer_types (True / False)

推断函数体中未类型化变量的类型。默认值为 None,表示仅允许安全(语义上不变)的推断。特别是,为在算术表达式中使用的变量推断 *整数* 类型被认为是不安全的(由于可能发生溢出),必须明确请求。

language_level (2/3/3str)

全局设置用于模块编译的 Python 语言级别。默认情况下,Cython 3.x 与 Python 3 兼容,Cython 0.x 与 Python 2 兼容。要启用 Python 3 源代码语义,请在模块开头将其设置为 3(或 3str),或将 “-3” 或 “–3str” 命令行选项传递给编译器。对于 Python 2 语义,请相应地使用 2 和 “-2”。 3str 选项启用 Python 3 语义,但不会更改 str 类型,并且当编译后的代码在 Python 2.x 中运行时,不会将无前缀的字符串文字更改为 unicode。语言级别 2 由于 int/long 模糊性,会忽略 x: int 类型注释。请注意,cimported 文件会从正在编译的模块继承此设置,除非它们明确设置自己的语言级别。包含的源文件始终继承此设置。

c_string_type (bytes / str / unicode)

全局设置从 char* 或 std::string 隐式强制转换的类型。

c_string_encoding (ascii, default, utf-8 等)

全局设置在将 char* 或 std:string 隐式强制转换为 unicode 对象时使用的编码。从 unicode 对象到 C 类型的强制转换仅在设置为 asciidefault 时允许,后者在 Python 3 中为 utf-8,在 Python 2 中几乎总是 ascii。

type_version_tag (True / False)

通过设置类型标志 Py_TPFLAGS_HAVE_VERSION_TAG 来启用 CPython 中扩展类型的属性缓存。默认值为 True,这意味着缓存已为 Cython 实现的类型启用。为了在类型需要在内部处理其 tp_dict 而无需关注缓存一致性的罕见情况下显式禁用它,可以将此选项设置为 False。

unraisable_tracebacks (True / False)

是否在抑制不可引发异常时打印跟踪信息。

iterable_coroutine (True / False)

PEP 492 指定 async-def 协程不能是可迭代的,以防止在非异步上下文中意外使用。但是,这使得编写在 Cython 中使用 async-def 协程但需要与使用旧的 yield-from 语法的异步 Python 代码交互的向后兼容代码变得困难且效率低下,例如 Python 3.5 之前的 asyncio。此指令可以在模块中应用,也可以选择性地作为装饰器应用于 async-def 协程,以使受影响的协程可迭代,从而直接与 yield-from 相互操作。

annotation_typing (True / False)

使用函数参数注释来确定变量的类型。默认值为 True,但可以禁用。由于 Python 不强制执行注释中给出的类型,因此设置为 False 会提高与 Python 代码的兼容性。从 Cython 3.0 开始,annotation_typing 可以按函数或按类设置。

emit_code_comments (True / False)

将原始源代码逐行复制到生成的代码文件中的 C 代码注释中,以帮助理解输出。这对于覆盖分析也是必需的。

cpp_locals (True / False)

通过允许 C++ 变量“未绑定”而不是始终在函数开始时默认构造它们,使 C++ 变量的行为更像 Python 变量。有关更多详细信息,请参阅 cpp_locals 指令

legacy_implicit_noexcept (True / False)

启用后,cdef 函数默认情况下不会传播引发的异常。因此,该函数的行为方式与使用 noexcept 关键字声明时相同。有关详细信息,请参阅 错误返回值。将此指令设置为 True 将导致 Cython 3.0 具有与 Cython 0.x 相同的语义。此指令仅添加以帮助迁移在 Cython 3 之前编写的旧代码。它将在将来的版本中删除。

可配置优化

optimize.use_switch (True / False)

是否将链式 if-else 语句(包括 if x == 1 or x == 2: 之类的语句)扩展为 C switch 语句。如果有很多值,这可能会带来性能优势,但如果存在任何重复值(在 Cython 编译时可能无法检测到所有 C 常量),则会导致编译器错误。默认值为 True。

optimize.unpack_method_calls (True / False)

Cython 可以生成代码,该代码在调用时乐观地检查 Python 方法对象,并解包底层函数以直接调用它。这可以显着加快方法调用速度,尤其是对于内置函数,但在某些情况下,如果猜测完全错误,也可能会对性能产生轻微的负面影响。禁用此选项还可以减小代码大小。默认值为 True。

警告

所有警告指令都采用 True / False 作为选项来打开/关闭警告。

warn.undeclared (默认值为 False)

警告任何未经 cdef 声明而隐式声明的变量。

warn.unreachable (默认值为 True)

警告关于静态确定为不可达的代码路径,例如无条件地返回两次。

warn.maybe_uninitialized (默认值为 False)

警告关于使用条件下未初始化的变量。

warn.unused (默认值为 False)

警告关于未使用的变量和声明。

warn.unused_arg (默认值为 False)

警告关于未使用的函数参数。

warn.unused_result (默认值为 False)

警告关于对相同名称的未使用的赋值,例如 r = 2; r = 1 + 2

warn.multiple_declarators (默认值为 True)

警告关于在同一行上声明多个变量,其中至少有一个指针类型。例如 cdef double* a, b - 就像在 C 中一样,它将 a 声明为指针,b 声明为值类型,但可能会被误解为声明两个指针。

show_performance_hints (默认值为 True)

在编译期间显示性能提示,指向代码中可能导致性能下降的位置。请注意,性能提示不是警告,因此上述以 warn. 开头的指令不会影响它们,并且当启用“警告错误”时,它们不会触发失败。

如何设置指令

全局

可以通过文件顶部附近的特殊头注释设置编译器指令,如下所示

# cython: language_level=3, boundscheck=False

该注释必须出现在任何代码之前(但可以出现在其他注释或空格之后)。

还可以使用 -X 开关在命令行上传递指令

$ cython -X boundscheck=True ...

在命令行上传递的指令将覆盖在头注释中设置的指令。

本地

对于本地块,需要 cimport 特殊的内置 cython 模块

#!python
cimport cython

然后可以使用指令作为装饰器或在 with 语句中,如下所示

#!python
@cython.boundscheck(False) # turn off boundscheck for this function
def f():
    ...
    # turn it temporarily on again for this block
    with cython.boundscheck(True):
        ...

警告

这两种设置指令的方法不受使用 -X 选项在命令行上覆盖指令的影响。

setup.py

编译器指令也可以在 setup.py 文件中设置,方法是将关键字参数传递给 cythonize

from setuptools import setup
from Cython.Build import cythonize

setup(
    name="My hello app",
    ext_modules=cythonize('hello.pyx', compiler_directives={'embedsignature': True}),
)

这将覆盖 compiler_directives 字典中指定的默认指令。请注意,如上所述的显式逐文件或本地指令优先于传递给 cythonize 的值。

跟踪中的 C 行号

为了提供更详细的调试信息,Cython 模块的 Python 跟踪显示了异常起源(或传播)的 C 行。此功能并非完全免费,并且会明显增加 C 编译时间,并将二进制扩展模块的大小增加 0-5%。因此,它在 Cython 3.1 中被禁用,并且可以使用 C 宏进行控制。

  • CYTHON_CLINE_IN_TRACEBACK=1 始终在跟踪中显示 C 行号,

  • CYTHON_CLINE_IN_TRACEBACK=0 从不在跟踪中显示 C 行号,

除非使用此宏完全禁用该功能,否则还支持在运行时启用和禁用该功能,这会导致更长的 C 编译时间和更大的扩展模块。这可以使用 C 宏进行配置

CYTHON_CLINE_IN_TRACEBACK_RUNTIME=1

然后要在运行时更改行为,可以在加载 Cython 模块后导入特殊模块 cython_runtime,并在该模块中设置属性 cline_in_traceback 为 true 或 false,以控制运行 Cython 代码时的行为

import cython_runtime
cython_runtime.cline_in_traceback = True

raise ValueError(5)

如果构建设置或 CFLAGS 都没有定义这两个宏,则该功能将被禁用。

在 Cython 3.0 及更早版本中,Cython 编译器选项 c_line_in_traceback(作为 setup.py 中的 cythonize 的参数传递)或命令行参数 --no-c-in-traceback 也可以用来禁用此功能。从 Cython 3.1 开始,这仍然是可能的,但应该迁移到使用 C 宏。在 Cython 3.1 之前,CYTHON_CLINE_IN_TRACEBACK 宏已经按描述工作,但需要 Cython 选项来消除编译时成本。

C 宏定义

Cython 有许多 C 宏,可用于控制编译。通常,这些宏将使用 setup.py 中的 extra_compile_args 设置(例如 extra_compile_args=['-DCYTHON_USE_TYPE_SPECS=1']),但也可以通过其他方式设置,例如使用 CFLAGS 环境变量。

除非您选择显式覆盖这些宏,否则 Cython 会自动将这些宏设置为合理的默认值,因此它们是大多数用户可以忽略的工具。并非所有宏组合都是兼容的或经过测试的,有些宏会改变其他宏的默认值。它们按从最重要的到最不重要的顺序列出如下

CYTHON_LIMITED_API

启用 Cython 的实验性 Limited API 支持,这意味着一个编译后的模块可以被多个 Python 解释器版本使用(以牺牲一些性能为代价)。在这个阶段,许多功能在 Limited API 中不起作用。如果您使用此宏,您还应该设置宏 Py_LIMITED_API 为您要支持的最低 Python 版本的版本十六进制(>=3.7)。0x03070000 将支持 Python 3.7 及更高版本。

CYTHON_PEP489_MULTI_PHASE_INIT

使用 PEP489 中描述的多阶段模块初始化。这提高了 Python 兼容性,尤其是在运行代码的初始导入时,因为它使 __file__ 等属性可用。因此,在支持的情况下,它默认情况下处于启用状态。

CYTHON_USE_MODULE_STATE

将模块数据存储在与模块对象关联的结构体上,而不是作为 C 全局变量。优点是应该可以多次导入同一个模块(例如,在不同的子解释器中)。目前,这处于实验阶段,并非所有数据都已移动。它还要求 CYTHON_PEP489_MULTI_PHASE_INIT 关闭 - 我们计划在未来消除此限制。

CYTHON_USE_TYPE_SPECS

cdef classes 定义为 “堆类型” 而不是“静态类型”。实际上,从用户的角度来看,这并没有改变太多,但它是实现 Limited API 支持所必需的。

CYTHON_EXTERN_C

与其他宏略有不同,它控制 cdef public 函数如何显示给 C++ 代码。有关完整详细信息,请参阅 C++ 公共声明

CYTHON_CLINE_IN_TRACEBACK

控制是否在跟踪中显示 C 行号。有关完整描述,请参阅 跟踪中的 C 行号

还有一系列宏可以关闭各种优化或语言功能。在正常情况下,Cython 会根据您正在编译的 Python 版本自动启用这些功能,因此无需使用它们来尝试启用额外的优化 - 所有支持的优化默认情况下都已启用。如果您正在尝试在新的和不受支持的 Python 解释器中使 Cython 工作,这些功能主要与您通常希望将其设置为 0 以禁用优化相关。为了完整起见,它们列在下面,但默认情况下隐藏,因为大多数用户对更改它们不感兴趣。