【循序漸進學Python】8.面向對象的核心——類型(下)


1 構造和初始化對象

__init__方法是Python內建眾多魔法方法(什么是魔法方法?)中最常見的一個,通過這個方法我們可以定義一個對象的初始操作。當構造函數被調用的時候的任何參數都會傳遞給__init__方法,然后該方法根據這些參數進行對象的初始化工作:

1 # -- coding: utf-8 --
2 class Employee(object):
3     def __init__(self,name):
4         self.name = name
5 
6 e = Employee('Sunshine')
7 
8 print e.name # Sunshine

 與__init__方法對應的是__del__析構方法,在對象被垃圾回收前調用,除了在進行套接字、文件IO這些非托管資源操作外,一般情況下很少會用到它。

 

1.1 正確的初始化子類型

重寫是繼承機制中一個重要的內容,對於構造方法尤其如此。大多數類型的子類既要初始化自己的部分,也要調用基類的構造方法,因為保證了對象被正確的初始化,如下所示:

 1 # -- coding: utf-8 --
 2 class Bird(object):
 3     def __init__(self):
 4         self.hungry = True
 5 
 6     def eat(self):
 7         if self.hungry:
 8             print 'Aaaah...'
 9             self.hungry = False
10         else:
11             print 'No. thanks'
12 
13 class SongBird(Bird):
14     def __init__(self):
15         self.sound = 'Squawk!'
16 
17     def sing(self):
18         print self.sound
19 
20 # AttributeError: 'SongBird' object has no attribute 'hungry'
21 sb = SongBird()
22 sb.eat()

 

SongBird這個子類在調用繼承於父類Birdeat()方法時由於其自身的構造函數沒有正確的初始化基類的成員hungry所有就會拋出AttributeError的異常。正確初始化父類成員的方法有兩個:

1.直接調用基類構造方法。這樣修改SongBird類型的定義即可:

1 class SongBird(Bird):
2     def __init__(self):
3         Bird.__init__(self) # 調用基類構造方法
4         self.sound = 'Squawk!'
5 
6     def sing(self):
7         print self.sound

 

2.調用super函數,可以這樣修改SongBird類型的定義:

1 class SongBird(Bird):
2     def __init__(self):
3         super(SongBird,self).__init__() # 使用super函數
4         self.sound = 'Squawk!'
5 
6     def sing(self):
7         print self.sound

 

一般來講,使用第一種方式:調用未綁定的基類構造方法,是為了兼容舊的代碼,如果不需要兼容舊版本的代碼還是推薦使用super函數來初始化基類的成員。從上面的示例可能覺得二者差別不大,但是為什么還要出現super函數呢,因為super函數(其他它是一個類)主要是用來解決多重繼承問題的,因為這牽涉到順序查找(MRO)、重復調用等等一系列的復雜問題。為了保證我們代碼的一致性,我們應該同時只使用一種風格來編寫代碼。

 

2. 魔法方法

在Python中內建了很多"magic"方法這些方法和其他函數有些細微的不同:

  1. 從形式上來說名字都是以雙下划線(__)來開始和結束的(如:__new__)
  2. 大部分的魔法方法會在特定的情況下被Python自動調用。(比如__init__方法會在對象被創建后自動調用)

 

2.1 通過魔法方法定制類型

使用Python魔法方法最大的優勢在於可以創建出自己的擁有和內置類型同樣行為的類出來。例如我們可以通過重寫下面的魔法方法,來為自己的類型定制自己的比較規則:

  • __eq__(self,other) —— 定義了等於的行為(=)
  • __ne__(self,other) —— 定義了不等於的行為(!=)
  • __lt__(self,other) —— 定義了小於的行為(<)
  • __gt__(self,other) —— 定義了大於的行為(>)
  • __le__(self,other) —— 定義了小於等於的行為(<=)
  • __ge__(self,other) —— 定義了大於等於的行為(>=)

