1. 程式人生 > >Python面向對象-魔術方法

Python面向對象-魔術方法

錯誤 名稱 args exception 引入 per ttr 用處 dir()

實例化相關

對象的實例化過程如下所示:

技術分享圖片

示例:

class Programer():
    def __new__(cls,*args,**kwargs):
        print('call __new__ method')
        print(args)
        return super(Programer,cls).__new__(cls)

    def __init__(self,name,age):
        print('call __init__ method')
        self.name = name
        self
.age = age if __name__ == '__main__': programer = Programer('Albert',25) print(programer.__dict__)

運行結果:

call __new__ method
('Albert', 25)
call __init__ method
{'name': 'Albert', 'age': 25}

在這個過程中:

  • __new__(cls[, ...])
    • __new__是在一個對象實例化的時候所調用的第一個方法
    • 它的第一個參數是這個類,其他的參數是用來直接傳遞給 __init__ 方法
    • __new__ 決定是否要使用該 __init__ 方法,因為 __new__ 可以調用其他類的構造方法或者直接返回別的實例對象來作為本類的實例,如果 __new__ 沒有返回實例對象,則 __init__ 不會被調用
    • __new__ 主要是用於繼承一個不可變的類型比如一個 tuple 或者 string
  • __init__(self[, ...])
    • 構造器,當一個實例被創建的時候調用的初始化方法

運算相關

  • 比較操作符
    • __lt__(self, other) 定義小於號的行為:x < y 調用 x.__lt__(y)

    • __le__(self, other) 定義小於等於號的行為:x <= y 調用 x.__le__(y)
    • __eq__(self, other) 定義等於號的行為:x == y 調用 x.__eq__(y)
    • __ne__(self, other) 定義不等號的行為:x != y 調用 x.__ne__(y)
    • __gt__(self, other) 定義大於號的行為:x > y 調用 x.__gt__(y)
    • __ge__(self, other) 定義大於等於號的行為:x >= y 調用 x.__ge__(y)
  • 算數運算符
    • __add__(self, other) 定義加法的行為:+
    • __sub__(self, other) 定義減法的行為:-
    • __mul__(self, other) 定義乘法的行為:*
    • __truediv__(self, other) 定義真除法的行為:/
    • __floordiv__(self, other) 定義整數除法的行為://
    • __mod__(self, other) 定義取模算法的行為:%
    • __divmod__(self, other) 定義當被 divmod() 調用時的行為
    • __pow__(self, other[, modulo]) 定義當被 power() 調用或 ** 運算時的行為
    • __lshift__(self, other) 定義按位左移位的行為:<<
    • __rshift__(self, other) 定義按位右移位的行為:>>
    • __and__(self, other) 定義按位與操作的行為:&
    • __xor__(self, other) 定義按位異或操作的行為:^
    • __or__(self, other) 定義按位或操作的行為:\|

示例:

class Programer(object):
    def __init__(self,name,age):
        self.name = name
        if isinstance(age,int):
            self.age = age
        else:
            raise Exception('age must be int')

    def __eq__(self,other):
        if isinstance(other,Programer):        # 首先判斷是否Programer對象
            if self.age == other.age:
                return True
            else:
                return False
        else:
            raise Exception('The type of object must be Programer')

    def __add__(self,other):
        if isinstance(other,Programer):
            return self.age + other.age
        else:
            raise Exception('The type of object must be Programer')

if __name__ == '__main__':
    p1 = Programer('Albert',25)
    p2 = Programer('Bill',30)
    print(p1==p2)
    print(p1+p2)

運行結果:

False
55

展現相關

  • __str__
    • 把對象轉換成適合人看的字符串
  • __repr__
    • 把對象轉換成適合機器看的字符串,即可以使用eval函數運行
  • __dir__(self)
    • 定義當 dir() 被調用時的行為

示例:

class Programer(object):
    def __init__(self,name,age):
        self.name = name
        if isinstance(age,int):
            self.age = age
        else:
            raise Exception('age must be int')

    def __str__(self):
        return '%s is %s years old'%(self.name,self.age)

    def __dir__(self):
        return self.__dict__.keys()

