cs61a 课时笔记 对象的抽象

cs61a对象的抽象学习笔记


对象抽象中的一个核心概念是泛型函数,它是一个可以接受多种不同类型的值的函数。我们将考虑实现泛型函数的三种不同技术:共享接口、类型调度和类型强制。Python对象系统支持创建泛型函数的特性。

Special Methods

只要对象被构造, init 方法就会被调用;
只要打印,str 方法就会被调用;
只要涉及显示与交互会话,repr 方法就会被调用;

True and false value

所有对象的布尔逻辑应该都是True,特殊方法 bool 方法被定义返回True。用户自定义的类从布尔逻辑来说应该是True,但要实现这个, bool 方法必须被重载。比如,我们认为银行账户的余额为0是False,需要给这个类添加一个 __bool__方法以实现这个行为。

>>> Account.__bool__ = lambda self: self.balance != 0

还可以直接调用bool构造器查看其逻辑值:

>>> bool(Account('Jack'))
False
>>> if not Account('Jack'):
        print('Jack has nothing')
Jack has nothing

Sequence operations

__len__方法

常使用len来求一个序列的长度,如:

>>> len('Go Bears!')
9

这是因为所有的内建序列类型都使用了__len__方法。上面的操作等价于下面的操作:

>>> 'Go Bears!'.__len__()
9

没有提供__bool__方法是,序列的布尔逻辑值由长度决定。长度为0是为false,不为0时为true。

>>> bool('')
False
>>> bool([])
False
>>> bool('Go Bears!')
True

__getitem__方法

这个方法等价于一个元素选择器,也是可以被直接调用的。如下:

>>> 'Go Bears!'[3]
'B'
>>> 'Go Bears!'.__getitem__(3)
'B'

Callable objects

在Python中,函数是一级对象,因此它们可以作为数据传递,并具有类似于任何其他对象的属性。Python还允许我们通过包含一个call方法来定义类似于“调用”函数的对象。使用此方法,我们可以定义一个行为类似于高阶函数的类。
如下例子,这是一个高阶函数:

>>> def make_adder(n):
        def adder(k):
            return n + k
        return adder
>>> add_three = make_adder(3)
>>> add_three(4)
7

可以创建一个类,定义一个__call__方法达到一样的效果:

>>> class Adder(object):
        def __init__(self, n):
            self.n = n
        def __call__(self, k):
            return self.n + k
>>> add_three_obj = Adder(3)
>>> add_three_obj(4)
7

data和function的分界还可以变得更加模糊。

Arithmetic

特殊方法还可以定义应用于用户定义对象的内置运算符的行为。为了提供这种通用性,Python遵循特定的协议来应用每个操作符。例如,要计算包含+运算符的表达式,Python会检查表达式的左操作数和右操作数上是否都有特殊方法。首先,Python在左操作数的值上检查add方法,然后在右操作数的值上检查radd方法。如果找到其中一个,则调用该方法,并将另一个操作数的值作为其参数。

Multiple Representations

实现一个复数类型,并实现复数的加法和乘法。
首先定义一个基类,然后使用通过add和mul方法抽象出该类的实例是怎么相加和相乘的。这个类没有__init__方法,它被设计为不能直接实例化,而是作为特殊数值类型,如复数等的超类。

>>> class Number:
        def __add__(self, other):
            return self.add(other)
        def __mul__(self, other):
            return self.mul(other)

复数有两种表达方式:(1)使用实部和虚部表示;(2)使用模和角度表示。
复数的加法结果使用方式(1)的表达更易求得复数实部相加,虚部相加;复数的乘法使用方式(2)的表达更易求得,将一个复数的长度乘以另一个复数的长度,然后将其旋转穿过另一个复数的角度得到的向量。
定义一个继承自Number的类Complex 类表示复数,并描述复数的运算。

>>> class Complex(Number):
        def add(self, other):
            return ComplexRI(self.real + other.real, self.imag + other.imag)
        def mul(self, other):
            magnitude = self.magnitude * other.magnitude
            return ComplexMA(magnitude, self.angle + other.angle)

上面的代码中假定存在两个类:通过实部和虚部对构造虚数ComplexRI 类;通过长度和角度构造虚数的ComplexMA 类。

两个或多个属性值之间保持固定关系的要求是一个新问题。一种解决方案是只为一个表示存储属性值,并在需要时计算另一个表示。
Python有一个从零参数函数动态计算属性的简单特性。@property修饰器允许在不使用调用表达式语法(表达式后面的括号)的情况下调用函数。ComplexRI类存储real和imag属性,并根据需要计算大小和角度。

>>> from math import atan2
>>> class ComplexRI(Complex):
        def __init__(self, real, imag):
            self.real = real
            self.imag = imag
        @property
        def magnitude(self):
            return (self.real ** 2 + self.imag ** 2) ** 0.5
        @property
        def angle(self):
            return atan2(self.imag, self.real)
        def __repr__(self):
            return 'ComplexRI({0:g}, {1:g})'.format(self.real, self.imag)

>>> ri = ComplexRI(5, 12)
>>> ri.real
5
>>> ri.magnitude
13.0
>>> ri.real = 9
>>> ri.real
9
>>> ri.magnitude
15.0

同理,通过大小和角度定义ComplexMA类,也能实现相互转化:

>>> from math import sin, cos, pi
>>> class ComplexMA(Complex):
        def __init__(self, magnitude, angle):
            self.magnitude = magnitude
            self.angle = angle
        @property
        def real(self):
            return self.magnitude * cos(self.angle)
        @property
        def imag(self):
            return self.magnitude * sin(self.angle)
        def __repr__(self):
            return 'ComplexMA({0:g}, {1:g} * pi)'.format(self.magnitude, self.angle/pi)
            
>>> ma = ComplexMA(2, pi/2)
>>> ma.imag
2.0
>>> ma.angle = pi
>>> ma.real
-2.0

最终能够实现两个虚数的加法和乘法:

>>> from math import pi
>>> ComplexRI(1, 2) + ComplexMA(2, pi/2)
ComplexRI(1, 4)
>>> ComplexRI(0, 1) * ComplexRI(0, 1)
ComplexMA(1, 1 * pi)

编码多个表示的接口方法具有吸引人的特性。每个表示的类可以单独开发;它们只能在共享属性的名称以及这些属性的任何行为条件上达成一致。如果另一个程序员想在同一个程序中添加第三个复数表示,他们只需要创建另一个具有相同属性的类。
使用数据抽象,我们能够在不改变程序含义的情况下改变数据类型的实现。通过接口和消息传递,我们可以在同一个程序中有多个不同的表示。在这两种情况下,一组名称和相应的行为条件定义抽象使得类的定义变得灵活。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章