用Python为Sage编写代码¶
本章讨论了Sage中编码的一些问题和建议。
设计¶
如果您计划为Sage开发一些新代码,那么设计是很重要的。所以,考虑一下你的程序将做什么,以及它如何与Sage的结构相匹配。特别是,Sage的大部分是用面向对象语言Python实现的,并且有一个组织代码和功能的类的层次结构。例如,如果您实现了一个环的元素,那么您的类应该从 sage.structure.element.RingElement
,而不是从头开始。试着找出你的代码应该如何与其他Sage代码相匹配,并相应地进行设计。
特殊Sage函数¶
带前导和尾随双下划线的函数 __XXX__
都是由Python预定义的。带前导和尾随单下划线的函数 _XXX_
是为Sage定义的。带有一个前导下划线的函数是半私有的,而带有双前导下划线的函数被认为是真正私有的。用户可以创建带有前导和尾随下划线的函数。
正如Python对对象有许多标准的特殊方法一样,Sage也有特殊的方法。它们是典型的形式 _XXX_
. 在少数情况下,尾随下划线不包括在内,但这最终将被更改,以便始终包含尾随下划线。本节介绍这些特殊方法。
Sage中的所有对象都应该派生自Cython扩展类 SageObject
:
from sage.structure.sage_object import SageObject
class MyClass(SageObject,...):
...
或者其他已经存在的Sage阶层:
from sage.rings.ring import Algebra
class MyFavoriteAlgebra(Algebra):
...
你应该实现 _latex_
和 _repr_
方法。其他方法取决于对象的性质。
Latex 表现¶
每一个物体 x
在萨奇应该支持命令 latex(x)
,因此任何Sage的对象都可以通过 Latex 轻松准确地显示出来。下面是如何使类(及其实例)支持该命令 latex
.
定义方法
_latex_(self)
返回对象的LaTeX表示。它应该是可以在数学模式下正确排版的东西。不包括期初和期末美元。通常对象是用其他Sage对象构建的,这些组件应该使用
latex
功能。例如,如果c
是你的对象的系数,你想排版c
使用 Latex ,使用latex(c)
而不是c._latex_()
,因为c
可能没有_latex_
方法,以及latex(c)
知道如何处理这件事。别忘了包括一个docstring和一个说明对象的LaTeX生成的示例。
可以使用中包含的任何宏
amsmath
,amssymb
或amsfonts
,或SAGE_ROOT/doc/commontex/macros.tex
.
一个示例模板 _latex_
方法如下。注意 .. skip
代码中不应该包含行;它是为了防止doctest在这个伪示例上运行。
class X:
...
def _latex_(self):
r"""
Return the LaTeX representation of X.
EXAMPLES::
sage: a = X(1,2)
sage: latex(a)
'\\frac{1}{2}'
"""
return '\\frac{%s}{%s}'%(latex(self.numer), latex(self.denom))
如示例所示, latex(a)
将生成表示对象的LaTeX代码 a
. 打电话 view(a)
将显示此的排版版本。
打印表示¶
标准的Python打印方法是 __repr__(self)
. 在Sage中,这是指从 SageObject
(这是Sage中的一切),而是定义 _repr_(self)
. 这是更好的,因为如果你只定义 _repr_(self)
而不是 __repr__(self)
,然后用户可以重命名您的对象以进行打印。此外,某些对象的打印方式应根据上下文的不同而有所不同。
下面是一个 _latex_
和 _repr_
的函数 Pi
班级。这是档案里的 SAGE_ROOT/src/sage/functions/constants.py
:
class Pi(Constant):
"""
The ratio of a circle's circumference to its diameter.
EXAMPLES::
sage: pi
pi
sage: float(pi) # rel tol 1e-10
3.1415926535897931
"""
...
def _repr_(self):
return "pi"
def _latex_(self):
return "\\pi"
来自对象的矩阵或向量¶
提供一个 _matrix_
方法,该对象可以强制为环上的矩阵 R . 那么Sage函数 matrix
将为这个对象工作。
以下是来自 SAGE_ROOT/src/sage/graphs/graph.py
:
class GenericGraph(SageObject):
...
def _matrix_(self, R=None):
if R is None:
return self.am()
else:
return self.am().change_ring(R)
def adjacency_matrix(self, sparse=None, boundary_first=False):
...
同样,提供 _vector_
方法,该对象可以强制为环上的向量 R . 那么Sage函数 vector
将为这个对象工作。以下是文件中的内容 SAGE_ROOT/sage/sage/modules/free_module_element.pyx
:
cdef class FreeModuleElement(element_Vector): # abstract base class
...
def _vector_(self, R):
return self.change_ring(R)
Sage准备¶
为了使Python更具交互性,当您从命令行或通过笔记本使用Sage时,对语法进行了许多调整(但Sage库中的Python代码则没有)。从技术上讲,这是由 preparse()
重写输入字符串的函数。最值得注意的是,进行了以下替换:
Sage支持一种特殊的语法来生成环,或者更普遍地说,使用命名生成器生成父元素:
sage: R.<x,y> = QQ[] sage: preparse('R.<x,y> = QQ[]') "R = QQ['x, y']; (x, y,) = R._first_ngens(2)"
整数和实数是Sage整数和Sage浮点数。例如,在纯Python中,这将是一个属性错误:
sage: 16.sqrt() 4 sage: 87.factor() 3 * 29
未准备好原始文字,从效率的角度来看,这可能是有用的。就像Python的int由L表示一样,在Sage的raw integer和浮点字面值后面跟一个表示raw的“r”(或“r”),意思是没有准备好。例如::
sage: a = 393939r sage: a 393939 sage: type(a) <... 'int'> sage: b = 393939 sage: type(b) <type 'sage.rings.integer.Integer'> sage: a == b True
原始文字在某些情况下非常有用。例如,当Python整数非常小时,它们可以比Sage整数更有效。大型Sage整数比Python整数要高效得多,因为它们是使用gmpc库实现的。
查阅档案 preparser.py
关于Sage准备的更多细节,更多涉及原始文字的例子,等等。
当一个文件 foo.sage
在Sage会话中加载或附加,它是 foo.sage
是用名称创建的 foo.sage.py
. 准备好的文件的开头说明:
This file was *autogenerated* from the file foo.sage.
可以使用 --preparse
命令行选项:运行::
sage --preparse foo.sage
创建文件 foo.sage.py
.
以下文件与在Sage中编制有关:
SAGE_ROOT/src/bin/sage
SAGE_ROOT/src/bin/sage-preparse
SAGE_ROOT/src/sage/repl/preparse.py
尤其是文件 preparse.py
包含Sage preparser代码。
Sage强制模式¶
主要的目的是使算术的主要目的是透明地进行集合之间的比较。例如,当一个人写 3 + 1/2 ,尽管左边的项是一个整数,但人们希望对作为有理数的操作数执行算术运算。这是有意义的,因为在有理数中包含了明显和自然的整数。强制系统的目标是促进这一点(以及更复杂的算法),而不必显式地将所有内容映射到同一个域中,同时还要严格到不解决歧义或接受无稽之谈。
Sage的强制模型在Sage参考手册的强制部分详细描述,并附有示例。
易变性¶
父结构(例如环、字段、矩阵空间等)应该是不可变的,并且尽可能是全局唯一的。不变性意味着,除其他外,诸如生成器标签和默认强制精度之类的属性不能更改。
在不浪费内存的情况下,全局唯一性最好使用标准pythonweakref模块、工厂函数和module scope变量来实现。
某些对象,例如矩阵,开始时可能是可变的,后来变为不可变的。查看文件 SAGE_ROOT/src/sage/structure/mutability.py
.
这个 __hash__ 特殊方法¶
以下是对 __hash__
从Python参考手册:
由内置函数调用
hash()
以及对散列集合成员(包括set、frozenset和dict)的操作。__hash__()
应返回一个整数。唯一需要的属性是比较相等的对象具有相同的哈希值;建议以某种方式混合(例如使用exclusive or)对象组件的哈希值,这些组件在对象比较中也起着一定的作用。如果类没有定义__cmp__()
方法不应定义__hash__()
操作;如果它定义__cmp__()
或__eq__()
但不是__hash__()
,它的实例将不能用作字典键。如果类定义可变对象并实现__cmp__()
或__eq__()
方法,不应实现__hash__()
,因为字典实现要求键的哈希值是不可变的(如果对象的哈希值更改,它将位于错误的哈希桶中)。
注意这句话,“唯一需要的属性是比较相等的对象具有相同的哈希值。”这是Python语言所做的一个假设,在Sage中我们根本无法做出(!)违反它会有后果。幸运的是,后果是非常明确的,并且相当容易理解,所以如果你知道了它们,它们不会给你带来麻烦。下面的例子很好地说明了它们:
sage: v = [Mod(2,7)]
sage: 9 in v
True
sage: v = set([Mod(2,7)])
sage: 9 in v
False
sage: 2 in v
True
sage: w = {Mod(2,7):'a'}
sage: w[2]
'a'
sage: w[9]
Traceback (most recent call last):
...
KeyError: 9
下面是另一个例子:
sage: R = RealField(10000)
sage: a = R(1) + R(10)^-100
sage: a == RDF(1) # because the a gets coerced down to RDF
True
但是 hash(a)
不应相等 hash(1)
.
不幸的是,在Sage身上我们根本无法要求
(#) "a == b ==> hash(a) == hash(b)"
因为严肃的数学对于这个规则来说太复杂了。例如,等式 z == Mod(z, 2)
和 z == Mod(z, 3)
会迫使 hash()
在整数上保持不变。
我们能够“修复”这个问题的唯一方法就是放弃使用 ==
运算符,并将Sage equality作为附加到每个对象的新方法来实现。然后我们可以遵循Python规则 ==
而我们对其他一切的规则,以及所有的Sage代码都将变得完全不可读(在这个问题上是不可改写的)。所以我们只能接受它。
所以Sage所做的就是试图满足 (#)
当这样做是相当容易的,但要运用判断力,不要过火。例如,
sage: hash(Mod(2,7))
2
输出2比一些随机散列(也涉及模)要好,但是从Python的角度来看,它当然不是正确的,因为 9 == Mod(2,7)
. 目标是生成一个快速的散列函数,但在合理的范围内考虑任何明显的自然包含和强制。
例外情况¶
请避免像这样捕获所有代码:
try:
some_code()
except: # bad
more_code()
如果您没有显式列出任何异常(作为元组),那么您的代码将捕获任何内容,包括 ctrl-C
,代码中的错误和警报,这将导致混乱。此外,这可能会捕捉到应该传播给用户的实际错误。
总而言之,只捕获特定的异常,如下例所示:
try:
return self.__coordinate_ring
except (AttributeError, OtherExceptions) as msg: # good
more_code_to_compute_something()
注意中的语法 except
是列出作为元组捕获的所有异常,后跟错误消息。
进口¶
我们提到了导入的两个问题:循环导入和导入大型第三方模块。
首先,你必须避免循环进口。例如,假设文件 SAGE_ROOT/src/sage/algebras/steenrod_algebra.py
从一行开始:
from sage.sage.algebras.steenrod_algebra_bases import *
那文件呢 SAGE_ROOT/src/sage/algebras/steenrod_algebra_bases.py
从一行开始:
from sage.sage.algebras.steenrod_algebra import SteenrodAlgebra
这就建立了一个循环:加载其中一个文件需要另一个文件,而另一个文件又需要第一个文件,依此类推。
使用此设置,运行Sage将产生错误:
Exception exceptions.ImportError: 'cannot import name SteenrodAlgebra'
in 'sage.rings.polynomial.polynomial_element.
Polynomial_generic_dense.__normalize' ignored
-------------------------------------------------------------------
ImportError Traceback (most recent call last)
...
ImportError: cannot import name SteenrodAlgebra
相反,您可以替换 import *
在文件顶部的行中按代码中需要的更具体的导入。例如 basis
类的方法 SteenrodAlgebra
可能如下所示(省略文档字符串):
def basis(self, n):
from steenrod_algebra_bases import steenrod_algebra_basis
return steenrod_algebra_basis(n, basis=self._basis_name, p=self.prime)
第二,不要在模块的顶层导入第三方模块(例如matplotlib)。如上所述,您可以在需要时导入模块的特定组件,而不是在文件的顶层。
重要的是 from sage.all import *
尽可能快,因为这是Sage启动时间的主导因素,控制顶级进口有助于做到这一点。Sage中的一个重要机制是lazy导入,它不实际执行导入,而是将其延迟到对象实际使用之前。看到了吗 sage.misc.lazy_import
有关惰性导入的更多详细信息,以及 文件和目录结构 例如,为新模块使用lazy导入。
贬低¶
当制作 backward-incompatible 在Sage中的修改,旧代码应该继续工作,并显示一条消息,指示将来应该如何更新/编写它。我们称之为 贬抑 .
注解
弃用的代码只能在其出现的第一个稳定版本一年后删除。
每个弃用警告都包含定义它的trac票证号。我们在下面的例子中使用666。对于每个条目,请参考函数的文档,以获取有关其行为和可选参数的更多信息。
重命名关键字: 通过用
rename_keyword
,任何用户呼叫my_function(my_old_keyword=5)
将看到警告:from sage.misc.decorators import rename_keyword @rename_keyword(deprecation=666, my_old_keyword='my_new_keyword') def my_function(my_new_keyword=True): return my_new_keyword
Rename a function/method: 调用
deprecated_function_alias()
要获取引发不推荐警告的函数的副本,请执行以下操作:from sage.misc.superseded import deprecated_function_alias def my_new_function(): ... my_old_function = deprecated_function_alias(666, my_new_function)
将对象移动到其他模块: 如果重命名源文件或将某个函数(或类)移到其他文件,则仍可以从旧模块导入该函数。可以使用
lazy_import()
带着嘲讽。在旧模块中,您将写下:from sage.misc.lazy_import import lazy_import lazy_import('sage.new.module.name', 'name_of_the_function', deprecation=666)
您还可以使用
*
或者一些使用元组的函数:from sage.misc.lazy_import import lazy_import lazy_import('sage.new.module.name', '*', deprecation=666) lazy_import('sage.other.module', ('func1', 'func2'), deprecation=666)
从全局命名空间中删除名称: 在这种情况下,您需要从全局命名空间中删除名称(例如,
sage.all
或者其他什么all.py
文件),但您希望通过显式导入保持功能可用。本例与前一个类似:使用带有deprecation的lazy import。一个细节:在这种情况下,你不需要名字lazy_import
若要在全局命名空间中可见,请添加前导下划线:from sage.misc.lazy_import import lazy_import as _lazy_import _lazy_import('sage.some.package', 'some_function', deprecation=666)
任何其他情况: 如果以上情况均不适用,请致电
deprecation()
在要弃用的函数中。它将显示您选择的消息(并与doctest框架正确交互):from sage.misc.superseded import deprecation deprecation(666, "Do not use your computer to compute 1+1. Use your brain.")
实验/不稳定代码¶
您可以将新创建的代码(类/函数/方法)标记为实验性的/不稳定的。在这种情况下,在更改此代码、其功能或其接口时,不需要任何弃用警告。
这可以让你尽早把你的东西放在Sage中,而不用担心以后会做出(设计)改变。
当对代码满意(稳定一段时间,比如一年)时,可以删除此警告。
和往常一样,所有代码都必须经过充分的文档化处理,并通过我们的审阅过程。
Experimental function/method: 使用装饰器
experimental
. 下面是一个例子:from sage.misc.superseded import experimental @experimental(66666) def experimental_function(): # do something
实验班: 使用装饰器
experimental
为其__init__
. 下面是一个例子:from sage.misc.superseded import experimental class experimental_class(SageObject): @experimental(66666) def __init__(self, some, arguments): # do something
任何其他情况: 如果以上情况均不适用,请致电
experimental_warning()
在你想要警告的代码中。它将显示您选择的消息:from sage.misc.superseded import experimental_warning experimental_warning(66666, 'This code is not foolproof.')
使用可选软件包¶
如果一个函数需要一个可选的包,那么该函数应该很正常地失败——也许使用 try
-除了“block”——当可选软件包不可用时,应该给出关于如何安装它的提示。例如,键入 ``sage -optional
提供所有可选包的列表,因此它可能建议用户键入该列表。命令 optional_packages()
从Sage内部也返回此列表。