if __name__ == '__main__':
    p = Programer('Albert',25)
    print(p)
    print(dir(p))

運行結果:

Albert is 25 years old
['age', 'name']

屬性控制相關

設置對象屬性

  • __setattr__(self, name, value)

獲取對象屬性

  • __getattr__(self, name)
    • 當查詢不到時才會調用
  • __getattribute__(self, name)
    • 每次訪問時一定會被調到

刪除對象屬性

  • __del__(self)

限制添加屬性

  • __slots__

    • 由於Python是動態語言,任何實例在運行期都可以動態地添加屬性。

      如果要限制添加的屬性,例如,Student類只允許添加 namegenderscore 這3個屬性,就可以利用Python的一個特殊的__slots__來實現。

      顧名思義,__slots__是指一個類允許的屬性列表:

示例1

class Programer(object):
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def __getattribute__(self,name):
        # return getattr(self,name) # 會產生無限遞歸
        # 調用父類的getattribute方法,不會產生無限遞歸
        return super(Programer,self).__getattribute__(name) 

    def __setattr__(self,name,value):
        # setattr(self,name,value) # 會產生無限遞歸
        self.__dict__[name] = value

if __name__ == '__main__':
    p = Programer('Albert',25)
    print(p.name)

運行結果:

Albert

示例2

class Student(object):
    __slots__ = ('name', 'gender', 'score')
    def __init__(self, name, gender, score):
        self.name = name
        self.gender = gender
        self.score = score

現在,對實例進行操作:

>>> s = Student('Bob', 'male', 59)
>>> s.name = 'Tim' # OK
>>> s.score = 99 # OK
>>> s.grade = 'A'
Traceback (most recent call last):
  ...
AttributeError: 'Student' object has no attribute 'grade'

__slots__的目的是限制當前類所能擁有的屬性,如果不需要添加任意動態的屬性,使用__slots__也能節省內存。

示例3

假設Person類通過__slots__定義了namegender,請在派生類Student中通過__slots__繼續添加score的定義,使Student類可以實現namegenderscore3個屬性。

Student類的__slots__只需要包含Person類不包含的score屬性即可。

參考代碼:

class Person(object):
    __slots__ = ('name', 'gender')
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

class Student(Person):
    __slots__ = ('score',)
    def __init__(self, name, gender, score):
        super(Student, self).__init__(name, gender)
        self.score = score

s = Student('Bob', 'male', 59)
s.name = 'Tim'
s.score = 99
print s.score

容器相關

  • len__(self)

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

  • __getitem__(self, key)

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

  • __setitem__(self, key)

    定義對容器中某一項使用 self[key] 的方式進行賦值操作時的行為。它是可變容器類型必須實現的一個方法,同樣應該在合適的時候產生 KeyErrorTypeError 異常。

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

示例:

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 , OrderedDictNamedTuple

上下文相關

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_valuetraceback 會是 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()方法時的處理)。

復制相關

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

  • __copy__(self)

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

  • __deepcopy__(self, memodict=)

    定義對類的實例使用 copy.deepcopy() 時的行為。 copy.deepcopy() 返回一個對象的深拷貝,這個對象和它的數據全都被拷貝了一份。 memodict 是一個先前拷貝對象的緩存,它優化了拷貝過程,而且可以防止拷貝遞歸數據結構時產生無限遞歸。當你想深拷貝一個單獨的屬性時,在那個屬性上調用 copy.deepcopy() ,使用 memodict 作為第一個參數。

這些魔法方法有什麽用武之地呢?像往常一樣,當你需要比默認行為更加精確的控制時。例如,如果你想拷貝一個對象,其中存儲了一個字典作為緩存(可能會很大),拷貝緩存可能是沒有意義的。如果這個緩存可以在內存中被不同實例共享,那麽它就應該被共享。

持久化相關

Pickle不僅僅可以用於內建類型,任何遵守pickle協議的類都可以被picklePickle協議有四個可選方法,可以讓類自定義它們的行為(這和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)

    當定義擴展類型時(也就是使用PythonC語言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調用。

示例

我們的例子是 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

Python面向對象-魔術方法