教程:Python和Sage中的对象和类¶
本教程是对Python和Sage中的面向对象编程的介绍。它需要有关命令式/过程性编程(最常见的编程风格)的基本知识--即条件指令、循环、函数(请参阅Sage教程的“编程”部分)--但不假定有关于对象和类的进一步知识。它被设计为正式介绍和练习的交替序列。 练习的解决方案 都在最后给出了。
前言:变量、名称和对象¶
作为一种面向对象的语言,对于习惯于命令式语言(如C或Maple)的人来说,Python的“变量”行为可能会令人惊讶。原因是他们是 not variables but names 。
下面的解释是 borrowed from David Goodger 。
其他语言也有“变量”¶
在许多其他语言中,为变量赋值会将值放入框中。
int a = 1;
|
![]() |
框“a”现在包含整数1。
将另一个值赋给同一变量将替换框中的内容:
a = 2;
|
![]() |
现在框“a”包含一个整数2。
将一个变量赋给另一个变量会复制该值并将其放入新框中:
int b = a;
|
![]() |
![]() |
“b”是第二个盒子,有一个整数2的拷贝。盒子“a”有一个单独的拷贝。
Python 有“名字”¶
在Python中,“名称”或“标识符”就像是贴在对象上的地块标签(或名称标签)。
a = 1
|
![]() |
这里,一个整数1对象有一个标记为“a”的标签。
如果我们重新赋值为“a”,我们只需将标记移动到另一个对象:
a = 2
|
![]() |
![]() |
现在,名称“a”被附加到一个整数2对象。
原来的INTEGER 1对象不再有标记“a”。它可能会继续存在,但我们不能通过“a”这个名字到达它。(当对象不再有引用或标记时,它将从内存中删除。)
如果我们将一个名字分配给另一个名字,我们只是将另一个名字标签附加到现有对象:
b = a
|
![]() |
名称“b”只是与“a”绑定到同一对象的第二个标签。
尽管我们通常甚至在Python中也提到“变量”(因为这是常见的术语),但我们实际上指的是“名称”或“标识符”。在Python中,“变量”是值的名称标签,而不是标有标签的框。
警告
因此,当有 two tags “a”和“b” same object ,修改标记为“b”的对象也会修改标记为“a”的对象::
sage: a = [1,2,3]
sage: b = a
sage: b[1] = 0
sage: a
[1, 0, 3]
请注意,重新分配标记“b”(而不是修改带有该标记的对象)不会影响标记为“a”的对象::
sage: b = 7
sage: b
7
sage: a
[1, 0, 3]
面向对象编程范型¶
这个 object-oriented programming 范式依赖于以下两条基本规则:
任何需要由计算机操纵的真实(或数学)世界的任何东西都由一个 object 。
每个对象都是一个 instance 一些人 class 。
在这一点上,这两条规则有点没有意义,所以让我们或多或少地给出一些准确的术语定义:
- object
一个 portion of memory 其中包含对真实世界的事物进行建模所需的信息。
- class
定义 data structure 用于将作为类实例的对象与其 behavior 。
让我们从一些例子开始:我们考虑向量空间 QQ 其基由排列和其中的特定元素索引:
sage: F = CombinatorialFreeModule(QQ, Permutations())
sage: el = 3*F([1,3,2])+ F([1,2,3])
sage: el
B[[1, 2, 3]] + 3*B[[1, 3, 2]]
(对于每个排列,例如 [1, 3, 2]
中的相应元素 F
表示为 B[[1, 3, 2]]
--在 CombinatorialFreeModule
,如果元素由 x
,则默认情况下其打印表示为 B[x]
。)
在Python中,一切都是对象,所以类型和类之间没有任何区别。可以获取对象的类 el
发信人::
sage: type(el)
<class 'sage.combinat.free_module.CombinatorialFreeModule_with_category.element_class'>
因此,这不是很有信息量。我们稍后再来讨论这个问题。与对象相关联的数据存储在所谓的 attributes 。它们是通过语法访问的 obj.attribute_name
。对于组合自由模的元素,主属性称为 _monomial_coefficients
。它是一个将系数与指数相关联的词典:
sage: el._monomial_coefficients
{[1, 2, 3]: 1, [1, 3, 2]: 3}
修改属性会修改对象:
sage: el._monomial_coefficients[Permutation([3,2,1])] = 1/2
sage: el
B[[1, 2, 3]] + 3*B[[1, 3, 2]] + 1/2*B[[3, 2, 1]]
警告
作为用户,您是 not 应该自己做这样的修改(请参阅 private attributes (见下文)。
作为向量空间的一个元素, el
具有特定的行为::
sage: 2*el
2*B[[1, 2, 3]] + 6*B[[1, 3, 2]] + B[[3, 2, 1]]
sage: sorted(el.support())
[[1, 2, 3], [1, 3, 2], [3, 2, 1]]
sage: el.coefficient([1, 2, 3])
1
The behavior is defined through methods (support
, coefficient
). Note
that this is true even for equality, printing or mathematical operations. For
example, the call a == b
actually is translated to the method call
a.__eq__(b)
. The names of those special methods which are usually called
through operators are fixed by the Python language and are of the form
__name__
. Examples include __eq__
and __le__
for operators ==
and
<=
, __repr__
(see 关于课程的SAGE细节) for printing, __add__
and
__mult__
for operators +
and *
. See
http://docs.python.org/library/ for a complete list.
sage: el.__eq__(F([1,3,2]))
False
sage: el.__repr__()
'B[[1, 2, 3]] + 3*B[[1, 3, 2]] + 1/2*B[[3, 2, 1]]'
sage: el.__mul__(2)
2*B[[1, 2, 3]] + 6*B[[1, 3, 2]] + B[[3, 2, 1]]
备注
我们现在创建一个自定义 Element
类来解释属性在Python中的工作方式的详细信息(您可以忽略 parent
在下面的代码中,这在这里不相关)::
sage: from sage.structure.element import Element
sage: class MyElt(Element):
....: def __init__(self, parent, val):
....: super(MyElt, self).__init__(parent)
....: self.value = val
sage: el = MyElt(val=42, parent=ZZ)
sage: el
Generic element of a structure
该类存储在一个名为 __class__
,并且普通属性存储在名为 __dict__
**
sage: el.__dict__
{'value': 42}
sage: el.__class__
<class '__main__.MyElt'>
某些特定操作会修改 el
**
sage: el.rename("bla")
sage: el
bla
sage: el.__dict__
{'_SageObject__custom_name': 'bla', 'value': 42}
很多Sage对象不是Python对象,而是编译后的Cython对象。Python将它们视为内置对象,您无权访问它们的一些内部数据结构。例如,基类 Element
存储的父级 el
作为Cython属性 _parent
但它不会出现在 __dict__
而且我们不能从Python访问它。
Cython类的一些示例(从技术上讲, extension types )包括整数和置换群元素。这些产品都没有 __dict__
根本没有::
sage: e = Integer(9)
sage: type(e)
<class 'sage.rings.integer.Integer'>
sage: e.__dict__
Traceback (most recent call last):
...
AttributeError: 'sage.rings.integer.Integer' object has no attribute '__dict__'...
sage: id4 = SymmetricGroup(4).one()
sage: type(id4)
<class 'sage.groups.perm_gps.permgroup_element.SymmetricGroupElement'>
sage: id4.__dict__
Traceback (most recent call last):
...
AttributeError: 'sage.groups.perm_gps.permgroup_element.SymmetricGroupElement' object has no attribute '__dict__'...
备注
每个对象对应于内存的一部分,称为其 identity 在Python中。您可以使用以下命令获取身份 id
**
sage: el = Integer(9)
sage: id(el) # random
139813642977744
sage: el1 = el; id(el1) == id(el)
True
sage: el1 is el
True
在Python中(因此在Sage中也是如此),具有相同身份的两个对象将是相等的,但反之亦然。因此,恒等式函数不同于数学恒等式:
sage: el2 = Integer(9)
sage: el2 == el1
True
sage: el2 is el1
False
sage: id(el2) == id(el)
False
摘要¶
要定义某个对象,首先必须编写一个 class 。该类将定义对象的方法和属性。
- method
与对象相关联的特定类型的函数,用于获取有关该对象的信息或对其进行操作。
- attribute
存储有关对象的信息的变量。
一个例子:餐馆里的一杯饮料¶
让我们写一个关于餐厅玻璃杯的小班::
sage: class Glass():
....: def __init__(self, size):
....: assert size > 0
....: self._size = float(size) # an attribute
....: self._content = float(0.0) # another attribute
....: def __repr__(self):
....: if self._content == 0.0:
....: return "An empty glass of size %s"%(self._size)
....: else:
....: return "A glass of size %s cl containing %s cl of water"%(
....: self._size, self._content)
....: def fill(self):
....: self._content = self._size
....: def empty(self):
....: self._content = float(0.0)
让我们创建一个小玻璃::
sage: myGlass = Glass(10); myGlass
An empty glass of size 10.0
sage: myGlass.fill(); myGlass
A glass of size 10.0 cl containing 10.0 cl of water
sage: myGlass.empty(); myGlass
An empty glass of size 10.0
以下是一些评论:
类的定义
Glass
定义了两个属性,_size
和_content
。它定义了四种方法,__init__
,__repr__
,fill
,以及empty
。(此类的任何实例还将具有从该类继承的其他属性和方法object
。看见 Inheritance (如下所示。)方法
__init__
用于初始化对象:它由所谓的 constructor 调用时执行的类的Glass(10)
。方法
__repr__
返回用于打印对象的字符串,例如在本例中计算myGlass
。
备注
Private Attributes
大多数情况下,为了确保数据结构的一致性,用户不应该直接更改对象的某些属性。这些属性称为 private 。由于在Python中没有确保隐私的机制,因此约定如下:私有属性的名称以下划线开头。
因此,只能通过方法进行属性访问。读取或写入私有属性的方法称为访问器。
仅供内部使用的方法也带有下划线前缀。
习题¶
添加方法
is_empty
如果杯子是空的,则返回TRUE。定义一种方法
drink
使用一个参数amount
这使得人们可以部分喝掉杯子里的水。如果一个人要求喝比杯子里的水更多的水或负量的水,就会出现错误。允许在玻璃杯中装满葡萄酒、啤酒或其他饮料。方法
fill
应接受参数beverage
。饮料存储在属性中_beverage
。更新方法__repr__
相应地。添加属性
_clean
和方法is_clean
和wash
。在创造的时候,杯子是干净的,一装满就变脏了,必须洗干净才能变得干净。测试所有的东西。
确保所有东西都经过了测试。
再测试一次所有东西。
继承¶
问题:目标: different 类可以共享一个 common behavior 。
例如,如果一个人想要处理不同的盘子(叉子、勺子等),那么就会有常见的行为(弄脏和被清洗)。因此,与不同种类的菜肴相关的不同等级应该是相同的 clean
, is_clean
和 wash
方法:研究方法。但复制和粘贴代码非常不利于维护:错误会被复制,要想更改任何东西,必须记住所有副本的位置。因此,需要一种允许程序员分解常见行为的机制。它被称为 inheritance 或 sub-classing :编写一个基类,该基类分解公共行为,然后重用该类中的方法。
我们首先编写一个小类‘’AbstractDish‘’,它实现“清理-脏-洗”行为::
sage: class AbstractDish():
....: def __init__(self):
....: self._clean = True
....: def is_clean(self):
....: return self._clean
....: def state(self):
....: return "clean" if self.is_clean() else "dirty"
....: def __repr__(self):
....: return "An unspecified %s dish"%self.state()
....: def _make_dirty(self):
....: self._clean = False
....: def wash(self):
....: self._clean = True
现在,您可以在类中重用此行为 Spoon
**
sage: class Spoon(AbstractDish): # Spoon inherits from AbstractDish
....: def __repr__(self):
....: return "A %s spoon"%self.state()
....: def eat_with(self):
....: self._make_dirty()
让我们测试一下::
sage: s = Spoon(); s
A clean spoon
sage: s.is_clean()
True
sage: s.eat_with(); s
A dirty spoon
sage: s.is_clean()
False
sage: s.wash(); s
A clean spoon
摘要¶
任何类都可以重复使用另一个类的行为。一种说法是,这个子类 inherits 从超类或它的 derives 从它那里。
子类的任何实例也是其超类的实例::
sage: type(s) <class '__main__.Spoon'> sage: isinstance(s, Spoon) True sage: isinstance(s, AbstractDish) True
如果一个子类重新定义了一个方法,那么它将替换前一个方法。一种说法是,这个子类 overloads 该方法。不过,可以显式调用隐藏的超类方法。
sage: s.__repr__() 'A clean spoon' sage: Spoon.__repr__(s) 'A clean spoon' sage: AbstractDish.__repr__(s) 'An unspecified clean dish'
备注
Advanced superclass method call
有时,人们想要调用重载方法,而不知道它是在哪个类中定义的。为此,请使用 super
操作员::
sage: super(Spoon, s).__repr__()
'An unspecified clean dish'
此构造的一个非常常见的用法是调用 __init__
超类的方法::
sage: class Spoon(AbstractDish):
....: def __init__(self):
....: print("Building a spoon")
....: super(Spoon, self).__init__()
....: def __repr__(self):
....: return "A %s spoon"%self.state()
....: def eat_with(self):
....: self._make_dirty()
sage: s = Spoon()
Building a spoon
sage: s
A clean spoon
习题¶
修改类
Glasses
这样它就继承了Dish
。写一个类
Plate
它的实例可以包含与类一起的任何用餐Fork
。尽可能避免代码重复(提示:您可以编写分解的类ContainerDish
)。测试所有的东西。
关于课程的SAGE细节¶
与Python相比,Sage有特殊的对象处理方式:
Sage中的任何数学对象类都应该继承自
SageObject
而不是来自object
。大多数情况下,它们实际上继承自一个子类,比如Parent
或Element
。打印应通过以下方式完成
_repr_
而不是__repr__
以允许重命名。更一般地,特定于SAGE的特殊方法通常被命名为
_meth_
而不是__meth__
。例如,许多类实现了_hash_
它由使用和缓存__hash__
。同样,组的元素通常实现_mul_
,这样就不需要注意强迫行为,因为它们是在__mul__
。
有关详细信息,请参阅《Sage开发人员指南》。
练习的解决方案¶
以下是第一个练习的解决方案:
sage: class Glass(): ....: def __init__(self, size): ....: assert size > 0 ....: self._size = float(size) ....: self.wash() ....: def __repr__(self): ....: if self._content == 0.0: ....: return "An empty glass of size %s"%(self._size) ....: else: ....: return "A glass of size %s cl containing %s cl of %s"%( ....: self._size, self._content, self._beverage) ....: def content(self): ....: return self._content ....: def beverage(self): ....: return self._beverage ....: def fill(self, beverage = "water"): ....: if not self.is_clean(): ....: raise ValueError("Don't want to fill a dirty glass") ....: self._clean = False ....: self._content = self._size ....: self._beverage = beverage ....: def empty(self): ....: self._content = float(0.0) ....: def is_empty(self): ....: return self._content == 0.0 ....: def drink(self, amount): ....: if amount <= 0.0: ....: raise ValueError("amount must be positive") ....: elif amount > self._content: ....: raise ValueError("not enough beverage in the glass") ....: else: ....: self._content -= float(amount) ....: def is_clean(self): ....: return self._clean ....: def wash(self): ....: self._content = float(0.0) ....: self._beverage = None ....: self._clean = True
让我们检查一下一切是否按预期运行::
sage: G = Glass(10.0) sage: G An empty glass of size 10.0 sage: G.is_empty() True sage: G.drink(2) Traceback (most recent call last): ... ValueError: not enough beverage in the glass sage: G.fill("beer") sage: G A glass of size 10.0 cl containing 10.0 cl of beer sage: G.is_empty() False sage: G.is_clean() False sage: G.drink(5.0) sage: G A glass of size 10.0 cl containing 5.0 cl of beer sage: G.is_empty() False sage: G.is_clean() False sage: G.drink(5) sage: G An empty glass of size 10.0 sage: G.is_clean() False sage: G.fill("orange juice") Traceback (most recent call last): ... ValueError: Don't want to fill a dirty glass sage: G.wash() sage: G An empty glass of size 10.0 sage: G.fill("orange juice") sage: G A glass of size 10.0 cl containing 10.0 cl of orange juice
以下是第二个练习的解决方案:
sage: class AbstractDish(): ....: def __init__(self): ....: self._clean = True ....: def is_clean(self): ....: return self._clean ....: def state(self): ....: return "clean" if self.is_clean() else "dirty" ....: def __repr__(self): ....: return "An unspecified %s dish"%self.state() ....: def _make_dirty(self): ....: self._clean = False ....: def wash(self): ....: self._clean = True sage: class ContainerDish(AbstractDish): ....: def __init__(self, size): ....: assert size > 0 ....: self._size = float(size) ....: self._content = float(0) ....: super(ContainerDish, self).__init__() ....: def content(self): ....: return self._content ....: def empty(self): ....: self._content = float(0.0) ....: def is_empty(self): ....: return self._content == 0.0 ....: def wash(self): ....: self._content = float(0.0) ....: super(ContainerDish, self).wash() sage: class Glass(ContainerDish): ....: def __repr__(self): ....: if self._content == 0.0: ....: return "An empty glass of size %s"%(self._size) ....: else: ....: return "A glass of size %s cl containing %s cl of %s"%( ....: self._size, self._content, self._beverage) ....: def beverage(self): ....: return self._beverage ....: def fill(self, beverage = "water"): ....: if not self.is_clean(): ....: raise ValueError("Don't want to fill a dirty glass") ....: self._make_dirty() ....: self._content = self._size ....: self._beverage = beverage ....: def drink(self, amount): ....: if amount <= 0.0: ....: raise ValueError("amount must be positive") ....: elif amount > self._content: ....: raise ValueError("not enough beverage in the glass") ....: else: ....: self._content -= float(amount) ....: def wash(self): ....: self._beverage = None ....: super(Glass, self).wash()
让我们检查一下一切是否按预期运行::
sage: G = Glass(10.0) sage: G An empty glass of size 10.0 sage: G.is_empty() True sage: G.drink(2) Traceback (most recent call last): ... ValueError: not enough beverage in the glass sage: G.fill("beer") sage: G A glass of size 10.0 cl containing 10.0 cl of beer sage: G.is_empty() False sage: G.is_clean() False sage: G.drink(5.0) sage: G A glass of size 10.0 cl containing 5.0 cl of beer sage: G.is_empty() False sage: G.is_clean() False sage: G.drink(5) sage: G An empty glass of size 10.0 sage: G.is_clean() False sage: G.fill("orange juice") Traceback (most recent call last): ... ValueError: Don't want to fill a dirty glass sage: G.wash() sage: G An empty glass of size 10.0 sage: G.fill("orange juice") sage: G A glass of size 10.0 cl containing 10.0 cl of orange juice
这就是所有人了!