例如,我們可以重寫定義類型的==等行為,只需要重寫__eq__方法即可:

 1 # -- coding: utf-8 --
 2 class Employee(object):
 3     def __init__(self,name):
 4         self.name = name
 5 
 6     def __eq__(self,other):
 7         return self.name == other
 8 
 9 e = Employee('Sun')
10 print e == 'Sun' # True

 Python內置的魔法方法中除了這些用於比較的魔法方法之外,還有關於操作符、類型轉換以及序列相關的等等一系列的魔法方法。如果想了解更多,這里有一份很好的文檔(注意文檔中錯別字):Python魔法方法指南

 

3. 屬性

在Python中通過訪問器定義的特性被稱為屬性(property)。有兩種定義屬性的方式,首先我們來使用property函數來創建屬性:

 1 # --coding:utf-8--
 2 class Employee(object):
 3     def __init__(self):
 4         self.__name = None
 5 
 6     # set 訪問器
 7     def getName(self):
 8         return self.__name
 9 
10     # get 訪問器
11     def setName(self,value):
12         self.__name = value
13 
14     # 析構函數
15     def delName(self):
16         del self.__name
17 
18     name = property(getName,setName,delName,'set and get Name property')
19 
20 c = Employee()
21 c.name = "Bob"
22 print c.name # output: Bob

 

另一種方式就是Python 2.6 新增的語法,get/set/del函數都是使用的同一個名字:

 1 # --coding:utf-8--
 2 class Employee(object):
 3     def __init__(self):
 4         self.__name = None
 5 
 6     def name(self):
 7         return self.__name
 8 
 9     def name(self,value):
10         self.__name=value
11 
12     def name(self):
13         del self.__name
14 
15 c = Employee()
16 c.name = "jobs"
17 print c.name # jobs

 

3.1 控制屬性訪問

如果需要加強對屬性的控制,可以使用Python為我們提供一系列的魔法方法:

  • __getattribute__(self,name):當特性name被訪問時自動被調用(只能新式類中使用)
  • __getattr__(self,name):當特性name被訪問且對象沒有相應的特性時被自動調用
  • __setattr__(self,name):給特性name賦值時會被自動調用
  • __delattr__(self,name):當刪除特性name時被自動調用

我們知道每當屬性被賦值的時候,__setattr__()就會被調用,通過它和__dict__[]的組合,我們可以通過動態為一個類創建創建特性,如下:

 1 # -- coding: utf-8 --
 2 class Employee(object):
 3     def __init__(self,name):
 4         self.name = name
 5 
 6     # RuntimeError: maximum recursion depth exceeded
 7     # #每當屬性被賦值的時候,__setattr__()會被調用,這樣就造成了遞歸調用。
 8     # def __setattr__(self,name,value):
 9     #   self.name = value
10 
11     def __setattr__(self,name,value):
12         self.__dict__[name] = value
13 
14 e = Employee("sunshine")
15 print e.name
16 e.email = "sunshine@gmail.com"
17 print e.email

 

如果只能訪問特定特性的話其實也很簡單,只需要想下面 __setattr__中加個if判斷即可:

 1 # -- coding: utf-8 --
 2 class AccessCounter(object):
 3     '''一個包含計數器的控制權限的類每當值被改變時計數器會加一'''
 4 
 5     def __init__(self, val):
 6         super(AccessCounter, self).__setattr__('counter', 0)
 7         super(AccessCounter, self).__setattr__('value', val)
 8 
 9     def __setattr__(self, name, value):
10         if name == 'value':
11             # 通過調用基類的__setattr__方法可以防止遞歸
12             super(AccessCounter, self).__setattr__('counter', self.counter + 1)
13         # 如果不想讓其他屬性被訪問的話,那么可以拋出 AttributeError(name) 異常
14         super(AccessCounter, self).__setattr__(name, value)
15 
16     def __delattr__(self, name):
17         if name == 'value':
18             super(AccessCounter, self).__setattr__('counter', self.counter + 1)
19         super(AccessCounter, self).__delattr__(name)
20 
21 c = AccessCounter(10)
22 c.value = 1
23 c.value = 2
24 c.value = 3
25 c.value = 4
26 c.value = 5
27 print c.counter # value特性的值改變了5次

 

