Skip to content

从Cython0.29迁移到3.0

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

Cython3.0是语言和编译器的一次大更新,带来了一些不可向后兼容的修改。这个文档将会列出其中比较重要的内容并说明如果在现有代码中处理。

Python 3 语法/语义

Cython 3.0 现在默认使用Python3语法和语义,在之前的版本中需要设置 language_level 编译器指令为 3 或者 3str 来实现。现在编译器指令的默认设置是 language_level=3str,这代表默认使用Python3语义,但是没有前缀修饰的字符串仍然是 str 对象,比如:在Python3中是Unicode文本,Python2.7中是字节字符串。

通过设置 language_level=2 你可以让你的代码回到使用Python2语义的模式。

根据语言级别一起变化的语义还有: - / 除法,使用true除法运算符,除非启用 cdivison 编译器指令。 - print 是一个函数而不是语句 - 没有定义基类( class C: ... )的Python类现在是“新式”的类了,在Py2.X中也是。如果你从没听过什么是“旧风格的类”,你大概率也没必要知道。 - 注解(类型提示)现在以字符串的形式存储。PEP 563 - 根据PEP 479 生成器中 StopIteration 的处理方式变了。

Python 语义

一些Python兼容性的BUG被修复了,比如: - 下标访问( x[1] )会在尝试序列协议之前先尝试映射协议。https://github.com/cython/cython/issues/1807 (译者注:就是先把x看作dict后看作list来尝试。) - 整数字面量的指数现在遵循Python语义而不是C语义。https://github.com/cython/cython/issues/2133

绑定函数

binding directive 编译器指令现在默认启用。这使得Cython编译的Python函数和普通的(没有编译的)的Python函数在大多数方面兼容,比如签名自省、注解等等。(译者注:自省是指对象可以提供查看自身属性、方法、类型的手段)。

这也使得他们可以通过属性赋值的方式绑定在Python类上。如果你不想要这样,比如:函数就是函数不应该成为方法,你可以通过设置 binding=False 或者有选择性的增加 @cython.binding(False) 装饰器来禁用“绑定”(以及所有其他Python函数特性)。在纯Python风格下,到Cython0.29.16版本为止,这个装饰器还没有被提供,但是编译过的代码不受此影响。

无论如何,我们推荐用标准的Python内置函数 staticmethod 来保留新的函数特性而不是处理绑定问题。

def func(self, b): ...

class MyClass(object):
    binding_method = func

    no_method = staticmethod(func)

命名空间包

根据PEP-420, Cython现在还支持从命名空间包中加载pxd文件。这可能会对引用路径造成影响。

NumPy C-API

Cython过去基于过时的 pre-NumPy-1.7 C-API 来生成代码。Cython3.0起不再是这样了。

你可以通过定义宏 NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION 来去掉长久以来的构建警告:“编译器的C模块使用了过时的API”。要么在每个文件中:

# distutils: define_macros=NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION

要么在你的 setup.py 的 Extension对象中:

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

使用不同的C-API的一个副作用是你的代码现在需要调用 NumPy C-API 初始化函数 -- 之前的版本中不必这么做。

为了这一点降低对用户对影响,当Cython3.0看到 numpycimport 但是函数还没有被使用的时候会自动调用(这个初始化函数)。在那些你不需要这么做(希望很少)的场景下,内部C-API的初始化可以被通过伪造一次函数使用但是并不真的调用来禁用,比如:

# 显式禁用 NumPy's C-API 的自动初始化.
<void>import_array

类私有名称的损毁

Cython更新后会更遵循Python对于类私有名称的规则。任何类内部以 __ 开头但是不以 __ 结尾的名称都会用类名称毁坏。大多数用户的代码应该不受影响 - 和Python不同的是,没有毁坏的全局名称仍然可以访问以保证能够访问以 __ 开头的C名称。

cdef extern void __foo()

class C: # or "cdef class"
   def call_foo(self):
       return __foo() # 调用的仍然是全局名称

覆盖 cdef class 里的 __ 开头的方法将变得无效。

cdef class Base:
    cdef __bar(self):
        return 1

    def call_bar(self):
        return self.__bar()

cdef class Derived(Base):
    cdef __bar(self):
        return 2

这里 Base.__bar 已经变为 _Base__barDerived.__bar 变为 _Derived__bar 。 因此 call_bar 会调用 _Base__bar 。这和Python行为一致,对 defcdefcpdef 的方法和属性都适用。

