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)

編碼多個表示的接口方法具有吸引人的特性。每個表示的類可以單獨開發;它們只能在共享屬性的名稱以及這些屬性的任何行爲條件上達成一致。如果另一個程序員想在同一個程序中添加第三個複數表示,他們只需要創建另一個具有相同屬性的類。
使用數據抽象,我們能夠在不改變程序含義的情況下改變數據類型的實現。通過接口和消息傳遞,我們可以在同一個程序中有多個不同的表示。在這兩種情況下,一組名稱和相應的行爲條件定義抽象使得類的定義變得靈活。

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