4. 定義靜態方法和類成員方法

靜態方法和類成員方法分別在創建時被裝入Staficmethod類型和Classmethod類型的對象中。靜態方法定義沒有任何參數,能夠被類本身直接調用。類方法在定義時需要名為cls的參數(類似於self參數),且能夠被類本身調用。Python 2.4 為我們提供了名為:裝飾器(decorators)的新語法,可以很簡單的創建這兩種方法:

 1 class MyClass(object):
 2 
 3     @staticmethod
 4     def smeth():
 5         print 'This is a static method'
 6 
 7     @classmethod
 8     def cmeth(cls):
 9         print 'This is a class method of',cls
10 
11 MyClass.smeth() # This is a static method
12 MyClass.cmeth() # This is a class method of <class '__main__.MyClass'>
13 my = MyClass() 
14 my.cmeth() # This is a class method of <class '__main__.MyClass'>
15 my.smeth() # This is a static method

 

5. 協議

協議(protocol)在Python中的概念和C#中的接口比較類似,協議說明了應該實現何種方法和這些方法應該做什么。比如說,如果想要自定義一個序列,那么只需要遵守序列的協議即可。

 

5.1 迭代器

迭代器必須遵循迭代器協議,需要有 __iter__ (返回它本身) 和 next。__iter__方法是迭代器規則(iterator protocol)的基礎。__iter__返回一個具有next方法的迭代器(iterator)對象,調用next方法時,迭代器會返回它的下一個值,直到沒有值可返回時才會引發一個StopIteration異常。下面的示例使用迭代器來創建斐波那契數列,如下:

 1 class Fibs(object):
 2     def __init__(self):
 3         self.a = 0
 4         self.b = 1
 5 
 6     def next(self):
 7         self.a,self.b = self.b,self.a+self.b
 8         return self.a
 9 
10     def __iter__(self):
11         return self
12 
13 fibs = Fibs()
14 
15 # output: 1 1 2 3 5 8 13 21 34 55
16 for f in fibs:
17     if f < 60:
18         print f,
19     else:
20         break

 

我們可以借助內建函數iter從可迭代的對象中獲得迭代器:

1 it = iter([1,2,3])
2 print it.next() # 1
3 print it.next() # 2
4 print it.next() # 3

 

也可以將迭代器轉換為一個序列:

 1 # -- coding: utf-8 --
 2 class TestIterator(object):
 3     value = 0
 4     def next(self):
 5         self.value += 1
 6         if self.value >10:raise StopIteration
 7         return self.value
 8 
 9     def __iter__(self):
10         return self
11 
12 
13 ti = TestIterator()
14 
15 # 通過list構造方法顯示將迭代器轉換為列表
16 print list(ti)

 

6. 生成器

生成器是用一種普通的函數語法定義的迭代器。任何包含yield語句的函數都可以稱為生成器。當包含yield語句的函數被調用時,函數體中的代碼不會被執行,而是返回一個迭代器對象。每次請求一個值,就會執行生成器中的代碼,知道遇到一個yield或者return語句。如下:

 1 # -- coding: utf-8 --
 2 nested = [[1,2],[3,4],[5]]
 3 
 4 def flatten(nested):
 5     for sublist in nested:
 6         for element in sublist:
 7             yield element
 8 
 9 # <generator object flatten at 0x0000000001D90168>
10 print flatten(nested)
11 
12 # 調用時才真正執行
13 for num in flatten(nested):
14     print num, # 1 2 3 4 5

 

 

參考資料&進一步閱讀

Python 魔法方法指南

Python 描述符簡介

Python基礎教程(第二版)

深刻理解Python中的元類(metaclass)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM