Skip to content

常见问题

原文: https://cython.readthedocs.io/en/latest/src/userguide/faq.html

基本问题

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

回答: 不。Cython可以编译 .pyx.py 文件。区别在于引申的Cython语法 (cdef ...) 只能在 .pyx Cython文件中使用( .py Python文件不行)

但是可以使用纯Python风格来为编译提供类型注释,包括用Python PEP-484的语法来做类型提示。

对于完全不需要和外部C库互动的场景来说,这也是你给代码添加类型的推荐做法,因为坚持 .py 文件和正常的Python语法可以保留整个 调试、静态分析、格式调整、性能测试等对你的软件开发有用的Python代码工具,这些工具通常处理不了 .pyx 文件的语法。


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

回答: 普通的类会变成完整的Python类,除此之外Cython还能生成C的类(类数据以一些限制作为代价用一个高效的C结构存储)


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

回答: 可以,很简单。看Cython源代码库中这里的案例。


如何用Cython和Numpy 数组连接?

回答: 看这里


我应该如何带着子包编译Cython?

回答: 非常推荐你将Cython模块用Python包结构一样的方式存放,就把它当成Python代码存放。只要你不把Cython代码放在不正常的地方,就应该生效。

一部分原因是编译的时候会解析完整的名称。移动 .so 文件或者在Cython编译和Python运行调用之间增加 __init__ 文件可能会使得 cimports 和 imports解析结果不同。这一步失败的话可能会产生类似于 .pxd文件找不到 这样的错误 或者是 'module' object has no attribute '__pyx_capi__'


我应该如何加快C语言编译?

回答: 特别是对于较大的模块来说,Cython生成的代码可以花掉C编译器不少的时间来优化。出于生产目的构建一般能接受,但是在开发的时候,这就很讨厌了。

关掉代码优化可以极大的加快C编译器运行的时间,比如在Linux或者MacOS上设置环境变量 CFLAGS="-O0 -ggdb" -- 还能启用完整的调试标识符查看更好的崩溃报告。对于Windows上的MSVC,你可以传一个 /0d 的选项来禁用一切优化。


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

回答: Python的distutils构建通常会在扩展模块中包含调试标识符。比如,对gcc来说默认是 -g2。禁用标识符(gcc的话,是CFLAGS=-g0)或者设置为能够输出足够回溯信息或者崩溃报告(gcc的话,是CFLAGS=-g1)的最低限度,可以显而易见的减小二进制文件的大小。


对Unicode的支持如何?

回答: 对Unicode的支持和CPython一样好,除此之外会区分Python str(Python2.7的 bytes )和 Unicode(总是Unicode文本)。注意:没有和Unicode字符串对应的C类型,但是Cython可以在Unicode文本和编码的C/C ++文本( char* / std::string)之间自动转换(编码/解码)


我应该如何...?

我应该如何序列化 cdef 类?

回答: 看这个文档


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

回答: 如果你看到这样的错误信息:

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

你应该修改你的 setup.py 文件来加入 numpy的 include 目录,像这样:

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提供一个足够把它们映射到一个相关的标准C类型的 ctypedef 声明就足够了。比如:

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会认为很大的整型字面量(大于 有符号的32bit)在C代码中使用不安全并可能因此使用Python对象来包装这些。通过加一个C后缀、比如 LL 或者 UL ,你可以确保一个大的字面量是一个安全的C字面量。注意,单个的 L 在Python2代码中不被认为是C后缀。


我如何声明布尔类型的对象?

回答: 这取决于你希望使用的是 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之间自动转换,比如 cdef object x = <bint>some_c_integer_value 会赋值 True 或者 False


我应该如何使用 const ?

回答: 在代码和声明用就行。

译者回答: 其实并不是随意用,根据Cython文档,对于const指针,只建议在函数参数和返回值中使用,对应于与外部C代码互动时满足对应的要求(因为在其他地方,Cython可能需要对声明和赋值分别生成代码)

如果是const常量,在传值的情况下没有这个必要。


我应该如何对内置函数比如 len() 使用C类型 char* ?

