Skip to content

源文件和编译

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

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

与 Python 不同,Cython 代码必须被编译。这发生在两个阶段:

  • A .pyx 文件由 Cython 编译为 .c 文件。
  • .c 文件由 C 编译器编译为 .so 文件(或 Windows 上的 .pyd 文件)

一旦编写了 .pyx/.py 文件,就可以通过几种方法将其转换为扩展模块。

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

除了Cython之外,还有一些处理 .pyx 的工具,比如: - Linting: https://pypi.org/project/cython-lint/

命令行编译

有两种用命令行编译的方法。

  • cython 命令获取 .py.pyx 文件并将其编译为 C / C ++文件。
  • cythonize 命令获取 .py.pyx 文件并将其编译为 C / C ++文件。然后,它将 C / C ++文件编译为可直接从 Python 导入的扩展模块。

使用 cython 命令进行编译

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

$ cython primes.pyx

这将生成一个名为 primes.c 的文件,然后需要使用适合您系统版本的选项来使用 C 编译器编译该文件以生成扩展模块。有关这些选项,请查看官方 Python 文档。

另一种可能更好的方法是使用 Cython 提供的 distutils 扩展。这种方法的好处是它将提供系统特定的编译选项,就像一个精简的自动工具。

使用 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 (Windows 的 yourmod.pyd )文件写入目标目录,您的模块 yourmod 可以像任何其他 Python 模块一样导入。请注意,如果您不依赖于 cythonize 或 distutils,您将不会自动受益于 CPython 为消除歧义而生成的特定于平台的文件扩展名,例如 CPython 3.5 的常规 64 位 Linux 安装上的 yourmod.cpython-35m-x86_64-linux-gnu.so

基本 setup.py

Cython提供的setup扩展允许你在配置文件中将 .pyx 文件直接传递给 Extension 构造器。

如果你有一个 Cython 文件要转换为编译的扩展模块,比如文件名 example.pyx,关联的 setup.py 将是:

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

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

如果你以这么方式直接在Cython上生成模块,你可能还需要告知pip为了执行这个 setup.py 需要cython、遵循 PEP-518 https://www.python.org/dev/peps/pep-0518/ ,创建一个 pyproject.toml ,并且至少包含以下内容:

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

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

$ python setup.py build_ext --inplace

配置 C-Build

如果您在非标准位置包含文件,你可以给 cythonize 传递一个 include_path 参数:

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

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

通常,提供 C 级 API 的 Python 包提供了查找必要包含文件的方法,例如,对于 NumPy而言:

include_path = [numpy.get_include()]

请注意:使用内存视图或使用 import numpy 导入 NumPy 并不意味着您必须添加 NumPy 包含文件的路径。只有在使用 cimport numpy 时才需要添加此路径。

尽管如此,您仍然会收到编译器中的以下警告,因为 Cython 没有禁用已弃用的过时的 Numpy API:

.../include/numpy/npy_1_7_deprecated_api.h:15:2: warning: #warning "Using deprecated NumPy API, disable it by " "#defining NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION" [-Wcpp]

在Cython3.0中,你可以通过在生成模块时定义C宏 NPY_NO_DEPRECATED_APINPY_1_7_API_VERSION 的方式来去掉这个警告信息。

# distutils: define_macros=NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION

或者这样:

Extension(
    ...,
    define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")],
)

在使用旧版本的Cython时,设置这个宏会导致编译为C文件的行为失败,因为Cython生成了使用过时的C-API的代码。但是在比较新版本的NumPy(包括1.18.x)中,这个警告信息没有任何实质的影响。你可以忽略这条警告,直到你或者你写的包的用户使用更新的去掉这个过时了很久的API的比较新的NumPy版本 -- 同时你还必须使用Cython3.0 。 因此,你越早切换到Cython3.0版本,对于那些使用你写的包的用户来说越好。

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

from distutils.core import setup
from distutils.extension import Extension
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),
)

请注意,使用 setuptools 时,您应该在 Cython 之前导入它,不然的话Cython和setuptools可能会在使用什么类上冲突。

另请注意,如果使用 setuptools 而不是 distutils,则运行 python setup.py install 时的默认操作是创建一个压缩的 egg 文件。当您尝试从依赖的包中使用它们时,这些文件无法用于 cimport pxd 文件。为防止这种情况,请在 setup() 的参数中包含 zip_safe=False

如果您的选项是静态的(例如,您不需要调用 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 distutils.core import setup
from Cython.Build import cythonize
from distutils.extension import Extension

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

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

setup(
    ext_modules=cythonize(extensions)
)

Extension 类可以接收很多选项,可以在 distutils 文档中找到更全面的解释。要了解的一些有用选项是 include_dirslibrarieslibrary_dirs ,它们指定链接到外部库时在哪里可以找到 .h 和库文件。

有时这还不够,你需要更精细的自定义 setuptools Extension。为此,您可以提供自定义函数 create_extension ,以便在 Cython 处理源,依赖项和 # distutils 指令之后但在生成Cython文件之前创建最终的 Extension 对象。该函数有 2 个参数 templatekwds ,其中 template 是作为 Cython 输入的 Extension 对象,kwds是一个包含了所有用于创建 Extension 的关键字参数的字典 dict 。函数 create_extension 必须返回 二元元组 (extension, metadata) ,其中 extension 是创建的Extensionmetadata 是元数据 -- 会在生成的 C 文件的顶部被以JSON格式输出。此元数据仅用于调试目的,因此您可以在其中放置任何内容(只要它可以转换为 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)
    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对象列表。传递一个Extension对象列表允许您通过常规 distutils 选项单独配置每个Extension对象。您还可以传递具有 glob 模式作为其源的Extension对象。然后,cythonize 将解析模式并为每个匹配的文件创建Extension对象的副本。
  • exclude - 将 glob 模式作为 module_list 传递时,可以通过将某些模块名称传递给 exclude 选项来明确排除它们。
  • nthreads - 并行编译的并发构建数(需要 multiprocessing 模块)。
  • aliases - 如果你想使用像 # distutils: ... 这样的编译器指令,但只能在编译时(运行 setup.py 时)知道要使用哪些值,你可以使用别名和在调用 cythonize() 时传递一个将包含这些别名映射到 Python 字符串的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 模块重新编译 ,即使时间戳数据显示不需要重新编译。
  • language - 要全局启用 C ++模式,可以传递 language='c++' 。否则,将在每个文件级别根据编译器指令确定。这仅影响基于文件名找到的模块。传入 cythonize() 的Extension实例不会受到影响。建议使用编译器指令 # distutils: language = c++ 而不是此选项。
  • exclude_failures - 如果你想要大概的“尝试编译” -- 忽略编译失败并简单地排除失败的扩展项,你可以传递 exclude_failures=True 。请注意,这仅对于编译 .py 文件是有意义的,因为这些文件也可以在不编译的情况下使用。
  • show_all_warnings - 默认情况,不是所有的Cython警告都会被打印出来。设置该参数为True将会打印所有的警告消息。
  • annotate - 如果 True ,将为每个编译的 .pyx.py 文件生成一个 HTML 文件。与普通的 C 代码相比,HTML 文件指示了每个源代码行中的 Python 交互量。它还允许您查看为每行 Cython 代码生成的 C / C ++代码。在优化速度函数以及确定何时 释放 GIL时,此报告非常有用:通常,nogil 代码块可能只包含“白色”的代码。参见 确定添加类型的位置Primes的位置。(译者注:“白色”代码是在生成的html注解文件中显示为白色底色的代码部分,这部分代码不包含任何对Python对象的操作,因此可以在释放全局解释器锁的代码块中使用。)
  • annotate-func - 如果为 True 则会输出一个用颜色标注的、包含完整生成C/C++代码的HTML文件。
  • compiler_directives - 允许在 setup.py 中像这样设置编译器指令: compiler_directives={'embedsignature': True}。参见本文档的编译器指令 这一节。
  • depfile - 如果为 True 则会为源文件输出depfiles

一个包里有多个 Cython 文件

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

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

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

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

setup(
    ext_modules = cythonize(extensions)
)

分发 Cython 模块

强烈建议您把生成的.c文件和Cython 源一起分发,以便用户可以安装模块而无需使用 Cython。

还建议在您分发的版本中默认不启用 Cython 编译。即使用户安装了 Cython,他/她可能也不想仅仅使用它来安装模块。此外,安装的版本可能与您使用的版本不同,并且可能无法正确编译您的源文件。

这只是意味着您附带的setup.py文件将只是生成的 .c 文件中的正常 distutils 文件,对于我们将要使用的基本示例:

from distutils.core import setup
from distutils.extension import Extension

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

可以更改扩展模块的源文件的文件扩展名,并与 cythonize() 结合使用:

from distutils.core import setup
from distutils.extension import Extension

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
)

