Python 高手编程系列一千二百六十四:Cython 作为一门语言

Cython 不仅是一个编译器,而且也是一个 Python 语言的超集。超集意味着任何有效的
Python 代码是允许的,并且它还具有一些额外的特性,如支持调用 C 函数或声明 C 类型的
变量和类属性。所以任何用 Python 编写的代码都可以用作为 Cython 语言使用。这解释了
为什么可以通过 Cython 编译器很容易地将普通的 Python 模块编译到 C。
但我们不会止步于这个简单的事实。我们引用的 fibonacci()函数也是 Python 超集
中的扩展的有效代码,我们将尝试做一点改进。我们不会对函数设计进行实质的优化,只
是做一些小的改动,使用 Cython 编写,我们就可以从中受益。
Cython 源文件使用不同的文件扩展名。它的扩展名是.pyx 而不是.py。假设我们仍
然想要实现我们的斐波纳契数列。fibonacci.pyx 的内容看起来可能是像这样的:
“”“提供斐波纳契数列函数的 Cython 模块”“”
def fibonacci(unsigned int n):
“”“递归计算返回斐波那契数列的第 n 项”“”
if n < 2:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
正如你可以看到的,唯一真正改变的是 fibonacci()函数的签名。由于 Cython 中的
可选静态类型,我们可以将 n 参数声明为 unsigned int,这会稍微改进我们函数的工作
方式。此外,它比以前用手写扩展时做的更多。如果 Cython 函数的参数声明为静态类型,
那么扩展将通过抛出合适的异常来自动处理转换和溢出的异常,如下所示:

from fibonacci import fibonacci
fibonacci(5)
5
fibonacci(-1)
Traceback (most recent call last):
File “”, line 1, in
File “fibonacci.pyx”, line 21, in fibonacci.fibonacci (fibonacci.c:704)
OverflowError: can’t convert negative value to unsigned int
fibonacci(10 ** 10)
Traceback (most recent call last):
File “”, line 1, in
File “fibonacci.pyx”, line 21, in fibonacci.fibonacci (fibonacci.c:704)
OverflowError: value too large to convert to unsigned int
我们已经知道 Cython 只能进行源到源的编译,而生成的代码使用相同的 Python/C API,
我们在手动为扩展编写 C 代码时也使用到了这些 API。注意 fibonacci()是一个递归函
数,所以它经常调用它自己。这意味着虽然我们为输入参数声明了一个静态类型,但在递
归调用期间,它会像对待其他 Python 函数一样对待自己。因此,n-1 和 n-2 将被打包成
Python 对象,然后传递到 fibonacci()的内部实现,该实现会再次返回 unsigned int
类型。这将会一次又一次地重复发生,直到我们达到最终的递归深度。但涉及到比真正需
要更多的参数处理时,就可能出现问题。
我们可以通过将更多的工作委托给一个不知道 Python 结构的纯 C 函数来减少 Python
函数调用和参数处理的开销。之前,我们在使用纯 C 创建 C 扩展时这样做过,同样,也可
以在 Cython 中这样做。我们可以使用 cdef 关键字声明接受和返回 C 类型的 C 风格函数:
cdef long long fibonacci_cc(unsigned int n):
if n < 2:
return n
else:
return fibonacci_cc(n - 1) + fibonacci_cc(n - 2)
def fibonacci(unsigned int n):
“”" 递归计算返回斐波那契数列的第 n 项
“”"
return fibonacci_cc(n)
我们可以进一步优化。有了一个简单的 C 示例,我们终于展示了如何在调用我们的纯
C 函数期间释放 GIL,因此这个扩展对于多线程应用程序更加友好。在前面的例子中,我
们使用了来自 Python/C API 头文件中的 Py_BEGIN_ALLOW_THREADS 和 Py_END_ALLOW_
THREADS 预处理器宏,便于让 Python 调用这些代码。Cython 语法更短,更容易记住。在
代码中使用简单的 with nogil 语句可以释放 GIL,如下所示:
def fibonacci(unsigned int n):
“”" 递归计算返回斐波那契数列的第 n 项 “”"
with nogil:
result = fibonacci_cc(n)
return fibonacci_cc(n)
你也可以标记整个 C 风格函数是无 GIL 的安全的调用:
cdef long long fibonacci_cc(unsigned int n) nogil:
if n < 2:
return n
else:
return fibonacci_cc(n - 1) + fibonacci_cc(n - 2)
重要的是要知道这样的函数不能有 Python 对象作为参数或返回类型。每当标记为
nogil 的函数需要执行任何 Python/C API 调用时,它必须使用 with gil 语句获取 GIL。

猜你喜欢

转载自blog.csdn.net/2301_77888392/article/details/143423244