常见问题解答

注意

此页面已从 github 上的 wiki 迁移,并且正在更新中;如果您发现需要改进的地方,请打开一个问题或 PR。

基础知识

我需要将我的 .py 文件重命名为 .pyx 吗?

答案:不需要。Cython 可以编译 .py 和 .pyx 文件。区别在于扩展的 Cython 语法(cdef )仅在 Cython .pyx 文件中可用,而在 Python .py 文件中不可用。

但是,您可以使用 Cython 的 纯 Python 模式 为编译提供类型声明,包括 Python 的 PEP-484 语法用于类型提示。

对于不需要与外部 C 库交互的情况,这也是为代码键入推荐的方式,因为坚持使用带有常规 Python 语法的 .py 文件可以使您的软件开发需求的整个范围内的调试、代码检查、格式化、分析等工具可用于您的软件开发需求,这些工具通常无法处理 .pyx 文件的语法。


Cython 可以为类生成 C 代码吗?

答案:一个普通的类将成为一个完整的 Python 类。Cython 还可以生成 C 类,其中类数据存储在高效的 C 结构中,但代价是一些额外的限制。


我可以在 C 中调用我的 Python 代码吗?

答案:可以,很容易。请按照 Cython 源代码分发中的 Demos/callback/ 中的示例操作。


如何使用 Cython 接口 numpy 数组?

答案:请按照 示例 操作。


如何使用子包编译 Cython?

答案:强烈建议将 Cython 模块以与代码库的 Python 部分完全相同的 Python 包结构进行排列。只要您不将 Cython 代码保存在不寻常的位置,一切应该都能正常工作。

这部分是由于完全限定名称在编译时解析,并且在 Cython 编译和 Python 运行时调用之间移动 .so 文件或在 Cython 编译和 Python 运行时调用之间添加 __init__ 文件意味着 cimport 和 import 可能以不同的方式解析。未能做到这一点可能会导致错误,例如 .pxd 文件未找到或 'module' object has no attribute '__pyx_capi__'


如何加快 C 编译速度?

答案:特别是对于大型模块,Cython 生成的代码可能需要 C 编译器相当长的时间才能优化。这对于生产构建通常是可以的,但在开发过程中,这可能会妨碍工作。

禁用代码优化可以显着加快 C 编译器运行速度,例如通过在 Linux 或 MacOS 上设置环境变量 CFLAGS="-O0 -ggdb",这还会为更好的崩溃报告和调试器使用启用完整的调试符号。对于 Windows 上的 MSVC,您可以传递选项 /Od 来禁用所有优化。


如何减小二进制模块的大小?

答案: Python distutils 构建通常会在扩展模块中包含调试符号。例如,gcc 的默认值为 -g2。禁用它们 (CFLAGS=-g0 对于 gcc),或将它们设置为崩溃时产生堆栈跟踪所需的最低限度 (CFLAGS=-g1 对于 gcc),可以明显减小二进制文件的大小。

以下是一些其他尝试方法

  • 如果你不需要为你的 cdef 类、内存视图或函数提供 pickle 支持,请考虑使用指令禁用自动 pickle 支持

    # cython: auto_pickle=False
    
    # you can still enable or disable it locally for single class:
    @cython.auto_pickle(True)
    @cclass
    class MyClass:
        ...
    
  • 如果你不需要在异常堆栈跟踪中包含 C 行信息(例如,Python/Cython 行就足够了,就像普通的 Python 代码一样),你可以使用 C 宏 CYTHON_CLINE_IN_TRACEBACK 禁用此功能

    -DCYTHON_CLINE_IN_TRACEBACK=0

    在 3.1 之前的 Cython 版本中,你还需要传递选项 --no-c-in-traceback 或设置选项 c_line_in_traceback=False 来减小大小。

  • 如果你不需要 Cython 实现的函数在内省方面(参数名称、注释等)看起来和行为像 Python 函数,你可以关闭 binding 指令,无论是全局的,还是针对类或特定函数的局部指令。这将使 Cython 对本机实现的函数使用正常的 CPython 实现,该实现不会公开此类功能。

  • 如果你不需要公开 Python 函数和类的文档字符串,你可以使用选项 Cython.Compiler.Options.docstrings 将它们从扩展模块中排除。


Unicode 支持程度如何?

