1. 程式人生 > >python基礎——第9章 魔法方法、特性和迭代器

python基礎——第9章 魔法方法、特性和迭代器

目錄

9.2.1 建構函式: init(self)

注意 Python提供了魔法方法__del__,也稱作解構函式(destructor)。這個方法在物件被銷燬(作為垃圾被收集)前被呼叫,但鑑於你無法知道準確的呼叫時間,建議儘可能不要使用__del__。
重寫是繼承機制的一個重要方面,對建構函式來說尤其重要。建構函式用於初始化新建物件的狀態,而對大多數子類來說,除超類的初始化程式碼外,還需要有自己的初始化程式碼。雖然所有方法的重寫機制都相同,但與重寫普通方法相比,重寫建構函式時更有可能遇到一個特別的問題:重寫建構函式時,必須呼叫超類(繼承的類)的建構函式,否則可能無法正確地初始化物件。
呼叫其超類(Bird)的建構函式,以確保基本的初始化得以執行。為此,有兩種方法:呼叫未關聯的超類建構函式,以及使用函式super。

9.2.2 呼叫未關聯的超類建構函式

注意 在Python中,協議通常指的是規範行為的規則,有點類似於第7章提及的介面。協議指定應實現哪些方法以及這些方法應做什麼。在Python中,多型僅僅基於物件的行為(而不基於祖先,如屬於哪個類或其超類等),因此這個概念很重要:其他的語言可能要求物件屬於特定的類或實現了特定的介面,而Python通常只要求物件遵循特定的協議。因此,要成為序列,只需遵循序列協議即可。

9.3 元素訪問

9.3.1 基本的序列和對映協議

序列和對映基本上是元素(item)的集合,要實現它們的基本行為(協議),不可變物件需要實現2個方法,而可變物件需要實現4個。

  • len(self):這個方法應返回集合包含的項數,對序列來說為元素個數,對對映來說為鍵-值對數。如果__len__返回零(且沒有實現覆蓋這種行為的__nonzero__),物件在布林上下文中將被視為假(就像空的列表、元組、字串和字典一樣)。
  • getitem(self, key):這個方法應返回與指定鍵相關聯的值。對序列來說,鍵應該是0~n-1的整數(也可以是負數,這將在後面說明),其中n為序列的長度。對對映來說,鍵可以是任何型別。
  • setitem(self, key, value):這個方法應以與鍵相關聯的方式儲存值,以便以後能夠使用__getitem__來獲取。當然,僅當物件可變時才需要實現這個方法。
  • delitem(self, key):這個方法在對物件的組成部分使用__del__語句時被呼叫,應刪除與key相關聯的值。同樣,僅當物件可變(且允許其項被刪除)時,才需要實現這個方法。
    對於這些方法,還有一些額外的要求。
  • 對於序列,如果鍵為負整數,應從末尾往前數。換而言之,x[-n]應與x[len(x)-n]等效。
  • 如果鍵的型別不合適(如對序列使用字串鍵),可能引發TypeError異常。
  • 對於序列,如果索引的型別是正確的,但不在允許的範圍內,應引發IndexError異常。
    要了解更復雜的介面和使用的抽象基類(Sequence),請參閱有關模組collections的文件。
    下面來試一試,看看能否建立一個無窮序列。
def check_index(key): 
   """ 
   指定的鍵是否是可接受的索引? 
   鍵必須是非負整數,才是可接受的。如果不是整數,將引發TypeError異常;
   如果是負數,將引發Index Error異常(因為這個序列的長度是無窮的) 
   """ 
	if not isinstance(key, int): raise TypeError 
 	if key < 0: raise IndexError 
class ArithmeticSequence: 
	def __init__(self, start=0, step=1): 
 		""" 
 		初始化這個算術序列 
 		start   -序列中的第一個值 
		step    -兩個相鄰值的差 
 		changed -一個字典,包含使用者修改後的值 
 		""" 
		self.start = start       # 儲存起始值 
   		self.step = step      # 儲存步長值
   		self.changed = {}   # 沒有任何元素被修改 
   	def __getitem__(self, key): 
 		""" 
 		從算術序列中獲取一個元素 
 		""" 
 		check_index(key) 
 		try: return self.changed[key]     # 修改過? 
 		except KeyError:      # 如果沒有修改過, 
  			return self.start + key * self.step    # 就計算元素的值 
   	def __setitem__(self, key, value): 
 		""" 
 		修改算術序列中的元素 
 		""" 
 		check_index(key) 
 		self.changed[key] = value     # 儲存修改後的值 

這些程式碼實現的是一個算術序列,其中任何兩個相鄰數字的差都相同。第一個值是由構造函
數的引數start(預設為0)指定的,而相鄰值之間的差是由引數step(預設為1)指定的。你允
許使用者修改某些元素,這是通過將不符合規則的值儲存在字典changed中實現的。如果元素未被
修改,就使用公式self.start + key * self.step來計算它的值。

9.3.2 從 list、dict 和 str 派生

在標準庫中,模組collections提供了抽象和具體的基類,但你也可以繼承內建型別。因此,如果要實現一種
行為類似於內建列表的序列型別,可直接繼承list。

9.5 特性

9.5.1 函式property

9.7

  • 包含yield語句的函式都被稱為生成器。

,生成器的行為與普通函式截然不同。差別在於,生成器不是使用return返回一個值,而是可以生成多個值,每次一個。每次使用yield生成一個值後,函式都將凍結,即在此停止執行,等待被重新喚醒。被重新喚醒後,函式將從停止的地方開始繼續執行。
在Python 2.4中,引入了一個類似於列表推導(參見第5章)的概念:生成器推導(也叫生成器表示式)。其工作原理與列表推導相同,但不是建立一個列表(即不立即執行迴圈),而是返回一個生成器,讓你能夠逐步執行計算。

>>> g = ((i + 2) ** 2 for i in range(2, 27)) 
>>> next(g) 
16 

如你所見,不同於列表推導,這裡使用的是圓括號。在像這樣的簡單情形下,還不如使用列表推導;但如果要包裝可迭代物件(可能生成大量的值),使用列表推導將立即例項化一個列表,從而喪失迭代的優勢。
另一個好處是,直接在一對既有的圓括號內(如在函式呼叫中)使用生成器推導時,無需再新增一對圓括號。換而言之,可編寫下面這樣非常漂亮的程式碼:
sum(i ** 2 for i in range(10))