Python中的魔術方法詳解


介紹

  在Python中,所有以“__”雙下划線包起來的方法,都統稱為“Magic Method”,中文稱『魔術方法』,例如類的初始化方法 __init__ ,Python中所有的魔術方法均在官方文檔中有相應描述,但是對於官方的描述比較混亂而且組織比較松散。很難找到有一個例子。

構造和初始化

  每個Pythoner都知道一個最基本的魔術方法, __init__ 。通過此方法我們可以定義一個對象的初始操作。然而,當調用 x = SomeClass() 的時候, __init__ 並不是第一個被調用的方法。實際上,還有一個叫做__new__ 的方法,兩個共同構成了“構造函數”。

 

  __new__是用來創建類並返回這個類的實例, 而__init__只是將傳入的參數來初始化該實例。

 

  在對象生命周期調用結束時,__del__ 方法會被調用,可以將__del__理解為“構析函數”。下面通過代碼的看一看這三個方法:

1
2
3
4
5
6
7
8
9
from  os.path  import  join
class  FileObject:
     '''給文件對象進行包裝從而確認在刪除時文件流關閉'''
     def  __init__( self , filepath = '~' , filename = 'sample.txt' ):
         #讀寫模式打開一個文件
         self . file  =  open (join(filepath, filename),  'r+' )
     def  __del__( self ):
         self . file .close()
         del  self . file

控制屬性訪問

  許多從其他語言轉到Python的人會抱怨它缺乏類的真正封裝。(沒有辦法定義私有變量,然后定義公共的getter和setter)。Python其實可以通過魔術方法來完成封裝。我們來看一下:

1
__getattr__( self , name):

  定義當用戶試圖獲取一個不存在的屬性時的行為。這適用於對普通拼寫錯誤的獲取和重定向,對獲取一些不建議的屬性時候給出警告(如果你願意你也可以計算並且給出一個值)或者處理一個 AttributeError 。只有當調用不存在的屬性的時候會被返回。

1
__setattr__( self , name, value):

  與__getattr__(self, name)不同,__setattr__ 是一個封裝的解決方案。無論屬性是否存在,它都允許你定義對對屬性的賦值行為,以為這你可以對屬性的值進行個性定制。實現__setattr__時要避免"無限遞歸"的錯誤。

1
__delattr__:

  與 __setattr__ 相同,但是功能是刪除一個屬性而不是設置他們。實現時也要防止無限遞歸現象發生。

1
__getattribute__( self , name):

  __getattribute__定義了你的屬性被訪問時的行為,相比較,__getattr__只有該屬性不存在時才會起作用。因此,在支持__getattribute__的Python版本,調用__getattr__前必定會調用 __getattribute__。__getattribute__同樣要避免"無限遞歸"的錯誤。需要提醒的是,最好不要嘗試去實現__getattribute__,因為很少見到這種做法,而且很容易出bug。

  在進行屬性訪問控制定義的時候很可能會很容易引起“無限遞歸”。如下面代碼:

1
2
3
4
5
6
7
8
9
#  錯誤用法
def  __setattr__( self , name, value):
     self .name  =  value
     # 每當屬性被賦值的時候(如self.name = value), ``__setattr__()`` 會被調用,這樣就造成了遞歸調用。
     # 這意味這會調用 ``self.__setattr__('name', value)`` ,每次方法會調用自己。這樣會造成程序崩潰。
#  正確用法
def  __setattr__( self , name, value):
     self .__dict__[name]  =  value   # 給類中的屬性名分配值
     # 定制特有屬性

Python的魔術方法很強大,但是用時卻需要慎之又慎,了解正確的使用方法非常重要。

創建自定義容器

  有很多方法可以讓你的Python類行為向內置容器類型一樣,比如我們常用的list、dict、tuple、string等等。Python的容器類型分為可變類型(如list、dict)和不可變類型(如string、tuple),可變容器和不可變容器的區別在於,不可變容器一旦賦值后,不可對其中的某個元素進行修改。

  在講創建自定義容器之前,應該先了解下協議。這里的協議跟其他語言中所謂的"接口"概念很像,它給你很多你必須定義的方法。然而在Python中的協議是很不正式的,不需要明確聲明實現。事實上,他們更像一種指南。