答案: Unicode 支持与 CPython 一样好,但额外区分了 Python str (bytes 在 Python 2.7 中) 和 unicode (始终为 Unicode 文本) 字符串类型。请注意,没有可用于 Unicode 字符串的等效 C 类型,但 Cython 可以自动从编码的 C/C++ 字符串 (char* / std::string) 转换(编码/解码)到编码的 C/C++ 字符串。

请参阅 字符串教程.

如何…?

如何对 cdef 类进行 pickle 操作?

答案: 请参阅 文档.


如何帮助 Cython 找到 numpy 头文件?

答案: 如果你看到类似以下错误

error: numpy/arrayobject.h: No such file or directory
error: numpy/ufuncobject.h: No such file or directory

你应该修改你的 setup.py 文件,以如下方式获取 numpy 包含目录

import numpy
...
setup(
    ...
    ext_modules = [Extension(..., include_dirs=[numpy.get_include()])]
)

如何声明数值或整数 C 类型?

答案: 在大多数情况下,你不需要这样做。对于在 stdint.h 中声明的类型,只需从 Cython 附带的 libc.stdintcimport 它们,例如

from libc.stdint cimport uint32_t, int64_t
cdef int64_t i = 5

对于非标准类型,只需为 Cython 提供一个 ctypedef 声明,将它们映射到一个密切相关的标准 C 类型,例如

cdef extern from "someheader.h":
    ctypedef unsigned long MySpecialCInt_t

cdef MySpecialCInt_t i

确保你在代码中使用原始 C(typedef)类型名称,而不是你在 Cython 中声明的替换类型!

在 C 编译时,类型的精确大小并不那么重要,因为 Cython 会生成自动大小检测代码(在 C 编译时进行评估)。但是,当您的代码在算术代码中混合使用不同类型时,Cython 必须了解正确的符号和近似长度,才能推断出表达式的适当结果类型。因此,当使用上面的 ctypedef 时,请尝试找到预期 C 类型的良好近似值。由于在混合算术表达式中,最大的类型占优,因此如果类型最终比 C 编译器最终为给定平台确定的类型略大,通常不会出现问题。在最坏的情况下,如果您的替换类型比实际的 C 类型大得多(例如,‘long long’ 而不是 ‘int’),您最终可能会得到速度稍慢的转换代码。但是,如果类型声明得太小,并且 Cython 认为它比与之一起使用的其他类型小,则 Cython 可能会推断出表达式的错误类型,并最终生成不正确的强制转换代码。在这种情况下,C 编译器可能会或可能不会发出警告。

还要注意,Cython 会认为大型整数文字(>32 位有符号)在 C 代码中使用不安全,因此可能会使用 Python 对象来表示它们。您可以通过添加 C 后缀(例如 ‘LL’ 或 ‘UL’)来确保大型文字被视为安全的 C 文字。请注意,在 Python 2 代码中,单个 ‘L’ 不被视为 C 后缀。


如何声明一个 bool 类型的对象?

答案:这取决于您想要的是 C99/C++ bool 还是 Python bool。以前,Cython 始终默认使用 Python bool 类型,这会导致用户在不知情的情况下在包装 C++ 代码时使用 bool,从而导致难以调试的问题。我们决定明确选择 - 您可以导入您想要的任何一个

  • 对于 Python 类型,请使用 from cpython cimport bool

  • 对于 C++ 类型,请使用 from libcpp cimport bool

请注意,还有一种名为 bint 的类型,它本质上是 C int,但会自动从 Python bool 值强制转换而来,以及强制转换为 Python bool 值,即 cdef object x = <bint>some_c_integer_value 会返回 TrueFalse


如何使用 const

答案:您可以在代码和声明中直接使用它。


如何使用 len() 等内置函数与 C 类型 char * 一起使用?

答案:Cython 将 len(char*) 直接映射到 strlen(),这意味着它将计算直到第一个 0 字节的字符数。类似地,(char*).decode(...) 被优化为 C-API 调用,将其应用于切片 char* 值将跳过长度计算步骤。

请参阅 字符串教程.

对于 char* 上的其他 Python 操作,生成的代码可能效率低下,因为可能需要创建临时对象。如果您在代码中注意到这一点,并且认为 Cython 可以做得更好,请在邮件列表中提出。


