1. 程式人生 > >Python內建方法詳解

Python內建方法詳解

  1. 簡介

  本指南主題是 魔法方法 。

  所以,為了修復我感知的Python文件的缺陷,我開始提供更為通俗的,有示例支援的Python魔法方法指南。我一開始 寫了一些博文,現在我把這些博文總起來成為一篇指南。

  希望你喜歡這篇指南,一篇友好,通俗易懂的Python魔法方法指南!

  2. 構造方法

  我們最為熟知的基本的魔法方法就是 __init__ ,我們可以用它來指明一個物件初始化的行為。然而,當我們呼叫 x = SomeClass() 的時候, __init__ 並不是第一個被呼叫的方法。事實上,第一個被呼叫的是 __new__ ,這個 方法才真正地建立了例項

。當這個物件的生命週期結束的時候, __del__ 會被呼叫。讓我們近一步理解這三個方法:

  __new__(cls,[...)

  __new__ 是物件例項化時第一個呼叫的方法,它只取下 cls 引數,並把其他引數傳給 __init__ 。 __new__ 很少使用,但是也有它適合的場景,尤其是當類繼承自一個像元組或者字串這樣不經常改變的型別的時候。我不打算深入討論 __new__ ,因為它並不是很有用, Python文件 中 有詳細的說明。

  __init__(self,[...])

  類的初始化方法。它獲取任何傳給構造器的引數(比如我們呼叫 x = SomeClass(10, ‘foo’) , __init__ 就會接到引數 10 和 ‘foo’ 。 __init__ 在Python的類定義中用的最多

  __del__(self)

  __new__ 和 __init__ 是物件的構造器, __del__ 是物件的銷燬器。它並非實現了語句 del x (因此該語句不等同於 x.__del__())。而是定義了當物件被垃圾回收時的行為。 當物件需要在銷燬時做一些處理的時候這個方法很有用,比如 socket 物件、檔案物件。但是需要注意的是,當Python直譯器退出但物件仍然存活的時候, __del__ 並不會 執行。 所以養成一個手工清理的好習慣是很重要的,比如及時關閉連線

這裡有個 __init__ 和 __del__ 的例子:

from os.path import
join class FileObject: '''檔案物件的裝飾類,用來保證檔案被刪除時能夠正確關閉。''' def __init__(self, filepath='~', filename='sample.txt'): # 使用讀寫模式開啟filepath中的filename檔案 self.file = open(join(filepath, filename), 'r+') def __del__(self): self.file.close() del self.file

  3. 操作符

  使用Python魔法方法的一個巨大優勢就是可以構建一個擁有Python內建型別行為的物件。這意味著你可以避免使用非標準的、醜陋的方式來表達簡單的操作。 在一些語言中,這樣做很常見:  

if instance.equals(other_instance):
    # do something

  你當然可以在Python也這麼做,但是這樣做讓程式碼變得冗長而混亂不同的類庫可能對同一種比較操作採用不同的方法名稱,這讓使用者需要做很多沒有必要的工作。運用魔法方法的魔力,我們可以定義方法 __eq_

if instance == other_instance:
    # do something

  這是魔法力量的一部分,這樣我們就可以建立一個像內建型別那樣的物件了

  3.1. 比較操作符

  Python包含了一系列的魔法方法,用於實現物件之間直接比較,而不需要採用方法呼叫。同樣也可以過載Python預設的比較方法,改變它們的行為。下面是這些方法的列表:

  __cmp__(self, other)

  __cmp__ 是所有比較魔法方法中最基礎的一個,它實際上定義了所有比較操作符的行為(<,==,!=,等等),但是它可能不能按照你需要的方式工作(例如,判斷一個例項和另一個例項是否相等採用一套標準,而與判斷一個例項是否大於另一例項採用另一套)。 __cmp__ 應該在 self < other 時返回一個負整數,在 self == other 時返回0,在 self > other 時返回正整數。最好只定義你所需要的比較形式,而不是一次定義全部。 如果你需要實現所有的比較形式,而且它們的判斷標準類似,那麼 __cmp__ 是一個很好的方法,可以減少程式碼重複,讓程式碼更簡潔。

  __eq__`(self, other)

  定義等於操作符(==)的行為。

  __ne__(self, other)

  定義不等於操作符(!=)的行為

  __lt__(self, other)

  定義小於操作符(<)的行為。

  __gt__(self, other)

  定義大於操作符(>)的行為。

  __le__(self, other)

  定義小於等於操作符(<)的行為

  __ge__(self, other)

  定義大於等於操作符(>)的行為

  舉個例子,假如我們想用一個類來儲存單詞。我們可能想按照字典序(字母順序)來比較單詞,字串的預設比較行為就是這樣。我們可能也想按照其他規則來比較字串,像是長度,或者音節的數量。在這個例子中,我們使用長度作為比較標準,下面是一種實現:

class Word(str):
    '''單詞類,按照單詞長度來定義比較行為'''

    def __new__(cls, word):
        # 注意,我們只能使用 __new__ ,因為str是不可變型別
        # 所以我們必須提前初始化它(在例項建立時)
        if ' ' in word:
            print "Value contains spaces. Truncating to first space."
            word = word[:word.index(' ')]
        # Word現在包含第一個空格前的所有字母
        return str.__new__(cls, word)

    def __gt__(self, other):
        return len(self) > len(other)
    def __lt__(self, other):
        return len(self) < len(other)
    def __ge__(self, other):
        return len(self) >= len(other)
    def __le__(self, other):
        return len(self) <= len(other)

  現在我們可以建立兩個 Word 物件( Word(‘foo’) 和 Word(‘bar’))然後根據長度來比較它們。注意我們沒有定義 __eq__ 和 __ne__ ,這是因為有時候它們會導致奇怪的結果(很明顯, Word(‘foo’) == Word(‘bar’) 得到的結果會是true)。根據長度測試是否相等毫無意義,所以我們使用 str 的實現來比較相等

  從上面可以看到,不需要實現所有的比較魔法方法,就可以使用豐富的比較操作。標準庫還在 functools 模組中提供了一個類裝飾器,只要我們定義 __eq__ 和另外一個操作符( __gt__, __lt__ 等),它就可以幫我們實現比較方法。這個特性只在 Python 2.7 中可用。當它可用時,它能幫助我們節省大量的時間和精力。要使用它,只需要它 @total_ordering 放在類的定義之上就可以了

  3.2. 數值操作符

  就像你可以使用比較操作符來比較類的例項,你也可以定義數值操作符的行為。固定好你的安全帶,這樣的操作符真的有很多。看在組織的份上,我把它們分成了五類:一元操作符,常見算數操作符,反射算數操作符(後面會涉及更多),增強賦值操作符,和型別轉換操作符。

  3.2.1. 一元操作符

  一元操作符只有一個操作符。

  __pos__(self)

  實現取正操作,例如 +some_object。

  __neg__(self)

  實現取負操作,例如 -some_object。

  __abs__(self)

  實現內建絕對值函式 abs() 操作。

  __invert__(self)

  實現取反操作符 ~。

  __round__(self, n)

  實現內建函式 round() ,n 是近似小數點的位數。

  __floor__(self)

  實現 math.floor() 函式,即向下取整。

  __ceil__(self)

  實現 math.ceil() 函式,即向上取整。

  __trunc__(self) 

  實現 math.trunc() 函式,即距離零最近的整數。

  3.2.2. 常見算數操作符

  現在,我們來看看常見的二元操作符(和一些函式),像+,-,*之類的,它們很容易從字面意思理解。

  __add__(self, other)

  實現加法操作。

  __sub__(self, other)

  實現減法操作。

  __mul__(self, other)

  實現乘法操作。

  __floordiv__(self, other)

  實現使用 // 操作符的整數除法。

  __div__(self, other)

  實現使用 / 操作符的除法。

  __truediv__(self, other)

  實現 _true_ 除法,這個函式只有使用 from __future__ import division時才有作用。

  __mod__(self, other)

  實現 % 取餘操作。

  __divmod__(self, other)

  實現 divmod 內建函式。

  __pow__

  實現 ** 操作符。

  __lshift__(self, other)

  實現左移位運算子 << 。

  __rshift__(self, other)

  實現右移位運算子 >> 。

  __and__(self, other)

  實現按位與運算子 & 。

  __or__(self, other)

  實現按位或運算子 | 。

  __xor__(self, other)

  實現按位異或運算子 ^ 。

  3.2.3. 反射算數運算子

  還記得剛才我說會談到反射運算子嗎?可能你會覺得它是什麼高階霸氣上檔次的概念,其實這東西挺簡單的,下面舉個例子:

 

some_object + other

 

  這是“常見”的加法,反射是一樣的意思,只不過是運算子交換了一下位置:

 

other + some_object

  所有反射運算子魔法方法和它們的常見版本做的工作相同,只不過是處理交換連個運算元之後的情況。絕大多數情況下,反射運算和正常順序產生的結果是相同的,所以很可能你定義 __radd__ 時只是呼叫一下 __add__。注意一點,操作符左側的物件(也就是上面的 other )一定不要定義(或者產生 NotImplemented 異常) 操作符的非反射版本。例如,在上面的例子中,只有當 other 沒有定義 __add__ 時 some_object.__radd__ 才會被呼叫

 

  __radd__(self, other)

  實現反射加法操作。

  __rsub__(self, other)

  實現反射減法操作。

  __rmul__(self, other)

  實現反射乘法操作。

  __rfloordiv__(self, other)

  實現使用 // 操作符的整數反射除法。

  __rdiv__(self, other)

  實現使用 / 操作符的反射除法。

  __rtruediv__(self, other)

  實現 _true_ 反射除法,這個函式只有使用 from __future__ import division 時才有作用。

  __rmod__(self, other)

  實現 % 反射取餘操作符。

  __rdivmod__(self, other)

  實現呼叫 divmod(other, self) 時 divmod 內建函式的操作。

  __rpow__

  實現 ** 反射操作符。

  __rlshift__(self, other)

  實現反射左移位運算子 << 的作用。

  __rshift__(self, other)

  實現反射右移位運算子 >> 的作用。

  __rand__(self, other)

  實現反射按位與運算子 & 。

  __ror__(self, other)

  實現反射按位或運算子 | 。

  __rxor__(self, other)

  實現反射按位異或運算子 ^ 。

  3.2.4. 增強賦值運算子

  Python同樣提供了大量的魔法方法,可以用來自定義增強賦值操作的行為。或許你已經瞭解增強賦值,它融合了“常見”的操作符和賦值操作,如果你還是沒聽明白,看下面的例子:  

 

  x = 5
  x += 1 # 也就是 x = x + 1

 

  這些方法都應該返回左側運算元應該被賦予的值(例如, a += b __iadd__ 也許會返回 a + b ,這個結果會被賦給 a ),下面是方法列表:

  __iadd__(self, other)

  實現加法賦值操作。

  __isub__(self, other)

  實現減法賦值操作。

  __imul__(self, other)

  實現乘法賦值操作。

  __ifloordiv__(self, other)

  實現使用 //= 操作符的整數除法賦值操作。

  __idiv__(self, other)

  實現使用 /= 操作符的除法賦值操作。

  __itruediv__(self, other)

  實現 _true_ 除法賦值操作,這個函式只有使用 from __future__ import division 時才有作用。

  __imod__(self, other)

  實現 %= 取餘賦值操作。

  __ipow__

  實現 **= 操作。

  __ilshift__(self, other)

  實現左移位賦值運算子 <<= 。

  __irshift__(self, other)

  實現右移位賦值運算子 >>= 。

  __iand__(self, other)

  實現按位與運算子 &= 。

  __ior__(self, other)

  實現按位或賦值運算子 | 。

  __ixor__(self, other)

  實現按位異或賦值運算子 ^= 。

  3.2.5. 型別轉換操作符

  Python也有一系列的魔法方法用於實現類似 float() 的內建型別轉換函式的操作。它們是這些:

  __int__(self)

  實現到int的型別轉換。

  __long__(self)

  實現到long的型別轉換。

  __float__(self)

  實現到float的型別轉換。

  __complex__(self)

  實現到complex的型別轉換。

  __oct__(self)

  實現到八進位制數的型別轉換。

  __hex__(self)

  實現到十六進位制數的型別轉換。

  __index__(self)

  實現當物件用於切片表示式時到一個整數的型別轉換。如果你定義了一個可能會用於切片操作的數值型別,你應該定義 __index__。

  __trunc__(self)

  當呼叫 math.trunc(self) 時呼叫該方法, __trunc__ 應該返回 self 擷取到一個整數型別(通常是long型別)的值。

  __coerce__(self)

  該方法用於實現混合模式算數運算,如果不能進行型別轉換, __coerce__ 應該返回 None 。反之,它應該返回一個二元組 self 和 other ,這兩者均已被轉換成相同的型別。

  4. 類的表示

  使用字串來表示類是一個相當有用的特性。在Python中有一些內建方法可以返回類的表示,相對應的,也有一系列魔法方法可以用來自定義在使用這些內建函式時類的行為。

  __str__(self)

  定義對類的例項呼叫 str() 時的行為。

  __repr__(self)

  定義對類的例項呼叫 repr() 時的行為。str() 和 repr() 最主要的差別在於“目標使用者”。 repr() 的作用是產生機器可讀的輸出(大部分情況下,其輸出可以作為有效的Python程式碼),而 str() 則產生人類可讀的輸出

  __unicode__(self)

  定義對類的例項呼叫 unicode() 時的行為。 unicode() 和 str() 很像,只是它返回unicode字串。注意,如果呼叫者試圖呼叫 str() 而你的類只實現了 __unicode__() ,那麼類將不能正常工作。所有你應該總是定義 __str__() ,以防有些人沒有閒情雅緻來使用unicode

  __format__(self)

  定義當類的例項用於新式字串格式化時的行為,例如, “Hello, 0:abc!”.format(a) 會導致呼叫 a.__format__(“abc”) 。當定義你自己的數值型別或字串型別時,你可能想提供某些特殊的格式化選項,這種情況下這個魔法方法會非常有用。

  __hash__(self)

  定義對類的例項呼叫 hash() 時的行為。它必須返回一個整數,其結果會被用於字典中鍵的快速比較。同時注意一點,實現這個魔法方法通常也需要實現 __eq__ ,並且遵守如下的規則: a == b 意味著 hash(a) == hash(b)

  __nonzero__(self)

  定義對類的例項呼叫 bool() 時的行為,根據你自己對類的設計,針對不同的例項,這個魔法方法應該相應地返回True或False。

  __dir__(self)

  定義對類的例項呼叫 dir() 時的行為,這個方法應該向呼叫者返回一個屬性列表。一般來說,沒必要自己實現 __dir__ 。但是如果你重定義了 __getattr__ 或者 __getattribute__ (下個部分會介紹),乃至使用動態生成的屬性,以實現類的互動式使用,那麼這這個魔法方法是必不可少的。  

  到這裡,我們基本上已經結束了魔法方法指南中無聊並且例子匱乏的部分。既然我們已經介紹了較為基礎的魔法方法,是時候涉及更高階的內容了。

  5. 訪問控制

  很多從其他語言轉向Python的人都抱怨Python的類缺少真正意義上的封裝即沒辦法定義私有屬性然後使用公有的getter和setter)。然而事實並非如此。實際上Python不是通過顯式定義的欄位和方法修改器,而是通過魔法方法實現了一系列的封裝

  __getattr__(self, name)

  當用戶試圖訪問一個根本不存在(或者暫時不存在)的屬性時,你可以通過這個魔法方法來定義類的行為。這個可以用於捕捉錯誤的拼寫並且給出指引,使用廢棄屬性時給出警告(如果你願意,仍然可以計算並且返回該屬性),以及靈活地處理AttributeError。只有當試圖訪問不存在的屬性時它才會被呼叫,所以這不能算是一個真正的封裝的辦法。

  __setattr__(self, name, value)

  和 __getattr__ 不同, __setattr__ 可以用於真正意義上的封裝。它允許你自定義某個屬性的賦值行為,不管這個屬性存在與否,也就是說你可以對任意屬性的任何變化都定義自己的規則。然後,一定要小心使用 __setattr__ ,這個列表最後的例子中會有所展示。

  __delattr__(self, name)

  這個魔法方法和 __setattr__ 幾乎相同,只不過它是用於處理刪除屬性時的行為。和 __setattr__ 一樣,使用它時也需要多加小心,防止產生無限遞迴(在  __delattr__ 的實現中呼叫 del self.name 會導致無限遞迴)。

  __getattribute__(self, name)

  ` __getattribute__` 看起來和上面那些方法很合得來,但是最好不要使用它。 __getattribute__ 只能用於新式類。在最新版的Python中所有的類都是新式類,在老版Python中你可以通過繼承 object 來建立新式類。 __getattribute__ 允許你自定義屬性被訪問時的行為它也同樣可能遇到無限遞迴問題(通過呼叫基類的 __getattribute__ 來避免)。 __getattribute__ 基本上可以替代 __getattr__ 。只有當它被實現,並且顯式地被呼叫,或者產生 AttributeError 時它才被使用。 這個魔法方法可以被使用(畢竟,選擇權在你自己),我不推薦你使用它,因為它的使用範圍相對有限(通常我們想要在賦值時進行特殊操作,而不是取值時),而且實現這個方法很容易出現Bug

  自定義這些控制屬性訪問的魔法方法很容易導致問題,考慮下面這個例子:

 

def __setattr__(self, name. value):
    self.name = value
    # 因為每次屬性幅值都要呼叫 __setattr__(),所以這裡的實現會導致遞迴
    # 這裡的呼叫實際上是 self.__setattr('name', value)。因為這個方法一直
    # 在呼叫自己,因此遞迴將持續進行,直到程式崩潰

def __setattr__(self, name, value):
    self.__dict__[name] = value # 使用 __dict__ 進行賦值
    # 定義自定義行為

  再次重申,Python的魔法方法十分強大,能力越強責任越大,瞭解如何正確的使用魔法方法更加重要。

 

  到這裡,我們對Python中自定義屬性存取控制有了什麼樣的印象?它並不適合輕度的使用。實際上,它有些過分強大,而且違反直覺。然而它之所以存在,是因為一個更大的原則:Python不指望讓杜絕壞事發生,而是想辦法讓做壞事變得困難自由是至高無上的權利,你真的可以隨心所欲。下面的例子展示了實際應用中某些特殊的屬性訪問方法(注意我們之所以使用 super 是因為不是所有的類都有 __dict__ 屬性):

 

class AccessCounter(object):
    ''' 一個包含了一個值並且實現了訪問計數器的類
    每次值的變化都會導致計數器自增'''

    def __init__(self, val):
        super(AccessCounter, self).__setattr__('counter', 0)
        super(AccessCounter, self).__setattr__('value', val)

    def __setattr__(self, name, value):
        if name == 'value':
            super(AccessCounter, self).__setattr_('counter', self.counter + 1)
        # 使計數器自增變成不可避免
        # 如果你想阻止其他屬性的賦值行為
        # 產生 AttributeError(name) 就可以了
        super(AccessCounter, self).__setattr__(name, value)

    def __delattr__(self, name):
        if name == 'value':
            super(AccessCounter, self).__setattr('counter', self.counter + 1)
            super(AccessCounter, self).__delattr(name)

 

  6. 自定義序列

  有許多辦法可以讓你的Python類表現得像是內建序列型別(字典,元組,列表,字串等)。這些魔法方式是目前為止我最喜歡的。它們給了你難以置信的控制能力,可以讓你的類與一系列的全域性函式完美結合。在瞭解激動人心的內容之前,首先你需要掌握一些預備知識。

  6.1. 預備知識

  既然講到建立自己的序列型別,就不得不說一說協議了。協議類似某些語言中的介面,裡面包含的是一些必須實現的方法。在Python中,協議完全是非正式的,也不需要顯式的宣告,事實上,它們更像是一種參考標準。

  為什麼我們要講協議?因為在Python中實現自定義容器型別需要用到一些協議。首先,不可變容器型別有如下協議:想實現一個不可變容器,你需要定義 __len__ 和 __getitem__ (後面會具體說明)。可變容器的協議除了上面提到的兩個方法之外,還需要定義 __setitem__ 和 __delitem__ 。最後,如果你想讓你的物件可以迭代,你需要定義 __iter__ ,這個方法返回一個迭代器。迭代器必須遵守迭代器協議,需要定義 __iter__ (返回它自己)和 next 方法

  6.2. 容器背後的魔法方法

  __len__(self)

  返回容器的長度,可變和不可變型別都需要實現

  __getitem__(self, key)

  定義對容器中某一項使用 self[key] 的方式進行讀取操作時的行為。這也是可變和不可變容器型別都需要實現的一個方法。它應該在鍵的型別錯誤式產生 TypeError 異常,同時在沒有與鍵值相匹配的內容時產生 KeyError 異常

  __setitem__(self, key)

  定義對容器中某一項使用 self[key] 的方式進行賦值操作時的行為。它是可變容器型別必須實現的一個方法,同樣應該在合適的時候產生 KeyError 和 TypeError 異常。  __iter__(self, key) 它應該返回當前容器的一個迭代器。迭代器以一連串內容的形式返回,最常見的是使用 iter() 函式呼叫,以及在類似 for x in container: 的迴圈中被呼叫。迭代器是他們自己的物件,需要定義 __iter__ 方法並在其中返回自己。

  __reversed__(self)

  定義了對容器使用 reversed() 內建函式時的行為。它應該返回一個反轉之後的序列。當你的序列類是有序時,類似列表和元組,再實現這個方法。

  __contains__(self, item)

  __contains__ 定義了使用 in 和  not in 進行成員測試時類的行為。你可能好奇為什麼這個方法不是序列協議的一部分,原因是,如果 __contains__ 沒有定義,Python就會迭代整個序列,如果找到了需要的一項就返回 True 。

  __missing__(self ,key)

  __missing__ 在字典的子類中使用,它定義了當試圖訪問一個字典中不存在的鍵時的行為(目前為止是指字典的例項,例如我有一個字典 d , “george” 不是字典中的一個鍵,當試圖訪問 d[“george’] 時就會呼叫 d.__missing__(“george”) )。

  6.3. 一個例子

  讓我們來看一個實現了一些函式式結構的列表,可能在其他語言中這種結構更常見(例如Haskell):

 

class FunctionalList:
    '''一個列表的封裝類,實現了一些額外的函式式
    方法,例如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 reversed(self.values)

    def append(self, value):
        self.values.append(value)

    def head(self):
        # 取得第一個元素
        return self.values[0]

    def tail(self):
        # 取得除第一個元素外的所有元素
        return self.valuse[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]

  就是這些,一個(微不足道的)有用的例子,向你展示瞭如何實現自己的序列。當然啦,自定義序列有更大的用處,而且絕大部分都在標準庫中實現了(Python是自帶電池的,記得嗎?),像 Counter , OrderedDict 和 NamedTuple 。

 

  7. 反射

  你可以通過定義魔法方法來控制用於反射的內建函式 isinstance 和 issubclass 的行為。下面是對應的魔法方法:

  __instancecheck__(self, instance)

  檢查一個例項是否是你定義的類的一個例項(例如 isinstance(instance, class) )。

  __subclasscheck__(self, subclass)

  檢查一個類是否是你定義的類的子類(例如 issubclass(subclass, class) )。  這幾個魔法方法的適用範圍看起來有些窄,事實也正是如此。我不會在反射魔法方法上花費太多時間,因為相比其他魔法方法它們顯得不是很重要。但是它們展示了在Python中進行面向物件程式設計(或者總體上使用Python進行程式設計)時很重要的一點:不管做什麼事情,都會有一個簡單方法,不管它常用不常用。這些魔法方法可能看起來沒那麼有用,但是當你真正需要用到它們的時候,你會感到很幸運,因為它們還在那兒(也因為你閱讀了這本指南!)

  8. 抽象基類

  請參考 http://docs.python.org/2/library/abc.html.

  9. 可呼叫的物件

  你可能已經知道了,在Python中,函式是一等的物件。這意味著它們可以像其他任何物件一樣被傳遞到函式和方法中,這是一個十分強大的特性。

  Python中一個特殊的魔法方法允許你自己類的物件表現得像是函式,然後你就可以“呼叫”它們,把它們傳遞到使用函式做引數的函式中,等等等等。這是另一個強大而且方便的特性,讓使用Python程式設計變得更加幸福。

  __call__(self, [args...])

  允許類的一個例項像函式那樣被呼叫。本質上這代表了 x() 和 x.__call__() 是相同的。注意 __call__ 可以有多個引數,這代表你可以像定義其他任何函式一樣,定義 __call__ ,喜歡用多少引數就用多少。

  __call__在某些需要經常改變狀態的類的例項中顯得特別有用。“呼叫”這個例項來改變它的狀態,是一種更加符合直覺,也更加優雅的方法。一個表示平面上實體的類是一個不錯的例子:

 

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

 

  10. 上下文管理器

  在Python 2.5中引入了一個全新的關鍵詞,隨之而來的是一種新的程式碼複用方法—— with 宣告。上下文管理的概念在Python中並不是全新引入的(之前它作為標準庫的一部分實現),直到PEP 343被接受,它才成為一種一級的語言結構。可能你已經見過這種寫法了:

 

with open('foo.txt') as bar:
    # 使用bar進行某些操作

  當物件使用 with 宣告建立時,上下文管理器允許類做一些設定和清理工作。上下文管理器的行為下面兩個魔法方法所定義:

 

  __enter__(self)

  定義使用 with 宣告建立的語句塊最開始上下文管理器應該做些什麼。注意 __enter__ 的返回值會賦給 with 宣告的目標,也就是 as 之後的東西。

  __exit__(self, exception_type, exception_value, traceback)

  定義當 with 宣告語句塊執行完畢(或終止)時上下文管理器的行為。它可以用來處理異常,進行清理,或者做其他應該在語句塊結束之後立刻執行的工作。如果語句塊順利執行, exception_type , exception_value 和 traceback 會是 None 。否則,你可以選擇處理這個異常或者讓使用者來處理。如果你想處理異常,確保 __exit__ 在完成工作之後返回 True 。如果你不想處理異常,那就讓它發生吧。

  對一些具有良好定義的且通用的設定和清理行為的類,__enter__ 和 __exit__ 會顯得特別有用。你也可以使用這幾個方法來建立通用的上下文管理器,用來包裝其他物件。下面是一個例子:

 

class Closer:
    '''一個上下文管理器,可以在with語句中
    使用close()自動關閉物件'''

    def __init__(self, obj):
        self.obj = obj

    def __enter__(self, obj):
        return self.obj # 繫結到目標

    def __exit__(self, exception_type, exception_value, traceback):
        try:
            self.obj.close()
        except AttributeError: # obj不是可關閉的
            print 'Not closable.'
            return True # 成功地處理了異常

  這是一個 Closer 在實際使用中的例子,使用一個FTP連線來演示(一個可關閉的socket):

>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
...     conn.dir()
...
# 為了簡單,省略了某些輸出
>>> conn.dir()
# 很長的 AttributeError 資訊,不能使用一個已關閉的連線
>>> with Closer(int(5)) as i:
...     i += 1
...
Not closable.
>>> i
6

 

  看到我們的包裝器是如何同時優雅地處理正確和不正確的呼叫了嗎?這就是上下文管理器和魔法方法的力量。Python標準庫包含一個 contextlib 模組,裡面有一個上下文管理器 contextlib.closing() 基本上和我們的包裝器完成的是同樣的事情(但是沒有包含任何當物件沒有close()方法時的處理)。

  11. 建立描述符物件

  描述符是一個類,當使用取值,賦值和刪除 時它可以改變其他物件。描述符不是用來單獨使用的,它們需要被一個擁有者類所包含。描述符可以用來建立面向物件資料庫,以及建立某些屬性之間互相依賴的類。描述符在表現具有不同單位的屬性,或者需要計算的屬性時顯得特別有用(例如表現一個座標系中的點的類,其中的距離原點的距離這種屬性)。

  要想成為一個描述符,一個類必須具有實現 __get__ , __set__ 和 __delete__ 三個方法中至少一個。

  讓我們一起來看一看這些魔法方法:

  __get__(self, instance, owner)

  定義當試圖取出描述符的值時的行為。 instance 是擁有者類的例項, owner 是擁有者類本身。

  __set__(self, instance, owner)

  定義當描述符的值改變時的行為。 instance 是擁有者類的例項, value 是要賦給描述符的值。

  __delete__(self, instance, owner)

  定義當描述符的值被刪除時的行為。instance 是擁有者類的例項

  現在,來看一個描述符的有效應用:單位轉換:

 

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, owner):
        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()
    foot = Foot()

  12. 拷貝

 

  有些時候,特別是處理可變物件時,你可能想拷貝一個物件,改變這個物件而不影響原有的物件。這時就需要用到Python的 copy 模組了。然而(幸運的是),Python模組並不具有感知能力, 因此我們不用擔心某天基於Linux的機器人崛起。但是我們的確需要告訴Python如何有效率地拷貝物件。

  __copy__(self)

  定義對類的例項使用 copy.copy() 時的行為。 copy.copy() 返回一個物件的淺拷貝,這意味著拷貝出的例項是全新的,然而裡面的資料全都是引用的。也就是說,物件本身是拷貝的,但是它的資料還是引用的(所以淺拷貝中的資料更改會影響原物件)。

  __deepcopy__(self, memodict=)

  定義對類的例項使用 copy.deepcopy() 時的行為。 copy.deepcopy() 返回一個物件的深拷貝,這個物件和它的資料全都被拷貝了一份。 memodict 是一個先前拷貝物件的快取,它優化了拷貝過程,而且可以防止拷貝遞迴資料結構時產生無限遞迴。當你想深拷貝一個單獨的屬性時,在那個屬性上呼叫 copy.deepcopy() ,使用 memodict 作為第一個引數。

  這些魔法方法有什麼用武之地呢?像往常一樣,當你需要比預設行為更加精確的控制時。例如,如果你想拷貝一個物件,其中儲存了一個字典作為快取(可能會很大),拷貝快取可能是沒有意義的。如果這個快取可以在記憶體中被不同例項共享,那麼它就應該被共享。

  13. Pickling

  如果你和其他的Python愛好者共事過,很可能你已經聽說過Pickling了。Pickling了。Pickling是Python資料結構的序列化過程,當你想儲存一個物件稍後再取出讀取時,Pickling會顯得十分有用。然而它同樣也是擔憂和混淆的主要來源

  Pickling是如此的重要,以至於它不僅僅有自己的模組( pickle ),還有自己的協議和魔法方法。首先,我們先來簡要的介紹一下如何pickle已存在的物件型別(如果你已經知道了,大可跳過這部分內容)。

  13.1. Pickling : 小試牛刀

  我們一起來pickle吧。假設你有一個字典,你想儲存它,稍後再取出來。你可以把它的內容寫入一個檔案,小心翼翼地確保使用了正確地格式,要把它讀取出來,你可以使用 exec() 或處理檔案輸入。但是這種方法並不可靠:如果你使用純文字來儲存重要資料,資料很容易以多種方式被破壞或者修改,導致你的程式崩潰,更糟糕的情況下,還可能在你的計算機上執行惡意程式碼。因此,我們要pickle它:

 

import pickle

data = {'foo': [1,2,3],
        'bar': ('Hello', 'world!'),
        'baz': True}
jar = open('data.pkl', 'wb')
pickle.dump(data, jar) # 將pickle後的資料寫入jar檔案
jar.close()

  過了幾個小時,我們想把它取出來,我們只需要反pickle它:

import pickle

pkl_file = open('data.pkl', 'rb') # 與pickle後的資料連線
data = pickle.load(pkl_file) # 把它載入進一個變數
print data
pkl_file.close()

  將會發生什麼?正如你期待的,它就是我們之前的 data 。

 

  現在,還需要謹慎地說一句: pickle並不完美。Pickle檔案很容易因為事故或被故意的破壞掉。Pickling或許比純文字檔案安全一些,但是依然有可能被用來執行惡意程式碼。而且它還不支援跨Python版本,所以不要指望分發pickle物件之後所有人都能正確地讀取。然而不管怎麼樣,它依然是一個強有力的工具,可以用於快取和其他型別的持久化工作。

  13.2. Pickle你的物件

  Pickle不僅僅可以用於內建型別,任何遵守pickle協議的類都可以被pickle。Pickle協議有四個可選方法,可以讓類自定義它們的行為(這和C語言擴充套件略有不同,那不在我們的討論範圍之內)。

  __getinitargs__(self)

  如果你想讓你的類在反pickle時呼叫 __init__ ,你可以定義 __getinitargs__(self),它會返回一個引數元組,這個元組會傳遞給 __init__ 。注意,這個方法只能用於舊式類。   __getnewargs__(self)

  對新式類來說,你可以通過這個方法改變類在反pickle時傳遞給__new__ 的引數。這個方法應該返回一個引數元組。

  __getstate__(self)

  你可以自定義物件被pickle時被儲存的狀態,而不使用物件的 __dict__ 屬性。 這個狀態在物件被反pickle時會被 __setstate__ 使用。

  __setstate__(self)

  當一個物件被反pickle時,如果定義了 __setstate__ ,物件的狀態會傳遞給這個魔法方法,而不是直接應用到物件的 __dict__ 屬性。這個魔法方法和 __getstate__ 相互依存:當這兩個方法都被定義時,你可以在Pickle時使用任何方法儲存物件的任何狀態。

  __reduce__(self)

  當定義擴充套件型別時(也就是使用Python的C語言API實現的型別),如果你想pickle它們,你必須告訴Python如何pickle它們。 __reduce__ 被定義之後,當物件被Pickle時就會被呼叫。它要麼返回一個代表全域性名稱的字串,Pyhton會查詢它並pickle,要麼返回一個元組。這個元組包含2到5個元素,其中包括:一個可呼叫的物件,用於重建物件時呼叫;一個引數元素,供那個可呼叫物件使用;被傳遞給 __setstate__ 的狀態(可選);一個產生被pickle的列表元素的迭代器(可選);一個產生被pickle的字典元素的迭代器(可選);

  __reduce_ex__(self)

  __reduce_ex__ 的存在是為了相容性。如果它被定義,在pickle時 __reduce_ex__ 會代替 __reduce__ 被呼叫。 __reduce__ 也可以被定義,用於不支援 __reduce_ex__ 的舊版pickle的API呼叫。

  13.3. 一個例子

  我們的例子是 Slate ,它會記住它的值曾經是什麼,以及那些值是什麼時候賦給它的。然而 每次被pickle時它都會變成空白,因為當前的值不會被儲存:

 

import time

class Slate:
    '''儲存一個字串和一個變更日誌的類
    每次被pickle都會忘記它當前的值'''

    def __init__(self, value):
        self.value = value
        self.last_change = time.asctime()
        self.history = {}

    def change(self, new_value):
        # 改變當前值,將上一個值記錄到歷史
        self.history[self.last_change] = self.value
        self.value = new_value)
        self.last_change = time.asctime()

    def print_change(self):
        print 'Changelog for Slate object:'
        for k,v in self.history.items():
            print '%s\t %s' % (k,v)

    def __getstate__(self):
        # 故意不返回self.value或self.last_change
        # 我們想在反pickle時得到一個空白的slate
        return self.history

    def __setstate__(self):
        # 使self.history = slate,last_change
        # 和value為未定義
        self.history = state
        self.value, self.last_change = None, None

  14. 總結

  這本指南的目標是使所有閱讀它的人都能有所收穫,無論他們有沒有使用Python或者進行面向物件程式設計的經驗。如果你剛剛開始學習Python,你會得到寶貴的基礎知識,瞭解如何寫出具有豐富特性的,優雅而且易用的類。如果你是中級的Python程式設計師,你或許能掌握一些新的概念和技巧,以及一些可以減少程式碼行數的好辦法。如果你是專家級別的Python愛好者你又重新複習了一遍某些可能已經忘掉的知識,也可能順便了解了一些新技巧。無論你的水平怎樣,我希望這趟遨遊Python特殊方法的旅行,真的對你產生了魔法般的效果(實在忍不住不說最後這個雙關)。