Python魔法方法(一)

構造方法

我們最爲熟知的基本的魔法方法就是 __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

操作符

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

if instance.equals(other_instance):
    # do something

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

if instance == other_instance:
    # do something

這是魔法力量的一部分,這樣我們就可以創建一個像內建類型那樣的對象了!

比較操作符

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 放在類的定義之上就可以了

數值操作符

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

一元操作符

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

  • __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() 函數,即距離零最近的整數。

常見算數操作符

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

  • __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)

實現按位異或運算符 ^ 。

反射算數運算符

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

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)

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

增強賦值運算符

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)

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

類型轉換操作符

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 ,這兩者均已被轉換成相同的類型。

發佈了21 篇原創文章 · 獲贊 9 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章