在Cython中编码¶
本章讨论的是Cython,这是一种基于Python的编译语言。与Python相比,它的主要优势是代码可以更快(有时是几个数量级),并且可以直接调用C和C++代码。由于Cython本质上是Python语言的超集,人们通常不会在Sage中区分Cython和Python代码(例如,人们谈论“Sage Python库”和“Python编码约定”)。
Python是一种解释性语言,没有声明变量的数据类型。这些特性使编写和调试变得很容易,但有时Python代码可能会很慢。Cython代码看起来很像Python,但它会被转换成C代码(通常是非常高效的C代码),然后编译。因此,它提供了一种对Python开发人员来说很熟悉的语言,但具有提高速度的潜力。Cython还允许Sage开发人员与C和C++进行交互,这比直接使用PythonC API要容易得多。
Cython是已编译版本的Python。它最初是基于PYREX的,但根据Sage的开发人员的需要进行了更改;Cython是与Sage协同开发的。然而,它现在是一个独立的项目,它的使用超出了Sage的范围。因此,它是一种年轻但正在发展的语言,具有年轻但正在发展的文档。有关最新信息,请参阅其网页http://www.cython.org/,,或查看 Language Basics 立即开始。
在Sage中编写Cython代码¶
有几种方法可以在Sage中创建和构建Cython代码。
在Sage Notebook中,任何单元格都以
%cython
。当你评估该单元格时,它被保存到一个文件中。
Cython在其上运行,并在必要时自动链接所有标准Sage库。
生成的共享库文件 (
.so
/.dll
/.dylib
)然后加载到您正在运行的Sage实例中。该单元格中定义的功能现在可供您在笔记本中使用。此外,输出单元格还有一个指向C程序的链接,该程序被编译以创建
.so
文件。A
cpdef
或def
函数,比如说testfunction
中定义的%cython
工作表中的单元格可以导入并在不同的%cython
通过导入同一工作表中的单元格,如下所示:%cython from __main__ import testfunction
创建一个
.spyx
文件并从命令行附加或加载它。这类似于创建一个%cython
笔记本中的单元格,但完全从命令行(而不是从笔记本)工作。创建
.pyx
文件并将其添加到Sage库中。那就跑吧sage -b
来重建赛奇。
附加或加载.spyx文件¶
尝试使用Cython而无需学习任何有关distutils等的知识的最简单方法是创建一个扩展名为 spyx
,代表“Sage派瑞克斯”:
创建文件
power2.spyx
。请在其中添加以下内容:
def is2pow(n): while n != 0 and n%2 == 0: n = n >> 1 return n == 1
启动Sage命令行解释器并加载
spyx
文件(如果您没有安装C编译器,则此操作将失败)。sage: load("power2.spyx") Compiling power2.spyx... sage: is2pow(12) False
请注意,您可以更改 power2.spyx
,然后再次加载它,它将被动态重新编译。您还可以附加 power2.spyx
因此,每当您进行更改时,它都会重新加载:
sage: attach("power2.spyx")
使用Cython是因为它的速度。以下是对2.6 GHz皓龙的定时测试:
sage: %time [n for n in range(10^5) if is2pow(n)]
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536]
CPU times: user 0.60 s, sys: 0.00 s, total: 0.60 s
Wall time: 0.60 s
现在,文件中的代码 power2.spyx
是有效的Python,如果我们将其复制到一个文件中 powerslow.py
加载后,我们会得到以下结果:
sage: load("powerslow.py")
sage: %time [n for n in range(10^5) if is2pow(n)]
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536]
CPU times: user 1.01 s, sys: 0.04 s, total: 1.05 s
Wall time: 1.05 s
顺便说一句,我们甚至可以用带有类型声明的Cython版本获得更快的速度,方法是更改 def is2pow(n):
至 def is2pow(unsigned int n):
。
中断和信号处理¶
在为Sage编写Cython代码时,必须特别小心,以确保代码可以被 CTRL-C
。
Sage使用 cysignals package 有关这一点,请参阅 cysignals documentation 以获取更多信息。
取消Cython代码pickling¶
对Python类和扩展类(如Cython)的pickling是不同的。中讨论了这一点 Python pickling documentation 。要取消对扩展类的筛选,需要编写一个 __reduce__()
方法,该方法通常返回元组 (f, args, ...)
以至于 f(*args)
返回原始对象(其副本)。作为示例,下面的代码片段是 __reduce__()
方法来自 sage.rings.integer.Integer
:
def __reduce__(self):
'''
This is used when pickling integers.
EXAMPLES::
sage: n = 5
sage: t = n.__reduce__(); t
(<cyfunction make_integer at ...>, ('5',))
sage: t[0](*t[1])
5
sage: loads(dumps(n)) == n
True
'''
# This single line below took me HOURS to figure out.
# It is the *trick* needed to pickle Cython extension types.
# The trick is that you must put a pure Python function
# as the first argument, and that function must return
# the result of unpickling with the argument in the second
# tuple as input. All kinds of problems happen
# if we don't do this.
return sage.rings.integer.make_integer, (self.str(32),)