自定義容器的magic method

  下面細致了解下定義容器可能用到的魔術方法。首先,實現不可變容器的話,你只能定義 __len__ 和 __getitem__ (下面會講更多)。可變容器協議則需要所有不可變容器的所有,另外還需要 __setitem__ 和 __delitem__ 。如果你希望你的對象是可迭代的話,你需要定義 __iter__ 會返回一個迭代器。迭代器必須遵循迭代器協議,需要有 __iter__(返回它本身) 和 next。

1
__len__( self ):

  返回容器的長度。對於可變和不可變容器的協議,這都是其中的一部分。

1
__getitem__( self , key):

  定義當某一項被訪問時,使用self[key]所產生的行為。這也是不可變容器和可變容器協議的一部分。如果鍵的類型錯誤將產生TypeError;如果key沒有合適的值則產生KeyError。

1
__setitem__( self , key, value):

  當你執行self[key] = value時,調用的是該方法。

1
__delitem__( self , key):

  定義當一個項目被刪除時的行為(比如 del self[key])。這只是可變容器協議中的一部分。當使用一個無效的鍵時應該拋出適當的異常。

1
__iter__( self ):

  返回一個容器迭代器,很多情況下會返回迭代器,尤其是當內置的iter()方法被調用的時候,以及當使用for x in container:方式循環的時候。迭代器是它們本身的對象,它們必須定義返回self的__iter__方法。

1
__reversed__( self ):

  實現當reversed()被調用時的行為。應該返回序列反轉后的版本。僅當序列可以是有序的時候實現它,例如對於列表或者元組。

1
__contains__( self , item):

  定義了調用in和not in來測試成員是否存在的時候所產生的行為。你可能會問為什么這個不是序列協議的一部分?因為當__contains__沒有被定義的時候,如果沒有定義,那么Python會迭代容器中的元素來一個一個比較,從而決定返回True或者False。

1
__missing__( self , key):

  dict字典類型會有該方法,它定義了key如果在容器中找不到時觸發的行為。比如d = {'a': 1}, 當你執行d[notexist]時,d.__missing__['notexist']就會被調用。

 

實例

  下面是書中的例子,用魔術方法來實現Haskell語言中的一個數據結構。

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
39
40
# -*- coding: utf-8 -*-
class  FunctionalList:
     ''' 實現了內置類型list的功能,並豐富了一些其他方法: head, tail, init, last, drop, take'''
     def  __init__( self , values = None ):
         if  values  is  None :
             self .values  =  []
         else :
             self .values  =  values
     def  __len__( self ):
         return  len ( self .values)
     def  __getitem__( self , key):
         return  self .values[key]
     def  __setitem__( self , key, value):
         self .values[key]  =  value
     def  __delitem__( self , key):
         del  self .values[key]
     def  __iter__( self ):
         return  iter ( self .values)
     def  __reversed__( self ):
         return  FunctionalList( reversed ( self .values))
     def  append( self , value):
         self .values.append(value)
     def  head( self ):
         # 獲取第一個元素
         return  self .values[ 0 ]
     def  tail( self ):
         # 獲取第一個元素之后的所有元素
         return  self .values[ 1 :]
     def  init( self ):
         # 獲取最后一個元素之前的所有元素
         return  self .values[: - 1 ]
     def  last( self ):
         # 獲取最后一個元素
         return  self .values[ - 1 ]
     def  drop( self , n):
         # 獲取所有元素,除了前N個
         return  self .values[n:]
     def  take( self , n):
         # 獲取前N個元素
         return  self .values[:n]

  

  其實在collections模塊中已經有了很多類似的實現,比如Counter、OrderedDict等等。

反射

  你也可以控制怎么使用內置在函數sisinstance()和issubclass()方法 反射定義魔術方法. 這個魔術方法是:

1
__instancecheck__( self , instance):

  檢查一個實例是不是你定義的類的實例

1
__subclasscheck__( self , subclass):

  檢查一個類是不是你定義的類的子類

  這些魔術方法的用例看起來很小, 並且確實非常實用. 它們反應了關於面向對象程序上一些重要的東西在Python上,並且總的來說Python: 總是一個簡單的方法去找某些事情, 即使是沒有必要的. 這些魔法方法可能看起來不是很有用, 但是一旦你需要它們,你會感到慶幸它們的存在。

可調用的對象

  你也許已經知道,在Python中,方法是最高級的對象。這意味着他們也可以被傳遞到方法中,就像其他對象一樣。這是一個非常驚人的特性。

 

  在Python中,一個特殊的魔術方法可以讓類的實例的行為表現的像函數一樣,你可以調用它們,將一個函數當做一個參數傳到另外一個函數中等等。這是一個非常強大的特性,其讓Python編程更加舒適甜美。

1
__call__( self , [args...]):

  允許一個類的實例像函數一樣被調用。實質上說,這意味着 x() 與 x.__call__() 是相同的。注意 __call__ 的參數可變。這意味着你可以定義 __call__ 為其他你想要的函數,無論有多少個參數。

  __call__ 在那些類的實例經常改變狀態的時候會非常有效。調用這個實例是一種改變這個對象狀態的直接和優雅的做法。用一個實例來表達最好不過了:

1
2
3
4
5
6
7
8
9
10
11
12
13
# -*- coding: UTF-8 -*-
class  Entity:
     """
     調用實體來改變實體的位置
     """
def  __init__( self , size, x, y):
     self .x,  self .y  =  x, y
     self .size  =  size
def  __call__( self , x, y):
     """
     改變實體的位置
     """
     self .x,  self .y  =  x, y

上下文管理

  with聲明是從Python2.5開始引進的關鍵詞。你應該遇過這樣子的代碼:

1
2
with  open ( 'foo.txt' ) as bar:
     # do something with bar

  在with聲明的代碼段中,我們可以做一些對象的開始操作和退出操作,還能對異常進行處理。這需要實現兩個魔術方法: __enter__ 和 __exit__。

1
__enter__( self ):

  定義了當使用with語句的時候,會話管理器在塊被初始創建時要產生的行為。請注意,__enter__的返回值與with語句的目標或者as后的名字綁定。

1
__exit__( self , exception_type, exception_value, traceback):

  定義了當一個代碼塊被執行或者終止后,會話管理器應該做什么。它可以被用來處理異常、執行清理工作或做一些代碼塊執行完畢之后的日常工作。如果代碼塊執行成功,exception_type,exception_value,和traceback將會為None。否則,你可以選擇處理這個異常或者是直接交給用戶處理。如果你想處理這個異常的話,請確保__exit__在所有語句結束之后返回True。如果你想讓異常被會話管理器處理的話,那么就讓其產生該異常。

創建對象描述器

  描述器是通過獲取、設置以及刪除的時候被訪問的類。當然也可以改變其它的對象。描述器並不是獨立的。相反,它意味着被一個所有者類持有。當創建面向對象的數據庫或者類,里面含有相互依賴的屬相時,描述器將會非常有用。一種典型的使用方法是用不同的單位表示同一個數值,或者表示某個數據的附加屬性。

  為了成為一個描述器,一個類必須至少有__get__,__set__,__delete__方法被實現:

1
__get__( self , instance, owner):

 

定義了當描述器的值被取得的時候的行為。instance是擁有該描述器對象的一個實例。owner是擁有者本身

1
__set__( self , instance, value):

定義了當描述器的值被改變的時候的行為。instance是擁有該描述器類的一個實例。value是要設置的值。

1
__delete__( self , instance):

定義了當描述器的值被刪除的時候的行為。instance是擁有該描述器對象的一個實例。

 

  下面是一個描述器的實例:單位轉換。

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
# -*- coding: UTF-8 -*-
class  Meter( object ):
     """
     對於單位"米"的描述器
     """
     def  __init__( self , value = 0.0 ):
         self .value  =  float (value)
     def  __get__( self , instance, owner):
         return  self .value
     def  __set__( self , instance, value):
         self .value  =  float (value)
class  Foot( object ):
     """
     對於單位"英尺"的描述器
     """
     def  __get__( self , instance, owner):
         return  instance.meter  *  3.2808
     def  __set__( self , instance, value):
         instance.meter  =  float (value)  /  3.2808
class  Distance( object ):
     """
     用米和英寸來表示兩個描述器之間的距離
     """
     meter  =  Meter( 10 )
     foot  =  Foot()
  使用時:
>>>d  =  Distance()
>>> print  d.foot
>>> print  d.meter
32.808
10.0

 

