教程:Python和Sage中的对象和类

本教程介绍Python和Sage中的面向对象编程。它需要有关命令式/过程式编程(最常见的编程风格)的基本知识,即条件指令、循环、函数(请参阅Sage教程的“编程”部分),但不需要进一步了解对象和类。它被设计成一系列的正式介绍和练习。 练习的解决方案 最后给出。

前言:变量、名称和对象

作为一种面向对象的语言,Python的“变量”行为对于习惯于命令式语言(如C或Maple)的人来说可能会令人惊讶。原因是他们是 不是变量而是名称 .

下面的解释是 borrowed from David Goodger .

其他语言有“变量”

在许多其他语言中,赋值给变量会将值放入框中。

int a = 1;
thematic_tutorials/media/a1box.png

框“a”现在包含一个整数1。

为同一变量指定另一个值将替换框中的内容:

a = 2;
thematic_tutorials/media/a2box.png

现在框“a”包含一个整数2。

将一个变量分配给另一个变量会复制该值并将其放入新框中:

int b = a;
thematic_tutorials/media/b2box.png thematic_tutorials/media/a2box.png

“b”是第二个框,其中有一个整数2的副本。方框“a”有单独的副本。

Python有“名字”

在Python中,“名称”或“标识符”类似于附着到对象上的地块标记(或名称标记)。

a = 1
thematic_tutorials/media/a1tag.png

这里,一个整数1对象有一个标记“a”。

如果我们重新分配给“a”,我们只需将标记移动到另一个对象:

a = 2
thematic_tutorials/media/a2tag.png thematic_tutorials/media/1.png

现在名称“a”被附加到一个整数2对象上。

原来的integer 1对象不再有标记“a”。我们可能无法活下去。(当对象不再有引用或标记时,它将从内存中删除。)

如果将一个名称指定给另一个名称,则只需将另一个名称标记附加到现有对象:

b = a
thematic_tutorials/media/ab2tag.png

名称“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 范式依赖于以下两个基本规则:

  1. 任何需要由计算机操作的真实(或数学)世界都是由 对象 .

  2. 每个对象都是 实例 一些 .

在这一点上,这两个规则有点毫无意义,所以让我们给出一些或多或少精确的术语定义:


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

一些评论:

  1. 类的定义 Glass 定义了两个属性, _size_content . 它定义了四种方法, __init____repr__fillempty . (此类的任何实例也将具有从类继承的其他属性和方法 object . 见 Inheritance 下面)

  2. 方法 __init__ 用于初始化对象:它由 构造函数 调用时执行的类 Glass(10) .

  3. 方法 __repr__ 返回用于打印对象的字符串,例如,在本例中,在计算 myGlass .

注解

私有属性

  • 大多数情况下,为了保证数据结构的一致性,用户不需要直接更改对象的某些属性。这些属性称为 私有的 . 由于Python中没有确保隐私的机制,约定如下:私有属性的名称以下划线开头。

  • 因此,属性访问只能通过方法进行。读取或写入私有属性的方法称为访问器。

  • 只供内部使用的方法也以下划线作为前缀。

练习

  1. 添加方法 is_empty 如果杯子是空的,则返回true。

  2. 定义方法 drink 带参数 amount 它能让人部分地喝下杯子里的水。如果一个人要求喝比杯子里更多的水或负量的水,就提出一个错误。

  3. 允许杯子装满葡萄酒、啤酒或其他饮料。方法 fill 应接受参数 beverage . 饮料存储在属性中 _beverage . 更新方法 __repr__ 因此。

  4. 添加属性 _clean 和方法 is_cleanwash . 在创造一个玻璃是干净的,一灌满它就变脏了,它必须经过清洗才能再次变得干净。

  5. 测试一切。

  6. 确保一切都经过测试。

  7. 再测试一遍。

遗传

问题:对象 不同的 类可以共享 普通行为 .

例如,如果一个人想要处理不同的盘子(叉子,勺子,…),那么就有常见的行为(变脏和被洗)。因此,不同种类的菜肴所对应的不同类别应该是相同的 cleanis_cleanwash 方法。但是复制和粘贴代码对维护非常不利:错误是被复制的,要更改任何内容,必须记住所有副本的位置。因此需要一种机制,允许程序员对常见行为进行分解。它被称为 遗传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

总结

  1. 任何类都可以重用另一个类的行为。有人说 继承 从超类或者说它 派生 从它开始。

  2. 子类的任何实例也是其超类的实例:

    sage: type(s)
    <class '__main__.Spoon'>
    sage: isinstance(s, Spoon)
    True
    sage: isinstance(s, AbstractDish)
    True
    
  3. 如果子类重新定义了一个方法,那么它将替换前一个方法。有人说 超载 方法。但是可以显式地调用隐藏超类方法。

    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

练习

  1. 修改类 Glasses 所以它继承了 Dish .

  2. 写一节课 Plate 它的实例可以和类一起包含任何一顿饭 Fork . 尽可能避免代码重复(提示:您可以编写因子分解类 ContainerDish

  3. 测试一切。

关于课程的Sage细节

与Python相比,Sage有处理对象的特殊方法:

  • Sage中数学对象的任何类都应该继承自 SageObject 而不是来自 object . 大多数时候,它们实际上是从一个子类继承的,比如 ParentElement .

  • 印刷应该通过 _repr_ 而不是 __repr__ 允许重命名。

  • 一般来说,Sage特有的特殊方法通常被命名为 _meth_ 而不是 __meth__ . 例如,许多类实现 _hash_ 由使用和缓存 __hash__ . 同样地,一个群体中的元素通常是 _mul_ ,这样就不需要像在 __mul__ .

有关更多详细信息,请参阅Sage开发人员指南。

练习的解决方案

  1. 以下是第一个练习的解决方案:

    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
    
  2. 让我们检查一下是否一切正常:

    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
    
  3. 下面是第二个练习的解决方法:

    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()
    
  4. 让我们检查一下是否一切正常:

    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
    

所有的人!