回答: Cython将 len(char*) 映射为 strlen(),代表着计算直到第一个0字节的字符数量。类似的,(char*).decode(...) 会被优化为C-API调用,用这个来切片 char* 值会跳过计算长度的步骤。

字符串教程

对于在char*上的其他Python操作,生成的代码可能不够高效,因为也许必须要创建一个临时对象。如果你在你的代码中发现了这个问题,并且认为Cython可以做的更好,欢迎在邮件列表中说明。


我应该写一个继承Python内置类型(比如list列表)的cdef类?

回答: 你可以把这个类型写成你cdef类的父类。

唯一例外是,字节类型(Python2中是 str)和元组 -- 只能被Python类子类化(cdef类不能)。这目前被认为是一个BUG。即使如此,你可以安全的子类化 unicodelist


我应该如何在Cython代码中引发一个异常、并对调用栈上的高层CPython代码可见?

回答: 如果你的cdef或者cpdef的函数或者方法没有声明返回类型(在CPython代码中很正常),那么你不用额外做什么就可以得到异常。

如果你的cdef或者cpdef函数声明了返回类型,可以查看错误返回值文档


我应该如何声明全局变量?

回答:

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))

更详细的信息请看这里: http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces


我应该如何像纯C代码一样创建对象或者对局部创建的对象使用运算符。

回答: 对于 __init____getitm__ 这样的方法,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模块中实现一个单类方法?

回答: 在Cython3.0中,Cython定义的方法会默认绑定。这意味着下面的代码可以生效:

#!python
import cython_module

class A(object):
    method = cython_module.optimized_method

我应该如何给Cython传可能带0字节的字符串缓冲区?

回答: 看字符串教程

你需要使用Python字节字符串对象或者是一个 char* 或者对应长度的变量对。

正常的从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字符串高效地解码切片为一个PythonUnicode字符串。

# -*- 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 字符串(string) 参数传递给C库?

回答: 看字符串教程

这取决于字符串的语义。假设你有这么一个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" 在这种情况下没用(will not cover this case)。


我应该如何使用可变参数?

回答: 对于正常的函数,像在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 选项,这个选项可以在生成的C代码中内嵌一份Python的解释器的副本。你需要把 foobar 改为你的脚本的名称,并且设置合适的 PYVERSION

更多细节可以看 如何在C/C ++程序中内嵌Cython模块(译者注:该文档还未翻译)。


我应该如何包装使用 Restrict 限定符的C代码?(Restrict Qualifier)

回答: 目前还没有办法。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)

通过允许C编译器在C之间处理问题,这避免了使用 Restrict 限定符的问题(对于声明在 FreeBSD 7.X版本以上 Regex.h 的函数是需要的 )。即使用 "const技巧“,Cython对此的支持都无法做到表现正常(至少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

通过把 cslurp.c 文件加入到扩展模块的待编译文件中,你也可以使用 distutils工具(译者注:现在改用setuptools了)。


我应该如何从 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 来解析定义。所以除Python依赖之外,唯一的要求就是在 PATH 中有C预处理器。

https://github.com/gabrieldemarmiesse/python-autopxd2 (这是一个Python-autopxd 的 folk,支持最近的Python版本).

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


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

回答: Cython在模块中生成一个 __test__ 字典,包含了所有Python可见函数的docsstrings以及像doctests这样的类(比如,包含 >>>)。这个doctest必须正确的使用这一点并运行doctest。

