教程:Python和Sage中的对象和类¶
本教程介绍Python和Sage中的面向对象编程。它需要有关命令式/过程式编程(最常见的编程风格)的基本知识,即条件指令、循环、函数(请参阅Sage教程的“编程”部分),但不需要进一步了解对象和类。它被设计成一系列的正式介绍和练习。 练习的解决方案 最后给出。
前言:变量、名称和对象¶
作为一种面向对象的语言,Python的“变量”行为对于习惯于命令式语言(如C或Maple)的人来说可能会令人惊讶。原因是他们是 不是变量而是名称 .
下面的解释是 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”。我们可能无法活下去。(当对象不再有引用或标记时,它将从内存中删除。)
如果将一个名称指定给另一个名称,则只需将另一个名称标记附加到现有对象:
b = a
|
![]() |
名称“b”只是与“a”绑定到同一对象的第二个标记。
尽管我们通常在Python中也提到“变量”(因为它是通用术语),但实际上我们指的是“名称”或“标识符”。在Python中,“变量”是值的名称标签,而不是带标签的框。
警告
因此,当 两个标签 上的“a”和“b” 相同的对象 ,修改标记为“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
一 部分内存 它包含了模拟真实世界所需的信息。
- class
定义 数据结构 用于存储作为类实例的对象及其 行为 .
让我们从一些例子开始:我们考虑向量空间 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'>
因此,这不是很有信息。我们稍后再谈。与对象相关联的数据存储在 属性 . 它们是通过语法访问的 obj.attribute_name
. 对于组合自由模的一个元素,main属性被称为 _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__
{'__custom_name': 'bla', 'value': 42}
很多Sage对象不是Python对象,而是编译过的Cython对象。Python将它们视为内置对象,而您无权访问它们的某些内部数据结构。例如,基类 Element
存储的父级 el
作为Cython属性 _parent
但它不会出现在 __dict__
我们不能从Python访问它。
一些Cython类的例子(技术上, extension types )在Sage中包含整数和置换群元素。这些没有 __dict__
完全::
sage: e = Integer(9)
sage: type(e)
<type '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)
<type '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__'
注解
每个对象对应于一部分称为 身份 在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
总结¶
要定义某个对象,首先必须编写一个 类 . 类将定义对象的方法和属性。
- method
与对象相关联的一种特殊函数,用于获取有关对象的信息或对其进行操作。
- attribute
变量,其中存储有关对象的信息。
例如:餐馆里的一杯饮料¶
让我们写一个关于餐馆里的眼镜的小课:
sage: class Glass(object):
....: 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__
用于初始化对象:它由 构造函数 调用时执行的类Glass(10)
.方法
__repr__
返回用于打印对象的字符串,例如,在本例中,在计算myGlass
.
注解
私有属性
大多数情况下,为了保证数据结构的一致性,用户不需要直接更改对象的某些属性。这些属性称为 私有的 . 由于Python中没有确保隐私的机制,约定如下:私有属性的名称以下划线开头。
因此,属性访问只能通过方法进行。读取或写入私有属性的方法称为访问器。
只供内部使用的方法也以下划线作为前缀。
练习¶
添加方法
is_empty
如果杯子是空的,则返回true。定义方法
drink
带参数amount
它能让人部分地喝下杯子里的水。如果一个人要求喝比杯子里更多的水或负量的水,就提出一个错误。允许杯子装满葡萄酒、啤酒或其他饮料。方法
fill
应接受参数beverage
. 饮料存储在属性中_beverage
. 更新方法__repr__
因此。添加属性
_clean
和方法is_clean
和wash
. 在创造一个玻璃是干净的,一灌满它就变脏了,它必须经过清洗才能再次变得干净。测试一切。
确保一切都经过测试。
再测试一遍。
遗传¶
问题:对象 不同的 类可以共享 普通行为 .
例如,如果一个人想要处理不同的盘子(叉子,勺子,…),那么就有常见的行为(变脏和被洗)。因此,不同种类的菜肴所对应的不同类别应该是相同的 clean
, is_clean
和 wash
方法。但是复制和粘贴代码对维护非常不利:错误是被复制的,要更改任何内容,必须记住所有副本的位置。因此需要一种机制,允许程序员对常见行为进行分解。它被称为 遗传 或 sub-classing :编写一个基类,该基类将常见行为分解,然后重用该类中的方法。
我们首先编写一个小类“AbstractDish”,它实现“干净脏洗”行为:
sage: class AbstractDish(object):
....: 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
总结¶
任何类都可以重用另一个类的行为。有人说 继承 从超类或者说它 派生 从它开始。
子类的任何实例也是其超类的实例:
sage: type(s) <class '__main__.Spoon'> sage: isinstance(s, Spoon) True sage: isinstance(s, AbstractDish) True
如果子类重新定义了一个方法,那么它将替换前一个方法。有人说 超载 方法。但是可以显式地调用隐藏超类方法。
sage: s.__repr__() 'A clean spoon' sage: Spoon.__repr__(s) 'A clean spoon' sage: AbstractDish.__repr__(s) 'An unspecified clean dish'
注解
高级超类方法调用
有时人们想调用重载方法而不知道它是在哪个类中定义的。为此,请使用 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(object): ....: 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(object): ....: 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
所有的人!