如何创建一个从内置 Python 类型(如列表)派生的 cdef 类?

答案:您可以在 cdef 类声明中直接使用该类型作为基类。

唯一的例外是类型 bytes(Python 2 中的 'str')和 tuple,它们只能被 Python 类(而不是 cdef 类)子类化。这被认为是一个 bug。但是,您可以安全地子类化 'unicode' 和 'list'。


如何在 Cython 代码中引发异常,使其对祖先(在调用堆栈中)CPython 代码可见?

答案:

如果您的 cdef 或 cpdef 函数或方法没有声明返回类型(如 CPython 代码中常见),那么您将获得异常而无需任何额外操作。

如果您的 cdef 或 cpdef 函数或方法声明了 C 样式的返回类型,请参阅 错误返回类型


如何声明全局变量?

答案:

global variable

如何给全局变量赋值?

答案:您需要在尝试给全局变量赋值之前声明该变量为全局变量(见上文)。这通常发生在代码类似于以下情况时

cdef int *data

def foo(n):
    data = malloc(n * sizeof(int))

这将导致错误“无法将 'int *' 转换为 Python 对象”。这是因为,与 Python 一样,赋值声明了一个局部变量。相反,您必须编写

cdef int *data

def foo(n):
    global data
    data = malloc(n * sizeof(int))

有关更多详细信息,请参阅 https://docs.pythonlang.cn/tutorial/classes.html#python-scopes-and-namespaces


如何创建对象或将运算符应用于本地创建的对象,就像纯 C 代码一样?

答案:对于 __init____getitem__ 等方法,Python 调用约定是强制性的,并且对所有对象都相同,因此 Cython 无法为它们提供主要的加速。

但是,要实例化扩展类型,最快的办法实际上是使用正常的 Python 习惯用法,即调用类型的 __new__() 方法

cdef class ExampleClass:
    cdef int _value
    def __init__(self):
        # calling "__new__()" will not call "__init__()" !
        raise TypeError("This class cannot be instantiated from Python")

cdef ExampleClass _factory():
    cdef ExampleClass instance = ExampleClass.__new__(ExampleClass)
    instance._value = 1
    return instance

请注意,这与正常的 Python 代码有类似的限制:它不会调用 __init__() 方法(这使得它快得多)。此外,虽然所有 Python 类成员都将初始化为 None,但您必须注意初始化 C 成员。__cinit__() 方法或类似上面的工厂函数都是很好的初始化位置。

如何在 Cython 模块中实现单个类方法?

答案:从 Cython 3.0 开始,Cython 定义的方法默认绑定。这意味着以下内容应该可以工作

#!python
import cython_module

class A(object):
    method = cython_module.optimized_method

如何将可能包含 0 字节的字符串缓冲区传递给 Cython?

答案:请参阅 字符串教程

您需要使用 Python 字节字符串对象或 char*/length 变量对。

将 char* 转换为 Python 字节字符串的正常方式如下

#!python
cdef char* s = "a normal C byte string"
cdef bytes a_python_byte_string = s

但是,这对于包含 0 字节的 C 字符串不起作用,因为 0 字节是 C 中终止字符串的正常方式。因此,上述方法将在第一个 0 字节处截断字符串。要正确处理这种情况,您必须指定要转换的字符串的总长度

cdef char* s = "an unusual \0 containing C byte string"
a_python_byte_string = s[:21]    #  take the first 21 bytes of the string, including the \0 byte

请注意,这不会处理指定切片长度大于实际 C 字符串的情况。如果 char* 的分配内存区域更短,则此代码将崩溃。

还支持将 C 字符串切片有效地解码为 Python unicode 字符串,如下所示

# -*- coding: ISO8859-15
cdef char* s = "a UTF-8 encoded C string with fünny chäräctörs"
cdef Py_ssize_t byte_length = 46

a_python_unicode_string = s[:byte_length].decode('ISO8859-15')

如何将 Python 字符串参数传递给 C 库?

请参阅 字符串教程 <string_tutorial>

答案:这取决于字符串的语义。假设您有以下 C 函数

cdef extern from "something.h":
    cdef int c_handle_data(char* data, int length)

对于二进制数据,您可以在 API 级别简单地要求字节字符串,这样就可以正常工作。

def work_with_binary_data(bytes binary_data):
    c_handle_data(binary_data, len(binary_data))

