python magic methods

英文原文:http://www.rafekettler.com/magicmethods.html#conclusion

1.__new__ 是靜態方法(__new__ is a static method),實例化時以類作對象爲第一個參數(The first argument to __new__ must be a class; the remainingarguments are the arguments as seen by the constructor call.)調用__new__返回實例對象,然後再調用__init__對實例對象做進一步初始化;對於一些不可變對象的類(int,str,tuple),其__init__是無效的,要想在初始化過程中改變其值,需要override __new__方法。改寫__new__時還是需要調用父類的__new__方法的,只是我們可以在之前改變參數或者在之後改變返回的實例對象。改寫的__new__內部調用父類__new__方法時不可把父類作爲第一個參數傳入,否則會得到父類的實例對象,應該傳入新類(A __new__ method that overrides a base class's __new__ methodmay call that base class's __new__ method. The first argument to thebase class's __new__ method call should be the class argument to theoverriding __new__ method, not the base class; if you were to pass inthe base class, you would get an instance of the base class)。官網對__new__的描述非常詳細http://www.python.org/download/releases/2.2/descrintro/#__new__

2.__del__相當於析構函數(對應的,__new__和__init__就是構造函數了),但並不是在del x的時候調用,而是在對象被垃圾回收的時候調用。其作用主要是處理一些額外的清理工作,比如關閉socket和文件描述符。__del__最好別用,或謹慎使用,因爲不能保證__del__在什麼時候執行。最好的習慣是自己手動做必要的善後工作,比如連接不用了就自己去把它關掉,不要交給__del__去做。

3.用於比較的魔法函數,__cmp__,__eq__,__ne__,__lt__,__gt__,__le__,__ge__(__cmp__根據</==/>返回負數/0/整數)

4.__str__由內建函數str()調用,__repr__由內建函數repr()調用(前者主要給人看,後者主要給機器看);__unicode__由內建函數unicode()調用,跟str()類似,只是只是返回的是unicode string。

5.__hash__由內建函數hash()調用,必須返回整數,用於在字典中做快速的key比較;改寫該函數一般也要改寫__eq__,a==b會包含hash(a)==hash(b)。

6.__dir__由內建函數dir()調用,返回一個包含所有屬性的list。一般沒必要改寫,但是在改寫__getattr__/__getattribute__或者動態生成屬性的時候這個函數是很重要的。

7.屬性訪問的控制,從其他語言過來的人抱怨python沒有真正意義上類的封裝(沒法定義私有屬性;且可以隨意的設置和獲取屬性)。事實並非如此,python通過魔法方法實現類的封裝,而不是顯示的修改類的方法和字段。

    __getattr__(self,name),當訪問不存在的屬性時會調用該方法(不是真正的做類封裝的解決方案)
    __setattr__(self,name,value),不管屬性是否已經存在,你可以通過該方法定義/設置一個屬性的行爲。也就是說你可以定製自己的規則使其在類的屬性發生改變時發揮作用(確實是一種類的封裝)
    __delattr__,跟__setattr__類似,只不過它是刪除屬性,而非設置屬性。調用del self.name時在__delattr__的實現中可能會導致無窮遞歸,需謹慎
    __getattribute__(self, name),不推薦使用(用途很少且很難做到沒BUG),只能用於新風格的類(最新版本python裏面都是新風格的類)。當被顯式調用或者__getattribute__返回AttributeError時,__getattr__會被調用
def __setattr__(self, name, value):
    self.name = value
    # since every time an attribute is assigned, __setattr__() is called, this
    # is recursion.
    # so this really means self.__setattr__('name', value). Since the method
    # keeps calling itself, the recursion goes on forever causing a crash

def __setattr__(self, name, value):
    self.__dict__[name] = value # assigning to the dict of names in the class
    # define custom behavior here
    
class AccessCounter(object):
    '''A class that contains a value and implements an access counter.
    The counter increments each time the value is changed.'''

    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)
        # Make this unconditional.
        # If you want to prevent other attributes to be set, raise 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)]

