Python第九章--特殊方法、屬性、迭代器
一、構造方法
1.構造方法
(1.構造方法類似於“第六章函式與模組”中使用過的名為init的初始化方法,與普通方法不同的是,當物件被建立後,會立即呼叫構造方法
例如,原來新建一個類物件需要做如下工作 >>> class MyNewClass(): def init(self): self.var = 30 >>> f = MyNewClass() >>> f.init() >>> f.var 30 現在可以通過建立一個構造方法,即把init方法的名字改為__init__,這樣前面構造一個物件時工作就會簡化>>> class MyNewClass(): def __init__(self): self.var = 30 >>> f = MyNewClass() >>> f.var 30
(2.構造方法中的引數傳遞
顯然可以在構造方法中定義其他引數,比如下面的例子中我們設定了另外一個關鍵字引數var >>> class MyNewClass(): def __init__(self, var = 'Hello, world!'): self.var = var 如果使用以下方法新建一個MyNewClass物件,>>> f = MyNewClass('Good morning!') >>> f.var 'Good morning! '
2.重寫一般方法
(1.我們知道每個類可能擁有一個或多個超類,並從超類那裡繼承行為方式;如果一個方法在B類的例項中被呼叫,但是B的定義中沒有該方法,那麼就會去B的超類A中去找。
>>> class A: def hello(self): print('Hello, I am A.') >>> class B(A): pass >>> a = A()>>> b = B() >>> a.hello() Hello, I am A. >>> b.hello() Hello, I am A. 由於B沒有自己的hello方法,所以在其例項中呼叫hello方法時實際上是其超類A的hello方法 在子類中增加功能的最基本方法就是增加方法,因此B可以重寫這個名為hello的方法 >>> class B(A): def hello(self): print('Hello, I am B.') >>> b = B() >>> b.hello() Hello, I am B.
3.重寫構造方法
(1.處理構造方法比重寫普通方法時,更可能遇到特別的問題:一個類的構造方法被重寫,那麼就需要呼叫其超類的構造方法,否則物件可能不會被正確地初始化
假設定義一個Bird類 class Bird(): def __init__(self): self.hungry = True def eat(self): if self.hungry: print('Aaaah...') self.hungry = False else: print('No, thanks.') Bird類定義了所有的鳥都具有的一些基本能力:吃(eat) >>> b = Bird() >>> b.eat() Aaaah... >>> b.eat() No, thanks. 在上面例子中可以看到,鳥吃過之後就不會再覺得飢餓 現在為類Bird新增一個子類SongBird class SongBird(Bird): def __init__(self): self.sound = 'Squawk!' def sing(self): print(self.sound) 因為SongBird是Bird的子類,所以它繼承了Bird類的eat方法 但如果呼叫該方法,你會發現 >>> songb = SongBird() >>> songb.eat() Traceback (most recent call last): File "<pyshell#56>", line 1, in <module> songb.eat() File "C:/python/bird.py", line 5, in eat if self.hungry: AttributeError: 'SongBird' object has no attribute 'hungry‘ 異常顯示SongBird類物件沒有hungry這個屬性,這是因為在其構造方法中沒有任何關於hungry初始化的程式碼
方案一: 為了解決前面的問題,我們可以在SongBird類的構造方法中呼叫其超類的構造方法,即 class SongBird(Bird): def __init__(self): Bird.__init__(self) # 增加了一行其超類的構造方法的呼叫語句 self.sound = 'Squawk!‘ …… >>> b = SongBird() >>> b.eat() Aaaah... >>> b.eat() No, thanks. 方案二: 另外一種方法是在SongBird類的構造方法中呼叫super函式,即 class SongBird(Bird): def __init__(self): super(SongBird, self).__init__() # 增加了super函式的呼叫 self.sound = 'Squawk!‘ …… >>> b = SongBird() >>> b.eat() Aaaah... >>> b.eat() No, thanks.
二.成員訪問
1.協議(規則):規則(或協議protocol)是用來描述管理某種形式的行為準則,說明了應該實現何種方法和這些方法應該做什麼
2.基本的序列和對映規則
(1.序列和對映均是物件的集合
為了實現序列和對映的基本行為(規則),其物件必須實現2個(對不可變物件而言)或4個方法(對可變物件而言)
(2.
__len__(self):返回序列中元素的個數或者對映中鍵/值對的數量
__getitem__(self, key):返回序列中與鍵對應的值,如果是序列,鍵的取值為0~n-1的整數,其中n是序列的長度,此外序列中鍵也可以是分片物件;當鍵是負數時則從末尾開始計數
__setitem__(self, key, value):儲存和鍵key相關的值value,該值隨後可以通__getitem__來獲取
__delitem__(self, key):在序列中刪除與鍵key對應的元素,在對映中刪除鍵key以及對應的值
建立一個無窮序列 def checkIndex(key): #檢查鍵是否是可接受的索引,如果鍵不是整數,會引發TypeError異常;如果是負數,則會引發IndexError if not isinstance(key, int): # 檢驗key是否是整數物件的例項 raise TypeError if key < 0: raise IndexError class ArithmeticSequence: def __init__(self, start = 0, step = 1): '''序列初始化''' self.start = start # 預設的序列初始值為start=0 self.step = step # 預設情況下每個元素之間相差1 self.changed = {} # 存放所有索引/值對的字典 def __getitem__(self, key): '''從序列中獲取一個元素''' checkIndex(key) try: return self.changed[key] except KeyError: # 若鍵key不存在,則新生成該鍵對應的值 return self.start + key * self.step def __setitem__(self, key, value): '''設定與key對應的值''' checkIndex(key) self.changed[key] = value # 設定鍵key/值value對 下面是應用例項: >>> acs = ArithmeticSequence() # 上面建立了一個序列,從0開始,元素之間間隔為1 >>> acs.__getitem__(5) # 獲取序列中關鍵字為5的元素 5 # 返回0 + 5 * 1 >>> acs[5] # 序列中利用索引獲取元素的方法 5 >>> acs = ArithmeticSequence(3, 2) # 上面建立了一個序列,從3開始,元素之間間隔為2 >>> acs.__getitem__(5) 13 # 返回 3 + 5 * 2 >>> acs[5] 13 注意: 在函式checkIndex中使用了內建函式isinstance,用於測試索引key是否是整型物件的例項 注意這個類ArithmeticSequence中沒有定義__delitem__方法,這是因為作者不希望元素被刪除,如果確實有刪除的必要,則可以自行定義;另外也沒有__len__方法 你可以自行嘗試使用一個非法型別的索引,會引發TypeError異常;若是負數索引,則會引發IndexError異常。這些異常均是程式設計師設定由raise語句主動引發的異常
3.定義列表、字典和字串的子類
(1.Python支援對內建型別進行子類化,假如需要實現一個和內建列表行為相似的序列,可以子類化list來實現
class CounterList(list): def __init__(self, *args): super(CounterList, self).__init__(*args) self.counter = 0 def __getitem__(self, index): self.counter +=1 return super(CounterList, self).__getitem__(index) 容易看出CounterList類沒有重寫任何的方法,由於是list類的子類,可以直接使用list的各種方法,包括append、extend等等 CounterList類與list類最大的不同在於,前者有一個例項變數self.counter,每次訪問該列表中的元素時,這個變數都會自增 >>> cl = CounterList(range(8)) >>> cl.reverse() # 直接呼叫父類的reverse方法 >>> cl [7, 6, 5, 4, 3, 2, 1, 0] >>> cl.append(8) # 直接呼叫父類的append方法 >>> cl [7, 6, 5, 4, 3, 2, 1, 0, 8] >>> cl.counter # 尚未訪問列表中的元素 0 >>> cl [7, 6, 5, 4, 3, 2, 1, 0, 8] >>> cl[2:4] = [] >>> cl [7, 6, 3, 2, 1, 0, 8] >>> cl[2] + cl[5] # 訪問列表元素會呼叫__getitem__方法 3 >>> cl.counter # 上面訪問兩次:cl[2]和cl[5] 2
三.屬性
(1.訪問器方法
下面定義了一個Rectangle類 class Rectangle: def __init__(self): self.width = 0 self.height = 0 def setSize(self, size): # 訪問器方法 self.width, self.height = size def getSize(self): # 訪問器方法 return self.width, self.height >>> r = Rectangle() >>> r.width = 4 >>> r.height = 5 >>> r.getSize() (4, 5) >>> r.setSize((6, 7)) >>> r.getSize() (6, 7) 可以認為Rectangle類中有一個特性名為size,是由width和height組成的元組,這裡的setSize和getSize方法均被稱為訪問器方法,而通過訪問器方法定義的特性(size)被稱為屬性
(2.利用property函式建立屬性
我們將前面Rectangle類的定義稍微改寫一下 class Rectangle: def __init__(self): self.width = 0 self.height = 0 def setSize(self, size): self.width, self.height = size def getSize(self): return self.width, self.height size = property(getSize, setSize) # 增加了一行!!! getsize:取屬性值方法 setsize;設定屬性值方法 >>> r = Rectangle() >>> r.width = 4 >>> r.height = 5 >>> r.size (4, 5) >>> r.size = 10, 20 >>> r.height 20 此時的size就像普通的屬性一樣,其特性仍然通過getSize和setSize方法確定
四.迭代器
1.迭代的意思是將一些事重複做若干次,和迴圈類似
2.我們曾經對序列和字典等物件使用for迴圈進行迭代,其實對其他物件也是可以的,只要該物件實現了__iter__方法,該方法是迭代器規則的基礎
3.
(1.__iter__方法會返回一個迭代器(iterator),所謂迭代器(iterator)就是具有__next__方法的物件,呼叫該方法時返回物件例項中的下一個值
(2.為什麼不用列表來一次性獲取所有的值
原因:如果有很多值,那麼列表就會佔用太多記憶體,而使用__iter__方法的好處是一個接一個地計算值
4.請記住:一個實現了__iter__方法的物件是可迭代的,
一個實現了__next__方法的物件是迭代器!
下面定義了一個斐波那契數列, class Fibs: def __init__(self): self.a = 0 self.b = 1 def __next__(self): self.a, self.b = self.b, self.a + self.b return self.a def __iter__(self): return self 這裡的Fibs物件是可迭代的,同時也是一個迭代器 >>> f = Fibs() >>> f.__next__() #f是可迭代物件,可以利用該方法獲取下一個元素 1 >>> f.__next__() 1 >>> f.__next__() 2 >>> for i in f.__iter__(): #__iter__返回迭代器,因此可以用於迴圈。此 # 時取出的下一個元素是當前(!)的下一元素,並不總是第一個元素 if i > 20: break else: print(i, end = ' ') 3 5 8 13
(1.內建函式iter可以從可迭代的物件中獲得迭代器,然後使用__next__方法來獲取迭代器中的下一個元素
>>> it = iter([1, 2, 3])
>>> it.__next__()
1
>>> it.__next__()
2
(2.當我們使用for語句的時候,for語句就會自動地通過__iter__()方法來獲得迭代器物件,並且通過__next__()方法來獲取下一個元素
class MyRange(object): def __init__(self, n): self.idx = 0 self.n = n def __iter__(self): return self def __next__(self): if self.idx < self.n: val = self.idx self.idx += 1 return val else: raise StopIteration() 執行如下程式碼: myRange =MyRange(3) for i in myRange: print(i) 執行如下程式碼: myRange =MyRange(3) print(myRange is iter(myRange)) print([i for i in myRange]) print([i for i in myRange])
class Zrange: def __init__(self, n): self.n = n def __iter__(self): return ZrangeIterator(self.n) class ZrangeIterator: def __init__(self, n): self.i = 0 self.n = n def __iter__(self): return self def __next__(self): if self.i < self.n: i = self.i self.i += 1 return i else: raise StopIteration() 執行如下程式碼: zrange = Zrange(3) print(zrange is iter(zrange)) print([i for i in zrange]) print([i for i in zrange])