Python中一切都是對象。類提供了創建新類型對象的機制。這篇教程中,我們不談類和面向對象的基本知識,而專注在更好地理解Python面向對象編程上。假設我們使用新風格的python類,它們繼承自object父類。
定義類
class 語句可以定義一系列的屬性、變量、方法,他們被該類的實例對象所共享。下面給出一個簡單類定義:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class Account( object ): num_accounts = 0 def __init__( self , name, balance): self .name = name self .balance = balance Account.num_accounts + = 1 def del_account( self ): Account.num_accounts - = 1 def deposit( self , amt): self .balance = self .balance + amt def withdraw( self , amt): self .balance = self .balance - amt def inquiry( self ): return self .balance |
類定義引入了以下新對象:
類對象
實例對象
方法對象
類對象
程序執行過程中遇到類定義時,就會創建新的命名空間,命名空間包含所有類變量和方法定義的名稱綁定。注意該命名空間并沒有創建類方法可以使用的新局部作用域,因此在方法中訪問變量需要全限定名稱。上一節的Account類演示了該特性;嘗試訪問num_of_accounts變量的方法需要使用全限定名稱Account.num_of_accounts,否則,如果沒有在__init__方法中使用全限定名稱,會引發如下錯誤:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class Account( object ): num_accounts = 0 def __init__( self , name, balance): self .name = name self .balance = balance num_accounts + = 1 def del_account( self ): Account.num_accounts - = 1 def deposit( self , amt): self .balance = self .balance + amt def withdraw( self , amt): self .balance = self .balance - amt def inquiry( self ): return self .balance >>> acct = Account( 'obi' , 10 ) Traceback (most recent call last): File "python" , line 1 , in <module> File "python" , line 9 , in __init__ UnboundLocalError: local variable 'num_accounts' referenced before assignment |
類定義執行的最后,會創建一個類對象。在進入類定義之前有效的那個作用域現在被恢復了,同時類對象被綁定到類定義頭的類名上。
先偏離下話題,你可能會問如果創建的類是對象,那么類對象的類是什么呢?。與一切都是對象的python哲學一致,類對象確實有個類,即python新風格類中的type類。
1
2
|
>>> type (Account) < class 'type' > |
讓你更迷惑一點,Account類型的類型是type。type類是個元類,用于創建其他類,我們稍后教程中再介紹。
類對象支持屬性引用和實例化。屬性通過標準的點語法引用,即對象后跟句點,然后是屬性名:obj.name。有效的屬性名是類對象創建后類命名空間中出現的所有變量和方法名。例如:
1
2
3
4
|
>>> Account.num_accounts >>> 0 >>> Account.deposit >>> <unbound method Account.deposit> |
類實例化使用函數表示法。實例化會像普通函數一樣無參數調用類對象,如下文中的Account類:
1
|
>>> Account() |
類對象實例化之后,會返回實例對象,如果類中定義了__init__方法,就會調用,實例對象作為第一個參數傳遞過去。這個方法會進行用戶自定義的初始化過程,比如實例變量的初始化。Account類為例,賬戶name和balance會被設置,實例對象的數目增加1。
實例對象
如果類對象是餅干切割刀,餅干就是實例化類對象的結果。實例對象上的全部有效操作為對屬性、數據和方法對象的引用。
方法對象
方法對象和函數對象類似。如果x是Account類的實例,x.deposit就是方法對象的例子。方法定義中有個附加參數,self。self指向類實例。為什么我們需要把實例作為參數傳遞給方法?方法調用能最好地說明:
1
2
3
|
>>> x = Account() >>> x.inquiry() 10 |
實例方法調用時發生了什么?你應該注意到x.inquiry()調用時沒有參數,雖然方法定義包含self參數。那么這個參數到底發生了什么?
特殊之處在于方法所作用的對象被作為函數的第一個參數傳遞過去。在我們的例子中,對x.inquiry()的調用等價于Account.f(x)。一般,調用n參數的方法等同于將方法的作用對象插入到第一個參數位置。
python教程上講:
當引用的實例屬性不是數據屬性時,就會搜索類。如果名稱表示一個合法的函數對象,實例對象和函數對象將會被打包到一個抽象對象,即方法對象中。包含參數列表的方法對象被調用時,將會根據實例對象和參數列表創建一個新的參數列表,然后函數對象將會使用新的參數列表被調用。
這適用于所有的實例方法對象,包括__init__方法。self參數其實不是一個關鍵字,任何有效的參數名都可以使用,如下Account類定義所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
class Account( object ): num_accounts = 0 def __init__(obj, name, balance): obj.name = name obj.balance = balance Account.num_accounts + = 1 def del_account(obj): Account.num_accounts - = 1 def deposit(obj, amt): obj.balance = obj.balance + amt def withdraw(obj, amt): obj.balance = obj.balance - amt def inquiry(obj): return obj.balance >>> Account.num_accounts >>> 0 >>> x = Account( 'obi' , 0 ) >>> x.deposit( 10 ) >>> Account.inquiry(x) >>> 10 |
靜態和類方法
類中定義的方法默認由實例調用。但是,我們也可以通過對應的@staticmethod和@classmethod裝飾器來定義靜態或類方法。
靜態方法
靜態方式是類命名空間中的普通函數。引用類的靜態方法返回的是函數類型,而不是非綁定方法類型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
class Account( object ): num_accounts = 0 def __init__( self , name, balance): self .name = name self .balance = balance Account.num_accounts + = 1 def del_account( self ): Account.num_accounts - = 1 def deposit( self , amt): self .balance = self .balance + amt def withdraw( self , amt): self .balance = self .balance - amt def inquiry( self ): return "Name={}, balance={}" . format ( self .name, self .balance) @staticmethod def type (): return "Current Account" >>> Account.deposit <unbound method Account.deposit> >>> Account. type <function type at 0x106893668 > |
使用@staticmethod裝飾器來定義靜態方法,這些方法不需要self參數。靜態方法可以更好地組織與類相關的代碼,也可以在子類中被重寫。
類方法
類方法由類自身來調用,而不是實例。類方法使用@classmethod裝飾器定義,作為第一個參數被傳遞給方法的是類而不是實例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import json class Account( object ): num_accounts = 0 def __init__( self , name, balance): self .name = name self .balance = balance Account.num_accounts + = 1 def del_account( self ): Account.num_accounts - = 1 def deposit( self , amt): self .balance = self .balance + amt def withdraw( self , amt): self .balance = self .balance - amt def inquiry( self ): return "Name={}, balance={}" . format ( self .name, self .balance) @classmethod def from_json( cls , params_json): params = json.loads(params_json) return cls (params.get( "name" ), params.get( "balance" )) @staticmethod def type (): return "Current Account" |
類方法一個常見的用法是作為對象創建的工廠。假如Account類的數據格式有很多種,比如元組、json字符串等。由于Python類只能定義一個__init__方法,所以類方法在這些情形中就很方便。以上文Account類為例,我們想根據一個json字符串對象來初始化一個賬戶,我們定義一個類工廠方法from_json,它讀取json字符串對象,解析參數,根據參數創建賬戶對象。另一個類實例的例子是dict.fromkeys 方法,它從一組鍵和值序列中創建dict對象。
Python特殊方法
有時我們希望自定義類。這需要改變類對象創建和初始化的方法,或者對某些操作提供多態行為。多態行為允許定制在類定義中某些如+等python操作的自身實現。Python的特殊方法可以做到這些。這些方法一般都是__*__形式,其中*表示方法名。如__init__和__new__來自定義對象創建和初始化,__getitem__、__get__、__add__、__sub__來模擬python內建類型,還有__getattribute__、__getattr__等來定制屬性訪問。只有為數不多的特殊方法,我們討論一些重要的特殊方法來做個簡單理解,python文檔有全部方法的列表。
進行對象創建的特殊方法
新的類實例通過兩階段過程創建,__new__方法創建新實例,__init__初始化該實例。用戶已經很熟悉__init__方法的定義;但用戶很少定義__new__方法,但是如果想自定義類實例的創建,也是可以的。
屬性訪問的特殊方法
我們可以通過實現以下方法來定制類實例的屬性訪問。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
class Account( object ): num_accounts = 0 def __init__( self , name, balance): self .name = name self .balance = balance Account.num_accounts + = 1 def del_account( self ): Account.num_accounts - = 1 def __getattr__( self , name): return "Hey I dont see any attribute called {}" . format (name) def deposit( self , amt): self .balance = self .balance + amt def withdraw( self , amt): self .balance = self .balance - amt def inquiry( self ): return "Name={}, balance={}" . format ( self .name, self .balance) @classmethod def from_dict( cls , params): params_dict = json.loads(params) return cls (params_dict.get( "name" ), params_dict.get( "balance" )) @staticmethod def type (): return "Current Account" x = Account( 'obi' , 0 ) |
__getattr__(self, name)__:這個方法只有當name既不是實例屬性也不能在對象的類繼承鏈中找到時才會被調用。這個方法應當返回屬性值或者引發AttributeError異常。例如,如果x是Account類的實例,嘗試訪問不存在的屬性將會調用這個方法。
1
2
3
|
>>> acct = Account( "obi" , 10 ) >>> acct.number Hey I dont see any attribute called number |
注意如果 __getattr__引用不存在的實例屬性,可能會發生死循環,因為__getattr__方法不斷被調用。
2.__setattr__(self, name, value)__:這個方法當屬性賦值發生時調用。__setattr__將會把值插入到實例屬性字典中,而不是使用self.name=value,因為它會導致遞歸調用的死循環。
3.__delattr__(self, name)__:del obj發生時調用。
4.__getattribute__(self, name)__:這個方法會被一直調用以實現類實例的屬性訪問。
類型模擬的特殊方法
對某些類型,Python定義了某些特定語法;比如,列表和元組的元素可以通過索引表示法來訪問,數值可以通過+操作符來進行加法等等。我們可以創建自己的使用這些特殊語法的類,python解釋器遇到這些特殊語法時就會調用我們實現的方法。我們在下面用一個簡單的例子來演示這個特性,它模擬python列表的基本用法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
class CustomList( object ): def __init__( self , container = None ): # the class is just a wrapper around another list to # illustrate special methods if container is None : self .container = [] else : self .container = container def __len__( self ): # called when a user calls len(CustomList instance) return len ( self .container) def __getitem__( self , index): # called when a user uses square brackets for indexing return self .container[index] def __setitem__( self , index, value): # called when a user performs an index assignment if index < = len ( self .container): self .container[index] = value else : raise IndexError() def __contains__( self , value): # called when the user uses the 'in' keyword return value in self .container def append( self , value): self .container.append(value) def __repr__( self ): return str ( self .container) def __add__( self , otherList): # provides support for the use of the + operator return CustomList( self .container + otherList.container) |
上面,CustomList是個真實列表的簡單包裝器。我們為了演示實現了一些自定義方法:
__len__(self):對CustomList實例調用len()函數時被調用。
1
2
3
4
5
6
7
|
>>> myList = CustomList() >>> myList.append( 1 ) >>> myList.append( 2 ) >>> myList.append( 3 ) >>> myList.append( 4 ) >>> len (myList) 4 |
2.__getitem__(self, value):提供CustomList類實例的方括號索引用法支持:
1
2
3
4
5
6
7
|
>>> myList = CustomList() >>> myList.append( 1 ) >>> myList.append( 2 ) >>> myList.append( 3 ) >>> myList.append( 4 ) >>> myList[ 3 ] 4 |
3.__setitem__(self, key, value):當對CustomList類實例上self[key]賦值時調用。
1
2
3
4
5
6
7
8
9
|
>>> myList = CustomList() >>> myList.append( 1 ) >>> myList.append( 2 ) >>> myList.append( 3 ) >>> myList.append( 4 ) >>> myList[ 3 ] = 100 4 >>> myList[ 3 ] 100 |
4.__contains__(self, key):成員檢測時調用。如果包含該項就返回true,否則false。
1
2
3
4
5
6
7
|
>>> myList = CustomList() >>> myList.append( 1 ) >>> myList.append( 2 ) >>> myList.append( 3 ) >>> myList.append( 4 ) >>> 4 in myList True |
5.__repr__(self):當用print打印self時調用,將會打印self的對象表示。
1
2
3
4
5
6
7
|
>>> myList = CustomList() >>> myList.append( 1 ) >>> myList.append( 2 ) >>> myList.append( 3 ) >>> myList.append( 4 ) >>> print myList [ 1 , 2 , 3 , 4 ] |
6.__add__(self, otherList):使用+操作符來計算兩個CustomList實例相加時調用。
1
2
3
4
5
6
7
8
9
|
>>> myList = CustomList() >>> otherList = CustomList() >>> otherList.append( 100 ) >>> myList.append( 1 ) >>> myList.append( 2 ) >>> myList.append( 3 ) >>> myList.append( 4 ) >>> myList + otherList + otherList [ 1 , 2 , 3 , 4 , 100 , 100 ] |
上面的例子演示了如何通過定義某些特殊類方法來定制類行為。可以在Python文檔中查看這些自定義方法的完整列表。在接下來的教程中,我們會將特殊方法放到一起來討論,并解釋描述符這個在python面向對象編程中廣泛使用的重要功能。