8.定製序列:自定義不可變容器,只用定義__len__和__getitem__方法;自定義可變容器,需要定義__len__/__getitem__/__setitem__/__delitem__。如果需要具備可迭代的屬性,還要定義__iter__(__iter__返回迭代器,該迭代器必須遵守迭代協議,即,需要迭代器有__iter__和next方法)

    __getitem__(self,key),使用self[key]的時候被調用,不可變容器和可變容器都支持該方法

    __setitem__(self,key,value),使用self[key]=value的時候被調用,可變容器才支持該方法

    __delitem__(self,key),使用del self[key]的時候被調用,可變容器才支持該方法

    __iter__(self),返回一個容器的迭代器,有很多方式會觸發該方法調用,常見的是內建函數iter()和“for x in container:”語法。迭代器需要定義方法__inter__返回自身

    __reversed__(self),使用內建函數reversed()的時候被調用,返回一個逆序序列,實現該方法需要序列有序(如tuple和list)

    __contains__(self,item),使用“in”和“not in”語法的時候觸發該方法調用,如果不定義該方法python會迭代這個序列並在遇到目標(item)時候返回True

    __missing__(self,key),爲dict的子類使用,當請求的key不存在的時候會調用該方法

9.reflection,很少會用到這些,但是如果需要你會發現python給了你這個功能

    __instancecheck__(self,instance),使用isinstance(instance,class)的時候會觸發該函數調用

    __subclasscheck__(self,subclass),使用issubclass(subclass,class)的時候會觸發該函數調用

10.callable objects,python中函數也是一種對象

    __call__(self, [args...]),該方法允許一個類的實例對象可以像函數一樣被調用。x()等價於x.__call__(),參數隨意,但是把實例對象當函數調用的時候參數個數必須跟__call__定義時一致;如果在某個class中重複定義了多個__call__方法,最後一個會覆蓋之前的,不論參數形式如何(python中沒有C++裏面那種函數名相同參數形式不同的重載)

11.Context Managers,上下文管理器,當對象的創建被with語法包裝時,上下文管理器可以對對象做設置和清理的動作(contextlib模塊有類似功能)

    __enter__(self),with語法控制的代碼塊執行前會調用被with包裝的對象的__enter__方法,該方法的返回值被綁定到“with語法的目標”上或者“with語法中as後面的變量”上

    __exit__(self,exception_type,exception_value,traceback),with控制的代碼塊執行完畢或終止的時候調用對象的__eixt__方法,該方法用於處理異常、執行清理、或者其他在with代碼塊之後需要做的事情。如果代碼塊執行成功,exception_type、exception_value和traceback是None。否則,你可以選擇處理exception或者丟出去讓用戶處理它:如果你要處理它應該保證處理完以後讓__exit__返回True,如果你不想讓exception被上下文管理器__exit__處理,就什麼也別管讓它去吧

class Closer:
    '''A context manager to automatically close an object with a close method
    in a with statement.'''

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

    def __enter__(self):
        return self.obj # bound to target

    def __exit__(self, exception_type, exception_val, trace):
        try:
           self.obj.close()
        except AttributeError: # obj isn't closable
           print 'Not closable.'
           return True # exception handled successfully
           
>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
...     conn.dir()
...
# output omitted for brevity
>>> conn.dir()
# long AttributeError message, can't use a connection that's closed
>>> with Closer(int(5)) as i:
...     i += 1
...
Not closable.
>>> i
6
12.Building Descriptor Objects

    __get__(self, instance, owner)
    __set__(self, instance, value)
    __delete__(self, instance)

http://onlypython.group.iteye.com/group/wiki/1362-python-39-s-descriptor <descriptor講解>

descriptor到底是什麼呢:簡單的說,descriptor是對象的一個屬性,只不過它存在於類的__dict__中並且有特殊方法__get__(可能還有__set__和__delete)而具有一點特別的功能,爲了方便指代這樣的屬性,我們給它起了個名字叫descriptor屬性