如果用户传递的不是字节字符串,它将引发错误(错误消息可能适合您的用例,也可能不适合)。

但是,对于文本数据,您必须处理 Unicode 数据输入。您对它的处理方式取决于您的 C 函数接受什么。例如,如果它需要 UTF-8 编码的字节序列,这可能有效。

def work_with_text_data(text):
    if not isinstance(text, unicode):
        raise ValueError("requires text input, got %s" % type(text))
    utf8_data = text.encode('UTF-8')
    c_handle_data( utf8_data, len(utf8_data) )

请注意,这也接受 Python unicode 类型的子类型。将“text”参数类型设置为“unicode”将无法涵盖这种情况。


如何使用可变参数?

答案:对于常规函数,只需像在 Python 中一样使用 *args

对于 C 函数,目前还无法干净地实现,但您可以使用 C va_args 机制。

cdef extern from "stdarg.h":
    ctypedef struct va_list:
        pass
    ctypedef struct fake_type:
        pass
    void va_start(va_list, void* arg)
    void* va_arg(va_list, fake_type)
    void va_end(va_list)
    fake_type int_type "int"

cdef int foo(int n, ...):
    print "starting"
    cdef va_list args
    va_start(args, <void*>n)
    while n != 0:
        print n
        n = <int>va_arg(args, int_type)
    va_end(args)
    print "done"

def call_foo():
    foo(1, 2, 3, 0)
    foo(1, 2, 0)

如何使用 Cython 从 Python 程序创建独立的二进制文件?

答案:您可能需要类似这样的方法。

神奇之处在于 --embed 选项,它将 Python 解释器主程序的副本嵌入到生成的 C 代码中。当然,您需要将 'foobar' 更改为您的脚本名称,并将 PYVERSION 更改为适当的值。

更多详细信息可以在 嵌入文档 中找到。


如何包装使用 restrict 限定符的 C 代码?

答案:目前还没有直接将 restrict 限定符转换为 C 代码的方法。Cython 不理解 restrict 限定符。但是,您可以通过包装来解决这个问题。

请参阅以下示例代码。

slurp.h

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <regex.h>
#include <Python.h>

int th_match(char *, char *);

cslurp.c

#include "slurp.h"

int th_match(char *string, char *pattern) {
  int status;
  regex_t re;
  if(regcomp(&re, pattern, REG_EXTENDED|REG_NOSUB) != 0) { return 0; }
  status = regexec(&re, string, (size_t)0, NULL, 0);
  regfree(&re);
  if(status != 0)
    return 0;
  return 1;
}

slurp.pyx

cdef extern from "slurp.h":
    int th_match(char *st, char *pt)

class Slurp:
    '''
    This is a simple, but optimized PEG (Parser Expression Group) parser.
    It will parse through anything you hand it provided what you hand it
    has a readline() method.

    Example:
        import sys
        from thci.ext import slurp
        o = slurp.Slurp()
        o.register_trigger('^root:.*:.*:.*:.*$', sys.stdout.write)
        o.process(open('/etc/passwd', 'r'))
    '''

    def __init__(self):
        ''' __init__(self) '''
        self.map = {}
        self.idx = 0

    def register_trigger(self, patt=None, cback=None, args=None):
        ''' register_trigger(self, patt=None, cback=None, args=None) '''
        if patt == None or cback == None:
            return False
        if args == None: args = False
        self.map[self.idx] = (patt, cback, args)
        self.idx += 0
        return True

    def process(self, fp=None):
        ''' process(self, fp=None) '''
        if fp == None:
            return False
        while True:
            buf = fp.readline()
            if not buf: break
            for patt, cback, args in self.map.values():
                if th_match(buf, patt) == True:
                    if args == False:
                        cback(buf.strip())
                    else:
                        cback(buf.strip(), args)

这避免了使用 restrict 限定符的问题(例如,FreeBSD [至少 7.X] 上 regex.h 中声明的函数所需的限定符),允许 C 编译器处理从 C 到 C 的转换,Cython 对此的支持,即使使用“const 技巧”,似乎也不正常(至少在 0.12 版本之前)。以下命令将从上述源代码生成编译后的模块。