如果您有许多 Extension 并希望在声明时避免额外复杂工作,您可以使用普通的 Cython 源声明它们,然后在不使用 Cython 时调用以下函数而不是 cythonize() 来调整 Extensions 中的源列表:

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

另一个选择是使 Cython 成为系统的安装依赖项,并使用 Cython 的 build_ext 模块作为构建过程的一部分运行 cythonize

setup(
    extensions = [Extension("*", ["*.pyx"])],
    cmdclass={'build_ext': Cython.Build.build_ext},
    ...
)

这依赖于 pip 知道 Cython 是一个安装依赖项 -- 通过配置一个 pyproject.toml 文件来实现。

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

如果你想把你的库的C级别的接口暴露给其他库引用,使用 package_data 来安装.pxd文件,例如:

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

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

请记住,如果使用 setuptools 而不是 distutils,则运行 python setup.py install 时的默认操作是创建一个压缩的 egg 文件,当您尝试从依赖包中使用它们时,这些文件无法用于 cimport pxd文件 。为防止这种情况发生,请在 setup() 的参数中包含 zip_safe=False

集成多个模块

在某些情况下,将多个 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_ 作为前缀。

然后,对于每个模块,用如下方式声明其模块的 init 函数,(把 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

这允许您在 Py​​thon 尝试导入的每个 .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(如上所述)。

Parameters:

  • pyximport - 如果设置为 False,则不尝试导入 .pyx 文件。
  • pyimport - 您可以传递 pyimport=True 以在元路径中安装 .py 导入钩子。但请注意,它处于实验阶段,对于某些 .py 文件和软件包根本不起作用,并且由于搜索和编译而会大大减慢您的导入速度。使用风险由您自己承担。
  • build_dir - 默认情况下,已编译的模块最终将位于用户主目录的 .pyxbld 目录中。传递一个不同的路径作为 build_dir 将覆盖。
  • build_in_temp - 如果 False ,将在本地生成 C 文件。在复杂的依赖项下工作和调试将因此变得更加容易。这主要可能会干扰同名的现有文件。
  • setup_args - 分发函数的参数的字典。见 distutils.core.setup()
  • reload_support - 支持动态 reload(my_module) ,例如在 Cython 代码更改后需要重新导入。当无法覆盖先前加载的模块文件时,额外的文件会因此 <so_path>.reloadNN
  • load_py_module_on_import_failure - 如果.py文件的编译成功,但后续导入由于某种原因失败,请使用普通.py模块而不是编译模块重试导入。请注意,这可能会导致在导入期间更改系统状态的模块出现不可预测的结果,因为第二次导入将在导入已编译模块失败后系统处于的任何状态下重新运行这些修改。
  • inplace - 在源文件同目录下安装已编译的模块(Linux 的 .so 和 Mac 的 .pyd )。
  • language_level - 要使用的(Python)语言级别:2 或 3。默认情况下,对 .py 文件使用当前的 Python 运行时语言级别,对 .pyx 文件使用 Py2。

依赖处理

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

限制

pyximport 不使用 cythonize() 。因此,不可能在 Cython 文件的顶部使用编译器指令或将 Cython 代码编译为 C ++。

Pyximport 不会让您控制 Cython 文件的编译方式。通常情况下,默认值可以接受。但是如果你想用一半C语言、一半Cython的编写你的程序并将它们构建到一个库中,你可能会遇到问题。

Pyximport 不会隐藏导入过程生成的 Distutils / 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

被装饰的函数不可以有类型声明。它们的类型是根据传递给函数的值自动确定的,因此可以编译为一个或者多个针对特定参数类型的函数。(译者注:有点像C++中自动生成特定类型代码的模板)

一个可以执行的例子是这样:

import cython

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

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

将会输出以下结果:

35
8

用 Sage 编译

Sage Notebook允许通过在单元格顶部键入并执行 %cython 来方便地编辑和编译 Cython 代码。 Cython 单元格中定义的变量和函数将导入到正在运行的会话中。有关详细信息,请查看 Sage 文档

您可以通过声明下面列出的这些指令来定制 Cython 编译器的行为(参考Jupyter Notebook)。

使用 Jupyter Notebook进行编译

使用 Cython 可以在笔记本单元中编译代码。为此你需要加载 Cython 魔法指令:

%load_ext cython

然后,您可以通过在单元格上方写入 %%cython 来将单元格定义 Cython 单元格。像这样:

%%cython

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

请注意,每个单元格将编译为单独的扩展模块。因此,如果您在 Cython 单元格中使用包,则必须在同一单元格中导入此包。在先前的单元格中导入包是不够的。如果你不遵守,Cython 会告诉你编译时有“未定义的全局名称”。

然后将单元格的全局名称(顶级函数,类,变量和模块)加载到笔记本的全局命名空间中。所以最后,它的行为就好像你执行了一个 Python 单元格。

下面列出了 Cython 魔法指令允许接受的其他参数。您也可以通过在 IPython 或 Jupyter 笔记本中键入 `%%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 添加 src 文件列表的路径(可以多次指定)。
-n NAME, -name NAME 指定 Cython 模块的名称。
-pgo 在 C 编译器中启用配置文件引导的优化。编译单元格两次并在其间执行以生成运行时配置文件。
-verbose 打印调试信息,如生成的.c / .cpp 文件位置和调用的精确 gcc / g ++命令。

编译器选项

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

from distutils.core 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 扩展中包含 docstring。如果为 False,则二进制大小将更小,但任何类或函数的__doc__属性将为空字符串。

  • Cython.Compiler.Options.embed_pos_in_docstring = False

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

  • Cython.Compiler.Options.generate_cleanup_code = False

    在退出时为每个模块删除全局变量以进行垃圾回收。 0:无,1 +:实习对象,2 +:cdef 全局,3 +:类型对象主要用于降低 Valgrind 中的噪声,仅在进程退出时执行(当所有内存都将被回收时)。请注意,该选项设置为True的时候直接或者间接的执行使全局变量或者类型无效的清理代码都会变得不安全,因为你无法保证引用对象被清理的顺序。这个顺序可能会根据目前有效的引用和引用循环的情况而改变。

  • 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 中构建时,一些遗留的内置函数会自动从 Python 2 名称重新映射到 Cython 的 Python 3 名称,这样即使启用此选项,它们也不会受到妨碍。

  • Cython.Compiler.Options.gcc_branch_hints = True

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

  • Cython.Compiler.Options.lookup_module_cpdef = False

    如果 cpdef 函数为 foo,则启用此选项以允许写入 your_module.foo = ... 来覆盖定义,代价是每次调用时额外的字典查找。如果为False,它只生成 Python 包装器而不进行覆盖检查。

  • Cython.Compiler.Options.embed = None

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

  • Cython.Compiler.Options.cimport_from_pyx = False

    允许从没有 pxd 文件的 pyx 文件中导入。

  • Cython.Compiler.Options.buffer_max_dims = 8

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

  • Cython.Compiler.Options.closure_freelist_size = 8

    要保留在空闲列表中的函数闭包实例数(0:没有空闲列表)

编译器指令

编译器指令是影响 Cython 代码行为的指令。以下是当前支持的指令列表: 译者注:下面这些选项,当表现的“危险”时,通常可以根据使用场景提供一定的加速效果,但是不熟悉者还是尽量使用默认值。

  • binding (True / False)

    控制自由的函数是否表现的更像是Python的CFunction(比如,len())。启用时,当函数被作为一个类的属性查找到的时候会被绑定给一个实例、且会表现出Python函数的特性,比如函数名称和注解的内部审查。默认为False。

  • boundscheck (True / False)

    是否进行边界检查。设置为False时,Cython假设代码中索引操作([]-运算法)不会引起任何索引错误。仅当索引可以被确定为非负数时(或者 wraparound 为False),列表、元祖、字符串会被影响。当设置为False的时候,通常引起索引错误的问题会引起分段错误或者数据污染。默认为True。( If set to False, Cython is free to assume that indexing operations ([]-operator) in the code will not cause any IndexErrors to be raised. Lists, tuples, and strings are affected only if the index can be determined to be non-negative (or if wraparound is False). Conditions which would normally trigger an IndexError may instead cause segfaults or data corruption if this is set to False. Default is True.

  • wraparound (True / False)

    是否对索引进行包装。在Python中, 数组和序列是索引为和末尾的相对值比如,一个 [-1] 可以代表列表最后一个元素的索引。在C语言中,负数索引是不被支持的。 如果设置为False,那么Cython将不会检查或者纠正负数索引,可能会因此引起分段错误或者数据污染。如果边界检查被启用了(默认情况下,看上面的 boundscheck), 那么Cython计算负数索引时会引起 IndexError。但是在用户代码中很难认识并将它们与潜在的Python数组或者序列对象的索引、切片行为区分开来,因此将会支持对索引进行包装。因此,将该选项设置为False仅对于绝对不接受负数索引的代码是安全的。默认为True。

  • initializedcheck (True / False)

    如果设置为True,Cython将会在一个内存视图的元素被访问或者赋值的时候检查是否被初始化;在cpp_locals 被打开时,检查一个C ++类在被访问时是否已经初始化。设置为False将会禁用这些检查。默认为True。

  • nonecheck (True / False)

    是否进行空值检查。 设置为False时,Cython假设当某变量为 None 的时候,对于被设置为扩展类型的该变量的访问或者是该缓冲区变量的缓冲区访问行为绝对不会发生。否则将会插入一个检查,并引起恰当的异常。处于追求性能的原因,默认情况下为False。

  • overflowcheck (True / False)

    设置为True时,将在C整数溢出的算术操作中引发错误。会产生一个不大的运行速度惩罚,但是还是会比使用Python整数更快。默认为False。 If set to True, raise errors on overflowing C integer arithmetic operations. Incurs a modest runtime penalty, but is much faster than using Python ints. Default is False.

  • overflowcheck.fold (True / False)

    如果设置为True、且上面的 overflowcheck也为True,那么会对嵌套的、没有副作用的算术表达式进行溢出比特位的检查(而不是每一步都检查)。受编译器、架构和编译设置影响,这可能对性能有益也可能有害。一个简单的例子可以在 Demos/overflow_perf.pyx 中找到。默认为True。

  • embedsignature (True / False)

    如果设置为True,Cython会在所有Python可见的函数和类的docstrings中内嵌一份调用签名的文本副本。类似于IPython(译者注:通过 ???)和epydoc这样的工具可以显示这些签名,不然的话无法在编译后被获取。默认为False。

  • embedsignature.format (c / python / clinic)

    当上面 embedsignature 为True时,该选项被用来决定对应的签名的风格。默认为C风格。 如果设置为 c ,Cython将会生成保留C类型声明和Python类型注解的签名。 如果设置为 Python ,Cython将会尽最大的可能用纯Python类型注解的风格写签名。对于没有Python类型注解的参数,其C类型会被映射为最接近的等效Python类型(比如C的 short 会被映射为 Python的 int ,C的 double 会被映射为 Python的 float )。特定的输出和类型的映射关系是实验性质的因此以后可能会改变。 clinic风格会生成兼容CPython的参数clinic工具的签名。CPython运行时会从docstrings中将这些签名剥离出来并翻译为一个 __text_signature__ 的属性。这个主要是在 binding=False 的时候有用,因为binding=True 的时候,生成的Cython函数没有、也不需要 __text_signature__ 属性。

  • cdivision (True / False)

    关于除法的行为是否遵循C的规范。默认为False。 如果设置为False,Cython会调整C类型的取余数和商的运算符行为以对应Python int的行为(在运算数符号不同的时候不同)且当右操作数为0的时候会触发 ZeroDivisionError 。这会造成35%的运行速度惩罚。 如果设置为True,那么没有不会执行任何检查。看CEP 516 译者注:参考Cython的限制中关于 a % b 在两者符号不同时,对于余数 C取第一个符号,Python取第二个符号,因此设置为True时会有速度提升。

  • cdivision_warnings (True / False)

    如果为True,Cython会在对涉及负操作数进行除法时发出一个runtime警告。见CEP 516。默认为False。 译者注:主要是因为C和Python对于涉及负数的除法的结果取符号的行为不同。

  • cpow (True / False)

    cpow 会修改 a**b 返回的类型,如下面这个表格显示的: | a 的类型 | b 的类型 | cpow=True 时 cpow的行为 | cpow=False 时 cpow的行为 | | --- | --- | --- | --- | | C integer | 负整数、编译期为常量 | 返回类型为 C double | 返回值为 C double(特例) | | C integer | C integer(编译时已知为非负数) | 返回类型为 integer | 返回类型为 integer | | C integer | C integer (可以是负数)| 返回类型为 integer | 返回类型是 C double(注意:Python会动态的取 int 或者 float,但是Cython不会) | | C float point浮点数| C integer | 返回值是C float point浮点数 | 返回值是C float point浮点数 | | C float point浮点数 或者 C integer | C float point浮点数 | 返回类型是C float point浮点数,结果可能是复数的时候为NaN | C real 或者 complex(牺牲一些性能) | cpow=True 时的行为极大程度的将结果的类型保留为一个操作数一样,反之将遵循Python的行为根据输入值的不同而灵活地返回一个类型。 在Cython3.0中引入,默认值为False;在引入这个选项之前的行为符合 cpow=True 的版本

  • always_allow_keywords (True / False)

    当设置为False时,在构建函数或者方法的时候采用 METH_NOARGSMETH_O 的签名 -- 代表接收0个或者是一个参数。对于使用超过一个参数的特殊方法没有影响。在Cython0.X中是默认行为,现在被Python 语义替代。在Cython3.0以及以后的版本中默认为False。

  • c_api_binop_methods (True / False)

    当为True的时候,使得二元运算符对应的特殊方法(比如, __add__ 等)表现遵循底层的C级别的API槽的语义,比如对于正常和反向的运算符只保留一个方法。在Cython0.X中曾经是默认行为,现在被Python语义替代。在Cython3.0以及以后的版本中默认为False。(译者注:Python语义会使用 __add____radd__, 也可以参考 扩展类型的特殊方法)

  • profile (True / False)

    将Python性能分析工具写入编译的C代码的写入钩子。默认为False。

  • linetrace (True / False)

    将Python性能工具的行追踪和覆盖率报告写入编译的C代码的写入钩子。启用这个也会启用上面的 profiling。 默认为False。注意:生成的模块不会真的使用行追踪,除非你还将C宏定义 CYTHON_TRACE=1 传递给C编译器(比如在 setuptools 的 define_marcos 选项中)。定义宏 CYTHON_TRACE_NOGIL=1 来添加 nogil 的函数和代码块。

  • infer_types (True / False)

    是否在函数主体中对没有标明类型的变量的静态类型进行推理。 默认值为None,即只进行最安全的推理(即完全不改变语义)。 特别的,对算术表达式中的整数类型的变量进行类型推理是不安全的(因为可能会有溢出的问题),因此必须被显式的请求(即设置为True)。

  • language_level (2/3/3str)

    全局的设置模块编译时使用的Python语言级别(2还是3)。默认行为是在Cython0.X中保留对Python2的兼容,在Cython3.X中使用Python3。如果要启用Python3的源代码语义,在模块文件的开头(接近首行)将这个指令值设置为3 或者 3str,或者在命令行中将 "-3" 或者 "-3str" 这个参数传递给编译器。如果要使用Python2语义,参数对应的改为 2 或者 "-2"。 3str 这个选项启用Python3语义但是在代码在Python2中运行的时候不会把 str 类型 没有加前缀的字符串字面量转换为 unicode。语言级别为2的时候会出于 int/long 语义模糊的原因忽略类似 x: int 的类型注解。注意,cimported的文件,除非显式的声明,将会继承被引入模块的这项设置。被引入(Include)的文件总是会继承这个设置。

  • c_string_type (bytes / str / unicode)

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

  • c_string_encoding (ascii, default, utf-8, etc.)

    全局的设置将 char*std::string 类型隐式强制转换为unicode对象时使用的编码。unicode对象强制转换为C类型仅在该设置为 ascii 或者 default 的时候被允许。 default 在Python3中代表 utf-8, 在Python2中几乎等同于 ascii。

  • type_version_tag (True / False)

    启用通过设置类型标识 Py_TPFLAGS_HAVE_VERSION_TAG 为 CPython中的扩展类型添加的属性缓存。 默认值为True,Cython实现了的类型对应的属性缓存是启用的。在少数情况下,当一个类型需要欺骗内部的 tp_dict 因此不需要在意缓存一致性的时候,可以将这个选项设置为False。

  • unraisable_tracebacks (True / False)

    在压制没有被触发的异常时是否将traceback打印出来。 译者注:在Cython中有些情况下,函数内部的异常会被压制、不触发(仅打印到控制台)

  • iterable_coroutine (True / False)

    PEP 492 要求为了异步函数在非异步的上下文中被误用,异步函数的协程必须不可迭代。但是,这使得编写向后兼容的代码变得艰难、且低效 -- 比如使用Cython中的异步函数协程但是也需要和使用旧版本yield-from语法的Python代码互动,比如Python3.5之前的asyncio。这个指令可以应用在模块中,或者是有选择的作为装饰器写在异步函数的协程中来使得该协程可迭代,从而能够兼容旧版本的yield-from语法。

  • annotation_typing (True / False)

    使用函数参数的注解来决定变量的类型。默认为True(可以被禁止)。 因为Python并不强制写类型注解,设置为False可以更好的和Python代码兼容。从Cython3.0版本起, annotation_typing 可以针对每个函数或者是类来分别设置(译者注:比如使用 @cython.annotation_typing 装饰器, 参考这里

  • emit_code_comments (True / False)

    是否将代码逐行的复制到生成文件的C语言代码的注释中,来帮助理解生成的代码。要获取覆盖率报告的话要求该项设置为True。

  • cpp_locals (True / False)

    使 C ++变量表现的更像是Python变量 -- 允许它们处于未绑定的状态,而不是总是在函数的开头完成对于它们的默认构造。更多细节可以看 cpp_locals指令.

  • legacy_implicit_noexcept (True / False)

    是否遵循遗留的noexcept风格。 当设置为True时,cdef 的函数默认不会传播引发的异常。因此,函数会表现地更新是声明了 noexcept 关键字的模式。更多细节可以看错误返回值. 将这个指令设置为True的时候,Cython3.0会使用和Cython0.X一样的语义。加入这个指令只是为了帮助Cython3.0之前写的历史遗留代码更好的迁移到3.0。这个选项以后会被删除。

配置优化项

  • optimize.use_switch (True / False)

    是否将链条式的if-else语句(包含 if x == 1 or x == 2: 这样的语句)展开为C语言的switch语句。如果有很多值那么会有性能收益,但是在存在重复值的时候导致编译器错误(并不是所有C常量在Cython编译的时候都可以被检测)。 默认True。

  • optimize.unpack_method_calls (True / False)

    Cython可以在运行Python方法时生成乐观的检查,并且将潜在的函数解包以直接调用。对于内置的函数而言,这可能会显著的加快调用速度,但是对于某些情况猜测错误时产生一个轻微的性能下降。 禁用这个选项可以减少生成的代码量。默认为True。

警告

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

  • warn.undeclared (default False)

    当任何变量没有用 cdef 声明,而是被隐式声明是发出警告

  • warn.unreachable (default True)

    当代码路径静态地被确定为不可到达时发出警告,例如没有条件的返回两次(译者注:类似于IDEA和Pycharm发出的 code unreachable 警告)

  • warn.maybe_uninitialized (default False)

    当有可能没有被初始化的变量被使用时发出警告

  • warn.unused (default False)

    对没有被使用的变量和声明发出警告

  • warn.unused_arg (default False)

    对没有被用到的函数参数发出警告

  • warn.unused_result (default False)

    针对由于使用了同样对名称因而没有被用到的赋值语句发出警告,例如 r = 2; r = 1 + 2 此处第一句赋值没有意义。(译者注:较真的说,其实未必没有意义)

  • warn.multiple_declarators (default True)

    对声明在同一行带至少一个指针类型的多个变量发出警告(可能发生类型声明错误),比如 cdef double* a, b 在 C 语言中, 声明 a 为指针, b 为数值, 但是可能被错误的解释为两个指针。 译者注:Cython鼓励Java风格的声明方式,且认为C风格的声明方式过时、不建议使用。

如何设置指令

全局

可以通过文件顶部附近写一行特殊标题注释设置编译器指令,如下所示:

# cython: language_level=3, boundscheck=False

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

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

$ cython -X boundscheck=True ...

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

局部

对于局部代码块,您需要使用特殊的内置 cython 模块:

#!python
cimport cython

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

#!python
@cython.boundscheck(False) # 关闭这个函数的边界检查
def f():
    ...
    # 为这个代码块临时打开边界检查
    with cython.boundscheck(True):
        ...

警告:这两种设置指令的方法不会被命令行中通过 -X 传递的参数覆盖(译者注:即设置在局部的指令拥有最高的优先级)

setup.py

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

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

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

这将覆盖compiler_directives 字典中指定的默认指令。请注意,如上所述的每个文件显式设置的或本地指令优先级高过传递给 cythonize的值(译者注:即优先级是 局部 > 文件头部 > 传递给cythonize的值 )。


我们一直在努力

apachecn/AiLearning

【布客】中文翻译组