data descriptor和non-data descriptor:同時具有__get__和__set__方法,這樣的descriptor叫做data descriptor,如果只有__get__方法,則叫做non-data descriptor

屬性查找策略,對於obj.attr(注意:obj可以是一個類):

    a> 如果attr是一個Python自動產生的屬性(屬性可以分爲兩類,一類是Python自動產生的,如__class__,__hash__等,另一類是我們自定義的,如上面的hello,name。類和實例對象都有__dict__屬性,裏面存放它們的自定義屬性(對於類,裏面還存放了別的東西)),找到!(優先級非常高!)

    b> 查找obj.__class__.__dict__,如果attr存在並且是data descriptor,返回data descriptor的__get__方法的結果,如果沒有繼續在obj.__class__的父類以及祖先類中尋找data descriptor

    c> 在obj.__dict__中查找,這一步分兩種情況,第一種情況是obj是一個普通實例,找到就直接返回,找不到進行下一步。第二種情況是obj是一個類,依次在obj和它的父類、祖先類的__dict__中查找,如果找到一個descriptor就返回descriptor的__get__方法的結果,否則直接返回attr。如果沒有找到,進行下一步

    d> 在obj.__class__.__dict__中查找,如果找到了一個descriptor(插一句:這裏的descriptor一定是non-data descriptor,如果它是data descriptor,第二步就找到它了)descriptor的__get__方法的結果。如果找到一個普通屬性,直接返回屬性值。如果沒找到,進行下一步

    e> 很不幸,Python終於受不了。在這一步,它raise AttributeError

對屬性賦值時的查找策略 ,對於obj.attr = value:

    a> 查找obj.__class__.__dict__,如果attr存在並且是一個data descriptor,調用attr的__set__方法,結束。如果不存在,會繼續到obj.__class__的父類和祖先類中查找,找到 data descriptor則調用其__set__方法。沒找到則進入下一步

    b> 直接在obj.__dict__中加入obj.__dict__['attr'] = value   

13.Copying

    __copy__(self),對某實例對象調用copy.copy()時會觸發該實例的__copy__方法調用。淺拷貝,實例自身是新實例,但是它的所有數據都是對原實例的數據的引用,修改新實例的數據可能會引起原始實例的數據變化

    __deepcopy__(self, memodict={}),對某實例對象調用copy.deepcopy()時會觸發該實例的__deepcopy__方法調用。深拷貝,實例自身和它的數據都是新的。memodict是之前已拷貝對象的緩存,當拷貝遞歸數據結構時它可以優化拷貝和阻止無盡遞歸。當你要深拷貝一個單獨的屬性時,對該屬性調用copy.deepcopy()並把memodict作爲第一個參數(如果要實際使用的話還得深入研究一下~)

14.Pickling Your Objects

    Pickling是指對python的數據結構做序列化處理,可以存儲和重新加載一個對象(多用於緩存)

    Pickling不是內建類型。它對於任何遵守pickle協議的類都支持。pickle協議由四個可選方法來定製pickling行爲(之前的一篇文章提到了其中兩個方法的使用,http://blog.csdn.net/xiarendeniao/article/details/6774520 item52)

    __getinitargs__(self)
    __getnewargs__(self)
    __getstate__(self)
    __setstate__(self, state)
    __reduce__(self)
    __reduce_ex__(self)
import time
class Slate:
    '''Class to store a string and a changelog, and forget its value when
    pickled.'''

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

    def change(self, new_value):
        # Change the value. Commit last value to history
        self.history[self.last_change] = self.value
        self.value = new_value
        self.last_change = time.asctime()

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

    def __getstate__(self):
        # Deliberately do not return self.value or self.last_change.
        # We want to have a "blank slate" when we unpickle.
        return self.history

    def __setstate__(self, state):
        # Make self.history = state and last_change and value undefined
        self.history = state
        self.value, self.last_change = None, None

15.哪些語法會觸發魔法方法?

   
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章