常见问题解答¶
注意
此页面已从 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.stdint
中 cimport
它们,例如
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
会返回 True
或 False
。
如何使用 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 版本)
如何在 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 扩展模块时都会遇到同样的问题。
这是最明智(如果不是唯一)的解决方法。
进入 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
在 Cython 3.x 中,使用 Python 类型注解而不是 Cython 语法。Python 类型注解区分
func(x: MyType)
和func(x: Optional[MyType])
,其中第一个**不允许**None
,而第二个明确允许它。func(x: MyType = None)
也允许它,因为它是由提供的默认值明确要求的。如果你想将
None
视为无效输入,那么你需要编写代码来检查它,并抛出适当的异常。如果你希望 Cython 在扩展类型参数中传递
None
时抛出异常,可以使用not None
声明def foo(MyClass val not None): <...>
这是
def foo(MyClass val): if val is None: raise <...> <...>
你也可以在文件顶部添加
#cython: nonecheck=True
,所有访问都会被检查是否为 None,但这会降低速度,因为它在每次访问时都会添加一个检查,而不是在函数调用时只检查一次。
关于项目¶
Cython 是 Python 的实现吗?¶
答案: 从官方角度来说,不是。但是,它编译了几乎所有现有的 Python 代码,这使得它非常接近一个真正的 Python 实现。结果取决于 CPython 运行时,不过,我们认为这是一个主要的兼容性优势。无论如何,Cython 的一个官方目标是编译常规 Python 代码并运行(大部分)正常的 Python 测试套件 - 显然比 CPython 快。 ;-)
Cython 比 CPython 快吗?¶
答案: 对于大多数情况来说,是的。例如,一个用 Cython 编译的 pybench 在总运行时间上快了 30% 以上,而在控制结构(如 if-elif-else
和 for
循环)上快了 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 仅具有历史意义。