从 Cython 0.29 迁移到 3.0¶
Cython 3.0 是编译器和语言的重大修订,它带来了一些向后不兼容的更改。本文档列出了重要的更改,并解释了如何在现有代码中处理这些更改。
Python 3 语法/语义¶
Cython 3.0 现在默认使用 Python 3 语法和语义,以前需要将 language_level
指令 <编译器指令> 设置为 3
或 3str
。新的默认设置现在是 language_level=3str
,这意味着 Python 3 语义,但无前缀字符串是 str
对象,即 Python 3 下的 Unicode 文本字符串和 Python 2.7 下的字节字符串。
您可以通过设置 language_level=2
将代码恢复到以前的(Python 2.x)语义。
由于语言级别,进一步的语义更改包括
Python 语义¶
一些 Python 兼容性错误已修复,例如:
下标(
x[1]
)现在尝试在序列协议之前尝试映射协议。(https://github.com/cython/cython/issues/1807)整数文字的指数运算现在遵循 Python 语义,而不是 C 语义。(https://github.com/cython/cython/issues/2133)
绑定函数¶
绑定指令 现在默认启用。这使得 Cython 编译的 Python (def
) 函数在签名自省、注释等方面与普通(未编译)Python 函数基本兼容。
它还使它们在 Python 类中作为方法绑定到属性赋值,因此得名。如果这不是预期的,即如果一个函数实际上是一个函数而不是一个方法,您可以通过设置 binding=False
或选择性地添加装饰器 @cython.binding(False)
来禁用绑定(以及所有其他 Python 函数功能)。在纯 Python 模式下,装饰器在 Cython 0.29.16 中尚不可用,但编译后的代码不会受到影响。
但是,我们建议保留新的函数功能,而是使用标准 Python staticmethod()
内置函数来处理绑定问题。
def func(self, b): ...
class MyClass(object):
binding_method = func
no_method = staticmethod(func)
命名空间包¶
Cython 现在支持根据 PEP-420 从命名空间包加载 pxd 文件。这可能会影响导入路径。
NumPy C-API¶
Cython 以前生成依赖于已弃用的 NumPy-1.7 之前的 C-API 的代码。Cython 3.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
中的 Extensions 中设置它
Extension(...
define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")]
)
不同 C-API 使用方式的一个副作用是,您的代码现在可能需要调用 NumPy C-API 初始化函数,而之前它可以不调用该函数。
为了减少用户在此方面的影响,Cython 3.0 现在将在看到 numpy
被 cimport,但该函数未被使用时自动调用它。在(希望很少见)的情况下,如果这妨碍了您的操作,可以通过模拟函数的使用而实际上不调用它来禁用内部 C-API 初始化,例如
# Explicitly disable the automatic initialisation of 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() # still calls the global name
不再起作用的是覆盖 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__bar
,而 Derived.__bar
被混淆为 _Derived__bar
。因此,call_bar
将始终调用 _Base__bar
。这与已建立的 Python 行为相匹配,并适用于 def
、cdef
和 cpdef
方法和属性。
算术特殊方法¶
Cython 3.0 中 cdef 类的算术特殊方法(例如 __add__
和 __pow__
)的行为已更改。它们现在支持这些方法的单独“反转”版本(例如 __radd__
、__rpow__
),这些版本的行为与纯 Python 中的行为相同。主要的不兼容更改是,现在假定第一个操作数(通常是 __self__
)的类型是定义类的类型,而不是依赖用户测试和转换每个操作数的类型。
异常值和 noexcept
¶
默认情况下,不是 extern
的 cdef
函数会安全地传播 Python 异常。以前,它们需要显式声明具有 异常值 才能防止它们吞没异常。可以使用新的 noexcept
修饰符来声明确实不会引发异常的 cdef
函数。
在现有代码中,您主要应该注意没有声明异常值的 cdef
函数
cdef int spam(int x):
pass
cdef void silent(int x):
pass
如果您错误地遗漏了异常值,即该函数应该传播 Python 异常,那么新的行为将为您处理这种情况,并正确传播任何异常。这是 Cython 代码中的一个常见错误,也是更改行为的主要原因。
另一方面,如果您没有声明异常值是因为您希望避免异常从该函数传播出去,那么新的行为将导致生成效率稍低的代码,现在涉及异常检查。为了防止这种情况,您必须显式声明该函数为 noexcept
cdef int spam(int x) noexcept:
pass
cdef void silent(int x) noexcept:
pass
对于也是 extern
的 cdef
函数的行为保持不变,因为 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
来启用不默认传播异常的不安全旧行为。
注解类型¶
Cython 3 在识别注解中的类型方面取得了重大改进,阅读 纯 Python 教程 了解一些改进非常值得。
一个值得注意的向后不兼容的更改是,x: int
现在被类型化为 x
是一个精确的 Python int
(Cython 0.29 会接受任何 Python 对象作为 x
),除非语言级别显式设置为 2。为了减轻影响,Cython 3.0 在 Python 2.x 下仍然接受 Python int
和 long
值。
您可能会遇到的一个潜在问题是,像 typing.List
这样的类型现在在注解中被理解(以前它们被忽略),并且被解释为表示精确 list
。这比 PEP-484 中指定的解释更严格,PEP-484 也允许子类。
为了更轻松地处理您的类型注解解释与 Cython 解释不同的情况,Cython 3 现在支持在每个类或每个函数级别设置 annotation_typing
指令。
C++ 后缀递增/递减运算符¶
Cython 3 区分前/后缀递增和前/后缀递减运算符(Cython 0.29 将两者都实现为前(递增/递减)运算符)。这只有在使用 cython.operator.postdecrement
/ cython.operator.postincrement
时才会产生影响。当遇到错误时,需要添加相应的运算符
cdef cppclass Example:
Example operator++(int)
Example operator--(int)
C++ 中的公共声明¶
在 C++ 模式下,公共声明在 Cython 3 中使用 extern "C++"
导出为 C++ API。可以通过使用 CYTHON_EXTERN_C
宏设置导出关键字来更改此行为,以允许 Cython 模块在 C++ 中实现,但可以从 C 调用。
**
幂运算符¶
Cython 3 已更改幂运算符的行为,使其更像 Python。结果是
a**b
两个整数可能返回浮点类型,a**b
一个或多个非复数浮点数可能返回复数。
可以通过将 cpow
编译指令 设置为 True
来恢复旧的行为。
DEF
/ IF
的弃用¶
条件编译功能 已被弃用,不应再在新的代码中使用。预计在未来的某个版本中将其移除。
DEF
的用法应替换为
全局 cdef 常量
全局枚举(C 或 Python)
C 宏,例如在 逐字 C 代码 中定义
通常的 Python 机制,用于在模块之间共享值和用法
IF
的用法应替换为
运行时条件和条件 Python 导入(即通常的 Python 模式)
从 Cython extern 结构定义中省略未使用的 C 结构字段名称(它不必完整)
在不同的 Cython 名称下重新定义 extern 结构类型,使用不同的(例如版本/平台相关)属性,但使用 相同的 cname 字符串。
将可选的(非平凡的)功能分离到可选的 Cython 模块中,并在需要时导入/使用它们(使用常规的运行时 Python 导入)
代码生成,作为最后的手段