cython -o slurp.c slurp.pyx
cc -shared -I/usr/include -I./ -I/usr/local/include/python2.5 -L/usr/local/lib -lpthread -lpython2.5 cslurp.c slurp.c -o slurp.so

也可以使用 distutils,将 cslurp.c 文件(或您的文件名称)添加到要为扩展模块编译的文件列表中。


如何从 C (.h) 或 C++ (.hpp) 头文件自动生成 Cython 定义文件?

答案:许多人创建了脚本,用于解析头文件并自动生成 Cython 绑定。

autowrap

autowrap 基于带注释(注释)的 cython pxd 文件,自动生成用于包装 C++ 库的 Python 扩展模块。当前功能包括包装模板类、枚举、自由函数和静态方法,以及从 Python 数据类型到(许多)STL 容器的转换器,以及反向转换。最后,还可以将手动编写的 Cython 代码合并到包装代码中。

http://github.com/uweschmitt/autowrap

python-autopxd

从 C 头文件自动生成 pxd。它使用 [pycparser](https://github.com/eliben/pycparser) 解析定义,因此除了 Python 依赖项之外,唯一的要求是在 PATH 上有一个 C 预处理器。

https://github.com/gabrieldemarmiesse/python-autopxd2(python-autopxd 的友好分支,支持最新的 Python 版本)

https://github.com/tarruda/python-autopxd(原始版本)


如何在 Cython 代码(pyx 文件)中运行 doctests?

答案:

Cython 在模块中生成一个 __test__ 字典,其中包含所有 Python 可见函数和类的文档字符串,这些文档字符串看起来像 doctests(即包含 >>>)。doctest 模块将正确地拾取并运行 doctests。

此模块(我们称之为“cydoctest”)提供了一种与 Cython 兼容的解决方法。

#!python
"""
Cython-compatible wrapper for doctest.testmod().

Usage example, assuming a Cython module mymod.pyx is compiled.
This is run from the command line, passing a command to Python:
python -c "import cydoctest, mymod; cydoctest.testmod(mymod)"

(This still won't let a Cython module run its own doctests
when called with "python mymod.py", but it's pretty close.
Further options can be passed to testmod() as desired, e.g.
verbose=True.)
"""

import doctest
import inspect

def _from_module(module, object):
    """
    Return true if the given object is defined in the given module.
    """
    if module is None:
        return True
    elif inspect.getmodule(object) is not None:
        return module is inspect.getmodule(object)
    elif inspect.isfunction(object):
        return module.__dict__ is object.func_globals
    elif inspect.isclass(object):
        return module.__name__ == object.__module__
    elif hasattr(object, '__module__'):
        return module.__name__ == object.__module__
    elif isinstance(object, property):
        return True # [XX] no way not be sure.
    else:
        raise ValueError("object must be a class or function")

def fix_module_doctest(module):
    """
    Extract docstrings from cython functions, that would be skipped by doctest
    otherwise.
    """
    module.__test__ = {}
    for name in dir(module):
       value = getattr(module, name)
       if inspect.isbuiltin(value) and isinstance(value.__doc__, str) and _from_module(module, value):
           module.__test__[name] = value.__doc__

def testmod(m=None, *args, **kwargs):
    """
    Fix a Cython module's doctests, then call doctest.testmod()

    All other arguments are passed directly to doctest.testmod().
    """
    fix_module_doctest(m)
    doctest.testmod(m, *args, **kwargs)

如何在 OS X 上安装时解决 -Wno-long-double error 错误?

答案:

这是 OS X 中某些 Python 安装的已知问题。它与 Cython 无关,并且每次您想要构建 C 扩展模块时都会遇到同样的问题。

这是最明智(如果不是唯一)的解决方法。

  1. 进入 Python 提示符,并输入以下内容。

>>> from distutils import sysconfig
>>> sysconfig.get_makefile_filename()

这应该会输出一个“Makefile”的完整路径……用任何文本编辑器打开该文件,并删除所有“ -Wno-long-double”标志的出现。


当使用 MinGW 作为编译器(在 Windows 上)时,如何解决“无法找到 vcvarsall.bat”错误?

答案:此错误表示 Python 无法在您的系统上找到 C++ 编译器。通常,这由 distutils 管理,但它可能还没有更新。例如,您可能在 setup.py 中使用它

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

相反,您可以尝试加载 setuptools,它将修补 distutils 以找到 vcvarsall.bat

try:
    from setuptools import setup
    from setuptools import Extension
except ImportError:
    from distutils.core import setup
   from distutils.extension import Extension

在 IPython 中,您可以像这样导入 setuptools

# First cell:
    import setuptools
            %load_ext Cython

# Second cell:
    %%cython -a
    import cython
    cimport cython

    cdef int alpha = 255
    print alpha

如果这没有成功,请尝试以下解决方法。

如果没有导入任何 python 库,请通过添加以下语句来定义编译器

--compiler=mingw32

因此,该行应为

python pyprog.py build_ext --compiler=mingw32 --inplace

但是,这并不能解决使用 pyximport 方法时的问题(参见教程)。或者,可以应用以下补丁。

注意:这尚未测试。

打开文件 pyximport/pyxbuild.py,并在适当的位置添加用“+”标记的四行。

最后,如果这不起作用,请在记事本中创建一个名为“pydistutils.cfg”的文件,并赋予它以下内容

[build_ext]
compiler=mingw32

将其保存到主目录,可以通过在命令提示符下键入以下内容找到

import os
os.path.expanduser('~')

解释

.pxd.pxi 文件有什么区别?什么时候应该使用它们?

简短答案:您应该始终使用 .pxd 文件进行声明,而 .pxi 文件仅用于您要包含的代码。

中等答案:.pxd 文件是声明列表,.pxi 文件是文本包含的,它们用于声明是 .pxd 文件存在之前共享常见声明的方式的历史遗留物。

答案:.pxd 文件是声明文件,用于在 C 扩展模块中声明类、方法等(通常在同名 .pyx 文件中实现)。它只能包含声明,即没有可执行语句。您可以从 .pxd 文件中cimport 东西,就像您在 Python 中导入东西一样。从同一个 .pxd 文件中 cimport 的两个独立模块将接收相同的对象。

一个 .pxi 文件是一个包含文件,它被文本包含(类似于 C 的 #include 指令),并且可能包含程序中给定位置的任何有效的 Cython 代码。它可能包含实现(例如,常见的 cdef 内联函数),这些实现将被复制到两个文件中。例如,这意味着如果我在 a.pxi 中声明了一个类 A,并且 b.pyx 和 c.pyx 都执行了 include a.pxi,那么我将有两个不同的类 b.A 和 c.A。C 库(包括 Python/C API)的接口通常在 .pxi 文件中声明(因为它们不与特定模块相关联)。它也会在每次调用时重新解析。

现在,由于可以使用 cimport *,因此没有理由使用 .pxi 文件进行外部声明。


哪个更好,一个大的模块还是多个独立的模块?

答案:简而言之,一个大的模块处理起来很笨拙,但允许 C 编译器进行更广泛的优化。

对于多个模块,编译时间实际上可能会减少,因为构建可以并行化。distutils 中的“build_ext”命令从 Py3.5 开始有一个“-j”选项。此外,较小的模块通常由 C 编译器更快地编译,因为某些优化可能涉及非线性开销。

当拆分一个模块时,分发大小和每个模块的大小可能会增加,因为 Cython 必须将某些东西复制到每个模块中。有一个功能请求可以缓解这种情况。

模块之间的 C 调用比模块内部的 C 调用稍微慢一些,仅仅是因为 C 编译器无法优化和/或内联它们。您将不得不为它们使用共享的 .pxd 声明,然后通过函数指针调用。但是,如果模块使用功能性拆分,这应该不会造成太大影响。对于在多个模块中使用的性能关键代码,创建一个包含内联函数的共享 .pxd 文件(或 .pxi 文件)可能仍然是一个好主意。

当拆分现有模块时,您还必须处理 API 更改。在某些地方保留一些旧的导入,或者将模块变成一个包,通过导入将模块命名空间合并在一起,这可能会在您移动名称并将其重新分配到多个模块时,防止您原始模块的用户出现代码中断。


PyObject*object 有什么区别?

答案: 类型为 PyObject* 的变量是一个简单的 C 指针,就像 void* 一样。它没有引用计数,有时被称为借用引用。一个 object 变量是对 Python 对象的拥有引用。你可以通过强制转换将它们相互转换

from cpython.ref cimport PyObject

py_object = [1,2,3]

cdef PyObject* ptr = <PyObject*>py_object

cdef object l = <object>ptr    # this increases the reference count to the list

请注意,对象的生存期只与它的拥有引用绑定,而不是与任何碰巧指向它的 C 指针绑定。这意味着上面的例子中 ptr 一旦指向该对象的最后一个引用消失,就会变得无效

py_object = [1,2,3]
cdef PyObject* ptr = <PyObject*>py_object
py_object = None   # last reference to list dies here

# ptr now points to a dead object
print(<object>ptr)   # expect a crash here!

指针通常用于通过 C 回调传递对象,例如

cdef int call_it_from_c(void* py_function, void* args):
    py_args = <tuple>args if args is not NULL else ()
    return (<object>py_function)(*py_args)

def py_func(a,b,c):
    print(a,b,c)
    return -1

args = [1,2,3]

call_it_from_c(<PyObject*>py_func, <PyObject*>args)

再次强调,必须注意在任何指向对象的指针仍在使用时保持对象存活。


为什么 Cython 并不总是对未初始化的变量给出错误?

答案: Cython 在编译时对变量初始化进行了一些静态检查,以防止在使用前未初始化,但这些检查非常基础,因为 Cython 不知道运行时将执行哪些代码路径

考虑以下情况

def testUnboundedLocal1():
   if False:
      c = 1
   print c
def testUnboundedLocal2():
   print c

使用 CPython,这两个函数都会导致以下异常

NameError: global name 'c' is not defined

使用 Cython,第一个变体打印“None”,第二个变体会导致编译时错误。这两种行为都不同于 CPython 的行为。

这被认为是一个 BUG,将在未来进行更改。


为什么一个带有 cdef 参数的函数可以接受 None?

答案: 在 Python 中,使用 None 来表示“无值”或“无效”是一种相当常见的习惯用法。这与 C 不兼容,因为 None 与任何 C 类型都不兼容。为了适应这种情况,带有 cdef 参数的函数默认情况下也接受 None。这种行为继承自 Pyrex,虽然有人提议改变它,但它可能会保留下来(至少在一段时间内),以保持向后兼容性。

你有四种选择来处理代码中的 None

  1. 在 Cython 3.x 中,使用 Python 类型注解而不是 Cython 语法。Python 类型注解区分 func(x: MyType)func(x: Optional[MyType]),其中第一个**不允许** None,而第二个明确允许它。 func(x: MyType = None) 也允许它,因为它是由提供的默认值明确要求的。

  2. 如果你想将 None 视为无效输入,那么你需要编写代码来检查它,并抛出适当的异常。

  3. 如果你希望 Cython 在扩展类型参数中传递 None 时抛出异常,可以使用 not None 声明

    def foo(MyClass val not None): <...>
    

    这是

    def foo(MyClass val):
        if val is None: raise <...>
        <...>
    
  4. 你也可以在文件顶部添加 #cython: nonecheck=True,所有访问都会被检查是否为 None,但这会降低速度,因为它在每次访问时都会添加一个检查,而不是在函数调用时只检查一次。

关于项目

Cython 是 Python 的实现吗?

答案: 从官方角度来说,不是。但是,它编译了几乎所有现有的 Python 代码,这使得它非常接近一个真正的 Python 实现。结果取决于 CPython 运行时,不过,我们认为这是一个主要的兼容性优势。无论如何,Cython 的一个官方目标是编译常规 Python 代码并运行(大部分)正常的 Python 测试套件 - 显然比 CPython 快。 ;-)


Cython 比 CPython 快吗?

答案: 对于大多数情况来说,是的。例如,一个用 Cython 编译的 pybench 在总运行时间上快了 30% 以上,而在控制结构(如 if-elif-elsefor 循环)上快了 60-90%。我们定期运行 CPython 基准测试套件中的测试(包括 Django 模板、2to3、计算基准测试和其他应用程序),其中大多数测试无需修改或静态类型即可开箱即用,性能提升了 20-60%。

然而,Cython 的主要优势在于它可以很好地扩展到更高的性能要求。对于大量使用常见内置类型(列表、字典、字符串)的代码,Cython 通常可以将处理循环的速度提高几个数量级。对于数值代码,与 CPython 相比,速度提高 100-1000 倍并不罕见,只需在代码的性能关键部分添加静态类型声明即可实现,从而用速度换取 Python 的动态类型。由于可以在代码中的任何粒度级别进行此操作,因此 Cython 使得编写足够快的简单 Python 代码变得容易,并且只需在正确的位置使用静态 C 类型,即可将代码中关键的 5% 调整到最大性能。


Cython 支持哪些 Python 版本?

答案:从 Cython 0.21 开始,支持的版本为 2.6、2.7 和 3.4+,由于缺乏测试能力,Python 2.6 正在逐步淘汰。Cython 3.0 完全删除了对 Python 2.6 的支持,需要 Python 2.7 或 Python 3.4+。Python 2.x 支持计划在 Cython 3.1 中删除,这可能需要在发布时需要 Python 3.6 或更高版本。

Cython 生成的 C 代码是可移植的,可以在所有支持的 Python 版本中构建。所有支持的 CPython 发布系列都会定期测试。通常在发布新 CPython 版本之前就会支持它们。

Cython 编译的源代码可以使用 Python 2 和 Python 3 语法,在 Cython 0.x 中默认使用 Python 2 语法,在 Cython 3.x 及更高版本中默认使用 Python 3 语法。在 Python 2 模式下编译 Cython 模块(.pyx 文件)时,如果大多数 Python 3 语法特性不会干扰 Python 2 语法(如 Python 2.7 中),则默认情况下可以使用它们,但通用语言语义定义为 Python 2 中的语义。在编译 Python 模块(.py 文件)时,特殊的 Cython 语法(例如 cdef 关键字)不可用。对于这两种输入类型,可以通过将“ -3”选项传递给编译器,或者通过在模块文件顶部(在第一个注释和任何代码或空行之前)添加以下内容来将语言级别设置为 Python 3:

# cython: language_level=3

在 Cython 3.x 中,编译 Python 2 代码需要选项“ -2”或指令 language_level=2。默认情况下,在 Cython 3.0 中使用 Python 3 语义时,print() 是一个函数,列表推导中的循环变量不会泄漏到外部作用域,等等。这等效于 language_level=3str 或选项 --3str。如果您选择 language_level=3,那么,此外,无前缀字符串始终是 unicode 字符串。


Cython 输出的许可证情况如何?

答案:您可以随意使用 Pyrex/Cython 的输出(并根据您的意愿对其进行许可 - 无论是 BSD、公有领域、GPL、所有权利保留,都可以)。

更多详细信息:Python 许可证与用于 GCC 的 GPL 不同。例如,GCC 需要对其输出添加一个特殊的例外条款,因为它与 GCC 的库部分(即 GPL 软件)链接,这会触发 GPL 限制。

Cython 不会做类似的事情,并且 Python 许可证没有限制与 Python 的链接,因此输出属于用户,没有其他权利或限制。

此外,Pyrex/Cython 的所有版权所有者在邮件列表中都声明,人们可以随意使用 Pyrex/Cython 的输出。


如何在学术论文中引用 Cython?

答案:如果您提到 Cython,最简单的引用方式是在脚注中添加我们网站的 URL。您也可以选择以更正式的方式引用我们的软件项目,例如

R. Bradshaw, S. Behnel, D. S. Seljebotn, G. Ewing, et al., The Cython compiler, http://cython.org.

(作者姓名列表取自 setup.py)

对于更正式的引用,有一篇关于 Cython 的期刊论文。如果您想引用它,以下是 Bibtex

@ARTICLE{ behnel2010cython,
    author={Behnel, S. and Bradshaw, R. and Citro, C. and Dalcin, L. and Seljebotn, D.S. and Smith, K.},
    journal={Computing in Science Engineering},
    title={Cython: The Best of Both Worlds},
    year={2011},
    month=march-april ,
    volume={13},
    number={2},
    pages={31 -39},
    keywords={Cython language;Fortran code;Python language extension;numerical loops;programming language;C language;numerical analysis;},
    doi={10.1109/MCSE.2010.118},
    ISSN={1521-9615},
}

Cython 和 Pyrex 之间的关系是什么?

答案:Cython 最初是基于一个名为 Pyrex 的先前项目,该项目主要由 Greg Ewing 开发。

几年后,Pyrex 的开发实际上已经停止,而 Cython 则一直在添加新功能和对新 Python 版本的支持。

截至 2023 年,Pyrex 仅具有历史意义。