復制

  有時候,尤其是當你在處理可變對象時,你可能想要復制一個對象,然后對其做出一些改變而不希望影響原來的對象。這就是Python的copy所發揮作用的地方。

1
__copy__( self ):

  定義了當對你的類的實例調用copy.copy()時所產生的行為。copy.copy()返回了你的對象的一個淺拷貝——這意味着,當實例本身是一個新實例時,它的所有數據都被引用了——例如,當一個對象本身被復制了,它的數據仍然是被引用的(因此,對於淺拷貝中數據的更改仍然可能導致數據在原始對象的中的改變)。

1
__deepcopy__( self , memodict = {}):

  定義了當對你的類的實例調用copy.deepcopy()時所產生的行為。copy.deepcopy()返回了你的對象的一個深拷貝——對象和其數據都被拷貝了。memodict是對之前被拷貝的對象的一個緩存——這優化了拷貝過程並且阻止了對遞歸數據結構拷貝時的無限遞歸。當你想要進行對一個單獨的屬性進行深拷貝時,調用copy.deepcopy(),並以memodict為第一個參數。

 

其他方法

用於比較的魔術方法

__cmp__(self, other)    是比較方法里面最基本的的魔法方法

__eq__(self, other) 定義相等符號的行為,==

__ne__(self,other)  定義不等符號的行為,!=

__lt__(self,other)  定義小於符號的行為,<

__gt__(self,other)  定義大於符號的行為,>

__le__(self,other)  定義小於等於符號的行為,<=

__ge__(self,other)  定義大於等於符號的行為,>=

 

數值計算的魔術方法

單目運算符和函數

__pos__(self)   實現一個取正數的操作

__neg__(self)   實現一個取負數的操作

__abs__(self)   實現一個內建的abs()函數的行為

__invert__(self)    實現一個取反操作符(~操作符)的行為

__round__(self, n)  實現一個內建的round()函數的行為

__floor__(self) 實現math.floor()的函數行為

__ceil__(self)  實現math.ceil()的函數行為

__trunc__(self) 實現math.trunc()的函數行為

 

雙目運算符或函數

__add__(self, other)    實現一個加法

__sub__(self, other)    實現一個減法

__mul__(self, other)    實現一個乘法

__floordiv__(self, other)   實現一個“//”操作符產生的整除操作()

__div__(self, other)    實現一個“/”操作符代表的除法操作

__truediv__(self, other)    實現真實除法

__mod__(self, other)    實現一個“%”操作符代表的取模操作

__divmod__(self, other) 實現一個內建函數divmod()

__pow__ 實現一個指數操作(“**”操作符)的行為

__lshift__(self, other) 實現一個位左移操作(<<)的功能

__rshift__(self, other) 實現一個位右移操作(>>)的功能

__and__(self, other)    實現一個按位進行與操作(&)的行為

__or__(self, other) 實現一個按位進行或操作的行為

__xor__(self, other)    __xor__(self, other)

 

增量運算

__iadd__(self, other)   加法賦值

__isub__(self, other)   減法賦值

__imul__(self, other)   乘法賦值

__ifloordiv__(self, other)  整除賦值,地板除,相當於 //= 運算符

__idiv__(self, other)   除法賦值,相當於 /= 運算符

__itruediv__(self, other)   真除賦值

__imod_(self, other)    模賦值,相當於 %= 運算符

__ipow__    乘方賦值,相當於 **= 運算符

__ilshift__(self, other)    左移賦值,相當於 <<= 運算符

__irshift__(self, other)    左移賦值,相當於 >>= 運算符

__iand__(self, other)   與賦值,相當於 &= 運算符

__ior__(self, other)    或賦值

__ixor__(self, other)   異或運算符,相當於 ^= 運算符

 

類型轉換

__int__(self)   轉換成整型

__long__(self)  轉換成長整型

__float__(self) 轉換成浮點型

__complex__(self)   轉換成 復數型

__oct__(self)   轉換成八進制

__hex__(self)   轉換成十六進制

__index__(self) 如果你定義了一個可能被用來做切片操作的數值型,你就應該定義__index__

__trunc__(self) 當 math.trunc(self) 使用時被調用__trunc__返回自身類型的整型截取

__coerce__(self, other) 執行混合類型的運算


免責聲明!

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



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