这个模块(我们叫它 "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)

在OSX上安装时,我应该如何绕过 -Wno-long-double error ?

回答: 对于在OSX上安装的Python来说有一个已知的问题。这和Cython无关,且你每次想要构建C扩展模块的时候都会遇到这个麻烦。

这是最正常的解决方法: 进入Python提示栏,输入:

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

这会输出 'Makefile' 的完整路径,用任何文本编译器打开这个文件并删除任何'-Wno-long-double' 标识即可。


当在Windows上使用MinGW时,我应该如何绕过 “unable to find vcvarsall.bat” 这个错误?

回答: 这个错误意味着Python没法在系统上找到C ++编译器。正常来说,这是由distutils来管理的,但是有可能它不是最新版本的。例如,你可能在 setup.py中是这么用的:

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

相反,你可以尝试载入setuptools,它给distutils打猴子补丁来找到vcvarsall.bat。(译者注:用setuptools就行,别再用distutils了)

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

把这个文件存到home目录,在命令提示里可以这么输入来找到:

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

解释类问题

.pxd.pxi 文件的区别是什么?分别在什么时候使用?

简短的回答: 你总是应该对声明使用 .pxd文件,对你需要include的代码使用 .pxi文件。

中等长度回答: .pxd文件是声明列表, .pxi文件是以文本的方式直接引用的,因此使用它们来声明是一个在.pxd文件存在之前比较常使用的历史遗留做法。

完整的回答: .pxd文件是一个声明文件,(比如在写C扩展模块时)用来声明类、方法等,通常在一个同名的.pyx文件中实现。它只能包含声明、不能包含可执行的语句。你可以像在Python中import一样从 .pxd 文件中 cimport 。从同一个.pxd文件中cimport的两个不同的模块会收到同样的对象。

.pxi文件时一个include引用文件,会以文本的方式引用(和C的 #include 指令类似)并可能包含任何在Cython引用时有效的Cython代码。它可能包含会被复制进多个文件的实现(常见的比如内联函数)。比如,这意味着如果我在 .pxi文件中声明了一个A类,且 b.pyx和c.pyx都 include a.pxi 那么我会有两个完全一样的类 b.A 和c.A。C库都接口通常在 .pxi文件中声明(因为它们和某个特定的模块没关系)。在每次调用时都会重新解析。

注意你可以使用 cimport *,没必要对于外部定义使用 .pxi 文件。


哪种更好? 一个大的模块还是多个分开的模块?

回答: 简单来说,一个大的模块处理起来太臃肿,但是对于C编译器而言会允许更深度的优化。

对于多个分开的模块而言,编译器时间是可能会下降的,因为允许并行编译。自从Python3.5起,distuls中的"build_ext"这个命令有一个"-j"选项。而且C编译器编译较小的模块也会更快,因为一些优化措施的会涉及非线性的额外开销。

分布版本的大小和每个模块的小,应该会增加。因为将一个模块分为几个小模块总有一些Cython必须要复制到每个字模块中的东西。目前有一个想避免这一点的特性请求

模块间的C调用会比模块内的C调用要慢一些,只是因为C编译器无法在模块间优化。你必须使用共享的 .pxd文件,然后通过函数指针来调用。如果模块对职能进行了划分,那么这么做的影响就不会太大。此时创建一个共享的 .pxd文件或者是 .pxi文件来写内联函数给多个模块中执行速度要求很高的模块代码仍然是一个好主意。

当你拆分模块的时候,你也需要处理API变化。把遗留的引入代码留着,或者把一个模块变为通过imports将包内容合并,可能会防止你原来模块的用户,在你在多个模块间移动、并重新分配名称时,出现代码崩坏。


PyObject*Object 的区别是什么?

回答: PyObject* 类型的变量是一个指针,就像 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. 在Cython3.X中,使用Python的类型注解而不是Cython的语法。Python的类型注解会区分 func(x: MyType)func(x: Optional[MyType]) -- 第一个函数禁止None,第二个函数显式的允许None。func(x: MyType = None) 也允许使用None,因为None在这里作为默认值。

  2. 如果你希望将 None 作为无效输入,你需要编写专门的检查它的代码,然后引发一个恰当的异常。

  3. 如果你希望Cython在None被传递给扩展类型的参数时引发异常,你可以使用 not None 声明。

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

相当于下面代码的简写:

def foo(MyClass val):
    if val is None: raise <...>
    <...>
  1. 你还可以在你的文件的顶部写 #cython: nonecheck=True, 那么所有的访问都会被检查是否为None。但是这会拖慢代码,因为在每次访问时都会加上一个检查而不是尽在函数调用的时候检查一次。

译者注:总之就是,要么你自己检查,要么你写指令让Cython帮你写检查的代码。


关于这个项目

Cython是Python的一个实现版本吗?

回答: 不是。你可以编译几乎所有的Python代码,因此和一个真实的Python实现版本相当接近。结果取决于实际运行的CPython (我们认为这是一个主要的兼容性优势)。无论如何,Cython的官方目标都是能够编译正常的Python代码、执行大多数Python的测试 - 且以CPython快的多的速度。

Cython比Python快吗?

回答: 大多数情况下,是。举例来说,一个Cython编译的pybench总体来说快30%,在一些类似于 if-elif-else 或者是 for 循环的结构体中快60%到90%。我们运行运行CPython标准测试用例的测试(包含 Django模版、2to3、计算标准和其他应用),且大多数情况下,即时没有任何修改、没有添加任何静态类型,性能也能提升 20% - 60%。

然而,Cython的主要优点是它可以很好的规模化、在规模化场景下速度表现甚至更好。对于严重依赖内置类型(比如:列表、字典、字符串)的代码,Cython经常可以对处理循环体进行指数级的提速。对于计算代码而言,和CPython相比100倍到1000倍的提速也很正常,且通常只需要给关键部分的代码增加静态类型声明即可 -- 牺牲Python的动态类型换取速度。由于你可以以任何粒度在代码中这么做,Cython使得写足够快的Python代码变得可能,只要使用静态类型来调整你的代码正确的5%关键部分就可以达到最大的速度。


Cython支持Python的哪些版本?

回答: 从Cython0.21版本起,支持的版本是2.6、2.7和3.4+,Python2.6由于缺乏测试能力正在被淘汰。Cython3.0完全移除了对Python2.6的支持,因此需求Python2.7或者Python3.4+。在Cython3.1版本中计划移除Python2.7的支持,那是会要求使用Python3.6或者更高版本。

Cython生成的C代码在所有支持的Python版本中都能构建和移植。,所以支持的CPython发布系列都会被常规性的测试。新的CPython版本通常是在它们发布前就已经测试过了。

Cython编译的源代码可以使用Python2和Python3语法,在Cython0.X版本中默认是Python2,在Cython3及之后的版本中默认是Python3。当在Python2模式下编译Cython模块时(.pyx文件),大多数Python3语法特性只要不和Python2语法交互(Python2.7)则默认可用,但是总体语法还是Python2定义的那样。当编译Python模块时(.py文件),特殊的Cython语法(例如 cdef 关键字)不可用。可以给编译器提供语言级别选项、并设置为3或者"-3",或者也可以在模块文件的顶部中这么写(通常是任何代码或者空行之前的第一行注释):

# cython: language_level=3

在Cython3.X中,编译Python2的代码要求提供选项 -2 或者 使用编译器指令 language_level=2 。默认情况下,Cython3.0使用Python3语义,因此 print 是一个函数,列表推导式的循环变量在外部不可见,等等。这相当于设置 language_level=3str 或者是选项 --3str 。如果你写的是 language_level=3 ,那么没有加前缀的字符串字面量总是unicode字符串。

译者注: 3str相较于3而言,主要是保留对Python2字符串的一定兼容,详细可以了解Unicode 和传递字符串 这个文档.

Cython输出的许可是什么?(license)

回答: 你可以任意使用 Cython/Pyrex的输出内容(用你喜欢的许可方式 - BSD, public domain, GPL, all rights reserved, 什么都行)

更多的细节:Python的许可和GCC使用的GPL是不同的。比如,GCC要求对它的输出内容增加特殊的意外子句 -- 因为它和GCC的库部分链接,比如对于AGL软件,会触发GPL的限制。

Cython并不会这么做,根据Python许可,链接到Python不受限制。因此输出内容属于用户,不涉及任何权利和限制。

另外,Pyrex/Cython在邮件列表写的所有版本的拥有者是可以把Python/Cython输出内容想怎么用就怎么用的人。

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

回答: 如果你提及Cython,最简单的说引用方法是在脚注上写我们网站的地址。你也可以选择用更正式的方法引用我们软件项目,像这样:

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只具有历史价值了。


我们一直在努力

apachecn/AiLearning

【布客】中文翻译组