算术特殊方法

cdef 定义的类的算术特殊方法的表现在Cython3.0中改变了。像在纯Python中一样,这些类现在支持使用特殊的反向版本的方法,比如( __radd____rpow__ )。主要的不兼容的变更是第一个操作数(通常是说 __self__ )的类型现在被假设为定义类,而不是由用户去测试和强制转换每个操作数(类型)。

可以通过设置编译器指令 c_api_binop_methods=True 来使用原先的行为。更多的细节可以在算法方法中阅读。

异常值和 noexcept

externcdef 函数现在能在默认情况下安全的传播Python异常。之前,他们需要被显式声明一个异常值来防止他们遮蔽异常。一个新的 noexcept 修饰符可以被用来声明一个 cdef 函数绝对不会引发任何异常。

在现有的代码中,你应该主要检查那些没有声明异常值的的 cdef 函数。

cdef int spam(int x):
    pass

cdef void silent(int x):
    pass

如果你错误地忘记写异常值了,比如:一个函数应该传播Python异常,那么在新版本中Cython会帮你传播、并且正常的传播任何异常。这在Cython代码中是一个常见的错误,因此也是这项更新的主要原因。

另一方面,在之前的版本中,如果你是因为希望避免从这个函数中传播任何Python异常所以才不声明异常值的,那么新版本将会生成性能稍微低一些的代码,因为现在会增加一个异常检查。如果你不想执行异常检查,你必须显式的用 noexcept 来修饰函数。

cdef int spam(int x) noexcept:
    pass

cdef void silent(int x) noexcept:
    pass

对于 externcdef 函数被认为是较 非 extern 而言更不可能引发Python异常、它们更多的是引发C异常,因此默认行为没有改变。因此这一点抵消了对于需要和C库互动的代码的影响。

对于任何显式声明了异常值的 cdef 函数,行为没有改变。(比如: cdef int spam(int x) except -1 )

对于 nogil 的函数这里有一个容易解决的潜在问题就是会隐式的声明为 except * 。这最有可能在返回类型为 void (原则上来说对大多数非数值型的返回值都适用)的时候发生。在这种情况下,Cython会被迫在每次含漱调用之后重新获取GIL来检查异常状态。如果需要避免这个额外开销,要么把含漱签名改为 noexcept (如果你确定合适的话),要么返回一个 int 并让Cython将int用作错误标识(默认情况下, -1 将会触发异常检查)。

注意: 默认不传播异常的遗留行为可以通过设置编译器宏 legacy_implicit_noexcept=True 来获取。查看编译器指令

类型注解

Cython3 在识别注解中的类型方面取得了较大的进步,阅读纯Python风格来了解一些进步的内容是值得的。

一个显著的不可向后兼容的变更是,除非显式将语言级别设置为2, x: int 现在会被认定为 Python的 int 类型 (在Cython0.29中x可能识别为任何Python对象)。为了抵消可能的影响,Cython仍然接受Python2.X中的 Python intlong

为了让你在处理你对于类型注解的理解和Cython不同的场景,Cython3现在支持在每个类或者每个函数的级别设置 annotation_typing 编译器指令。(译者注:在每个函数或者类的级别需要使用对应的装饰器, @cython.annotation_typing

C ++预增/后减 运算符

Cython 3在预增/预减和后增/后减操作符之间进行了区分(Cython 0.29将两者都实现为预(增/减少)操作符)。这仅在使用 cython.operator.postdecrement / cython.operator.postincrement 时才会产生影响。当遇到错误时,需要添加相应的操作符。

cdef cppclass Example:
    Example operator++(int)
    Example operator--(int)

C ++的公开声明

在Cython3.0中,C ++模式下的公开声明会用 extern "C++" 作为 C ++ API导出。 可以通过输出关键字 CYTHON_EXTERN_C 来允许用C ++实现的Cython模块在C中允许调用。

** 幂运算符

Cytyhon3 改变了幂运算符使之更贴近Python。结果是:

  1. a**b 两个整数会返回一个浮点值类型。
  2. a**b 一个或者多个非复数的浮点值会返回一个复数。

可以通过设置 cpow 编译器指令来还原旧的行为。


我们一直在努力

apachecn/AiLearning

【布客】中文翻译组