使用Cython的纯Python模式,更快的Python变得更容易。


使用Cython的纯Python模式,更快的Python变得更容易。

长期以来,Cython一直是Python性能的伟大秘密武器之一,它可以让你把Python代码变成C语言的速度。但长期以来,Cython也受到繁琐和反直觉的语法的困扰,是C和Python的奇怪混合体。

好消息是:近年来,Cython已经开发了一种替代的语法,称为纯Python模式。顾名思义,纯Python模式使用本地Python语法来表达Cython的行为和结构,使Python程序员更容易开始使用Cython。

[ Also on InfoWorld: 4 keys to writing modern Python in 2022 ]

纯Python模式也增强了Cython的最大优势之一。它使我们更容易从传统的Python代码库开始,并逐步将其转化为C代码。此外,在纯Python模式下编写的Cython代码可以选择作为普通Python运行,尽管没有Cython的速度提升。

最后,纯Python模式允许Python提示和代码分析工具与Cython模块一起工作。现有的Python工具文化不必在Cython障碍处结束。

原始的Cython语法

下面是一个用传统Cython语法编写的简短模块。它计算了一个给定数字的斐波那契数列(效率不高)。(注意,我们在这里使用类并不是因为它们是解决这个问题的最好方法,而是因为它们在 Cython 中是如何映射为等价元素的,这一点值得证明。)

class Fibonacci: def __init__(self, start: int): if start<0: raise ValueError("起始数字必须大于 0") self. n = start def calc(self) -> int: return self._calc(self.n) def _calc(self, val: int) -> int: if val == 0: return 0 elif val == 1 or val == 2: return 1 else: return self._calc(val-1)+self._calc(val-2)

在 AMD Ryzen 5 3600 上,该代码的执行时间约为 20 秒。当涉及到数学时,Python的效率很低。如果我们用 Cython 重写这段代码,我们可以大大加快速度。

下面是相同代码的 Cython 版本(以 .pyx 文件扩展名保存):

cdef class Fibonacci: cdef int n def __init__(self, int start): if start<0: raise ValueError("起始数必须大于 0") self. n = start cpdef int calc(self): return self._calc(self.n) cdef int _calc(self, int val): if val == 0: return 0 elif val == 1 or val == 2: return 1 else: return self._calc(val-1)+self._calc(val-2)

这段Cython代码运行得快很多:在同一硬件上大约半秒!但是我们可能会注意到,Cython代码的运行速度是非常慢的。但是你可能已经注意到,Cython的语法可能很混乱。

如果你努力眯起眼睛,你可以看到原始的Python语法仍然在那里,尽管被埋在其他一些不是Python的东西下面。例如,cdef和cpdef被用来声明只有Cython和Cython包装的函数。另外,在对象和函数签名上使用的类型装饰与我们在Python中普遍使用的类型提示语法完全不同。

Cython语法还有很多难以解析的地方,但这个例子应该给你一个大致的概念。

Cython中的纯Python语法

下面是同一模块,以纯Python模式重写(并以常规的.py扩展名保存):

import cython@cython.cclassclass Fibonacci: n: cython.int def __init__(self, start:cython.int): if start<

Cython.int: n: cython.int int): if start<0: raise ValueError("起始数必须大于0") self.n = start @cython.ccall def calc(self) -> cython.int: return self._calc(self.n) @cython.cfunc def _calc(self, val: cython.int) -> cython. int: if val == 0: return 0 elif val == 1 or val == 2: return 1 else: return self._calc(val-1)+self._calc(val-2)

关于这段代码的几件事应该马上就能看出来:

我们通过cython导入的方式将Cython功能添加到代码中,而不是自定义语法。这里显示的所有语法都是标准的 Python.我们的变量的类型提示是以传统的 Python 方式完成的。例如,变量n是在类的层次上用Python类型提示声明的。同样,函数签名也使用了Python风格的类型提示,以表明它们接收和返回什么。为了声明Cython函数和类,我们使用了一个装饰器(Python语法的标准位),而不是cdef/cpdef关键字(完全不标准)。这段代码可以在常规的 Python 中按原样运行。我们可以简单地导入模块并使用它,而不用编译它,尽管我们不会得到 Cython 提供的速度优势。如果我们对它进行编译,导入它将会导入编译后的版本。

纯Python模式下已编译和未编译的代码

纯Python模式下的一个有用的功能是根据代码是在常规Python模式还是已编译的Cython模式下运行,在模块中创建备用的代码路径的方法。一个例子:

如果 cython.compiled: data = cython.cast( cython.p_int, PyMem_Malloc(array_size * cython.sizeof(cython.int)) ) else: data = arr.array("i", [0] * array_size)]data[0] = 32

这里我们根据该代码是否被编译来给 data 赋予两个可能值之一。如果编译了,data 是一个指向使用 Python 运行时间分配的内存区域的指针。如果没有编译,data 是一个由 32 位整数组成的 Python array.array 对象。在这两种情况下,我们都可以用同样的代码来访问数组元素并对其进行设置,无论代码是否被编译。

纯 Python 模式不能做什么 (目前)

纯 Python 模式有一些限制,意味着你还不能在 "经典 "Cython 工作的所有情况下使用它。像Final或Union这样的注解并不被尊重。使用 PEP 484 风格注解的主要原因是为 Cython 自己的类型提示提供一种方便的方式,所以许多在常规 Python 中使用的类型提示还没有被支持。

第二,纯 Python 模式不支持打包的 C 结构 (对于使用一些 C 库来说是非常重要的) 或 C 风格枚举。

[ 收听 Dev with Serdar,在 5 分钟内获得 Python 编码技巧 ]

最后,你不能像在常规 Cython 中那样从纯 Python 模式中调用 C 函数。通常,在Cython中,你可以通过包括对它的引用来调用C函数,比如:

cdef extern from "math.h": cpdef double sin(double x)

纯Python模式没有提供直接这样做的机制,但是你可以使用Cython文档中描述的选择性导入来设计一个变通方法。


本文标签

热门标签

会员评论