用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++接口相比,直接使用Python C API要容易得多。
Cython是Python的编译版本。它最初是基于Pyrex的,但是根据Sage的开发人员需要而改变了;Cython是与Sage一起开发的。但是,它现在是一个独立的项目,它的使用范围超出了Sage的范围。因此,它是一种年轻的,但正在发展的语言,有着年轻的,但正在发展的文档。看看它的网页,http://www.cython.org/,以获取最新信息或查看 Language Basics 立即开始。
用Sage编写Cython代码¶
有几种方法可以在Sage中创建和构建Cython代码。
在Sage笔记本中,以
%cython
. 当你评估那个细胞时,它被保存到一个文件中。
Cython在它上运行,所有的标准Sage库都会在必要时自动链接起来。
生成的共享库文件 (
.so
/.dll
/.dylib
)然后加载到Sage的运行实例中。在该单元格中定义的功能现在可供您在笔记本中使用。另外,输出单元有一个链接到C程序,该程序被编译来创建
.so
文件。A
cpdef
或def
功能,比如说testfunction
,定义为%cython
可以导入工作表中的单元格,并在其他%cython
导入同一工作表中的单元格,如下所示:%cython from __main__ import testfunction
创建一个
.spyx
文件并从命令行附加或加载它。这类似于创建%cython
单元格在笔记本中,但完全从命令行工作(而不是从笔记本)。创建一个
.pyx
归档并添加到Sage库中。首先,将Cython扩展的列表添加到变量中
ext_modules
在文件中SAGE_ROOT/src/module_list.py
. 见distutils.extension.Extension
类获取有关创建新Cython扩展的详细信息。跑
sage -b
重建Sage。
例如,为了编译
SAGE_ROOT/src/sage/graphs/chrompoly.pyx
,我们在module_list.py
:Extension('sage.graphs.chrompoly', sources = ['sage/graphs/chrompoly.pyx'], libraries = ['gmp']),
附加或加载.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 Opteron上进行的定时测试:
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代码¶
Python类和扩展类(如Cython)的pickle是不同的。这在 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),)