python的__get__、__set__、__delete__(1)

內容:
    描述符引導
        摘要
        定義和介紹
        描述符協議
        調用描述符
        樣例
        Properties
        函數和方法
        靜態方法和類方法
        
    
摘要
    定義並展現如何調用描述符,展現自定義描述符和幾個內置的python描述符,包括函數、屬性、靜態方法和類方法,經過給出一個Python的示例應用來展現描述符是如何工做的.
    熟練掌握描述符不只讓你擁有python使用的額外技巧,而且能夠加深對Python內部如何工做的理解,提高對程序設計的能力,並且體會到python的設計優雅之處

定義和介紹
    通常來講,描述符是帶有「綁定行爲」的對象屬性,它的屬性訪問已經被描述符協議中的方法覆蓋了.這些方法是__get__(),__set__(),和__delete__().
    若是一個對象定義了這些方法中的任何一個,它就是一個描述符.

    默認的屬相訪問是從對象的字典中 get, set, 或者 delete 屬性,;例如a.x的查找順序是:
    a.x -> a.__dict__['x'] -> type(a).__dict__['x'] -> type(a)的基類(不包括元類),若是查找的值是對象定義的描述方法之一,python可能會調用描述符方法來重載默認行爲,
    發生在這個查找環節的哪裏取決於定義了哪些描述符方法
    注意,只有在新式類中描述符纔會起做用(新式類繼承type或者object class)
    描述符是強有力的通用協議,屬性、方法、靜態方法、類方法和super()背後使用的就是這個機制,描述符簡化了底層的c代碼,併爲Python編程提供了一組靈活的新工具


描述符協議

html

    descr.__get__(self, obj, type=None) -> value

    descr.__set__(self, obj, value) -> None

    descr.__delete__(self, obj) -> None


    定義任何上面三個方法的任意一個,這個對象就會被認爲是一個描述符,而且能夠在被做爲對象屬性時重載默認的行爲, 若是一個對象定義了__get__() 和 __set__(),它被認爲是一個數據描述符.只定義 __get__()被認爲是非數據描述符,數據和非數據描述符的區別在於:若是一個實例的字典有和數據描述符同名的屬性,那麼數據描述符會被優先使用,若是一個實例的字典實現了無數據描述符的定義,那麼這個字典中的屬性會被優先使用,實現只讀數據描述符,同時定義__get__()和__set__(),在__set__()中拋出AttributeError.

描述符調用

    描述符能夠直接用方法名稱調用,好比:d.__get__(obj)
    
    然而,描述符更經常使用的方式是屬性訪問時被自動調用,例如:obj.d 在obj的字典中查找d,若是d定義了方法__get__(),而後d.__get__(obj)會被經過下面的優先級列表調用
    詳細的調用依賴於obj是一個對象仍是一個類,無論哪一種方式,描述符只工做在新式對象和類,若是一個類是object的子類(繼承object),這個類就是一個新式類
    
    對於對象來講,object.__getattribute__() 把b.x 變爲 type(b).__dict__['x'].__get__(b, type(b)) .優先級順序:
    數據描述符 > 實例變量 > 非數據描述符,__getattr__()具備最低優先級(若是實現了的話),C語言的實現能夠在 Objects/object.c 中 PyObject_GenericGetAttr() 查看.
    
    對於類來講,type.__getattribute__() 把 B.x 變爲 B.__dict__['x'].__get__(None, B),代碼實現爲:
    python

        def __getattribute__(self, key):
            "Emulate type_getattro() in Objects/typeobject.c"
            v = object.__getattribute__(self, key)
            if hasattr(v, '__get__'):
                return v.__get__(None, self)
            return v


    重點:
        描述符被__getattribute()方法調用
        重載__getattribute__()會阻止描述符自動調用
        __getattribute__()只適用於新式類和對象
        object.__getattribute__()和type.__getattribute__()對__get__()的調用不同
        數據描述符會重載實例字典
        非數據描述符可能會被實例字典重載
        
     super()返回的對象會使用定製__getattribute__()方法來調用描述符,調用super(B, obj).m() 會在緊鄰着B的基類A搜索obj.__class__.__mro__而後返回A.__dict__['m'].__get__(obj, B),若是不是一個描述符,返回未改變的m
     若是不在字典中,m會調用 object.__getattribute__() 查詢

     注意:在python2.2,若是m是一個數據描述符,super(B, obj).m() 會調用__get__(),在python2.3,無數據描述符也會執行調用,除非是個舊式類,super_getattro() 的細節在Objects/typeobject.c中
     
     上面展現的是描述符在object, type, and super() 的 __getattribute__() 方法中的實現機制,繼承object的類自動實現或者他們有一個元類提供相似的功能,一樣,重載 __getattribute__()能夠中止描述符的調用
     
描述符例子
    下面的代碼建立了一個類,每次訪問get或者set都會打印一條信息.重載__getattribute__()也可使每一個屬性實現這一方法,然而,描述符在查看特定的屬性時比較有用程序員

        class RevealAccess(object):
            """A data descriptor that sets and returns values
               normally and prints a message logging their access.
            """

            def __init__(self, initval=None, name='var'):
                self.val = initval
                self.name = name

            def __get__(self, obj, objtype):
                print 'Retrieving', self.name
                return self.val

            def __set__(self, obj, val):
                print 'Updating', self.name
                self.val = val

        >>> class MyClass(object):
        ...     x = RevealAccess(10, 'var "x"')
        ...     y = 5
        ...
        >>> m = MyClass()
        >>> m.x
        Retrieving var "x"
        10
        >>> m.x = 20
        Updating var "x"
        >>> m.x
        Retrieving var "x"
        20
        >>> m.y
        5

    這個協議很簡單卻又能夠提供使人爲之一振的可能性.Properties, bound 和 unbound methods, 靜態方法和 類方法 都是基於描述符協議

Properties
    調用property()是一種創建數據描述符的方便方法,能夠在訪問一個屬性的時候觸發方法的調用
    property(fget=None, fset=None, fdel=None, doc=None) -> property attribute

    下面展現一個定義管理屬性x的典型的樣例:編程

        class C(object):
            def getx(self):return self.__x
            def setx(self, value):self.__x = value
            def delx(self):del self.__x
            x = property(getx, setx, delx, "I'm the 'x' property.")

    property()使用純python方式實現描述符:函數

        class Property(object):
            "Emulate PyProperty_Type() in Objects/descrobject.c"

            def __init__(self, fget=None, fset=None, fdel=None, doc=None):
                self.fget = fget
                self.fset = fset
                self.fdel = fdel
                if doc is None and fget is not None:
                    doc = fget.__doc__
                self.__doc__ = doc

            def __get__(self, obj, objtype=None):
                if obj is None:
                    return self
                if self.fget is None:
                    raise AttributeError("unreadable attribute")
                return self.fget(obj)

            def __set__(self, obj, value):
                if self.fset is None:
                    raise AttributeError("can't set attribute")
                self.fset(obj, value)

            def __delete__(self, obj):
                if self.fdel is None:
                    raise AttributeError("can't delete attribute")
                self.fdel(obj)

            def getter(self, fget):
                return type(self)(fget, self.fset, self.fdel, self.__doc__)

            def setter(self, fset):
                return type(self)(self.fget, fset, self.fdel, self.__doc__)

            def deleter(self, fdel):
                return type(self)(self.fget, self.fset, fdel, self.__doc__)

 
    當用戶接口已經受權訪問屬性,這時候需求發生變化,property()能夠提供便利, 例如,一個電子表格類能夠經過單元('b10')授予對單元格值的訪問權.這時候,對程序的後續改進要求在每次訪問時從新計算單元格的值;然而,程序員不但願影響現有客戶端代碼.解決方案是在屬性數據描述符中封裝對value屬性的訪問:工具

        class Cell(object):
            . . .
            def getvalue(self):
                "Recalculate the cell before returning value"
                self.recalc()
                return self._value
            value = property(getvalue)


函數和方法
    python的面向對象是創建在函數的基礎上,使用非數據描述符,二者會結合的很是緊密.

    類的字典將方法比做函數存儲.在一個類的定義中,使用def和lambda來聲明方法,這是用於建立函數的經常使用工具. 惟一不一樣之處,就是第一個參數用來表示對象實例,python約定,實例引用可使self或者this或者其餘變量名稱

    爲了支持方法調用,函數經過__get__()方法來實現屬性訪問時的方法綁定
    這說明全部的函數都是非數據描述符,它返回綁定或者非綁定方法依賴於它被對象仍是類調用
    
    在python中的實現以下:

this

        class Function(object):
            . . .
            def __get__(self, obj, objtype=None):
                "Simulate func_descr_get() in Objects/funcobject.c"
                return types.MethodType(self, obj, objtype)


    在解釋器中展現函數描述符如何運行:spa

        >>> class D(object):
        ...     def f(self, x):
        ...         return x
        ...
        >>> d = D()
        >>> D.__dict__['f']  # Stored internally as a function
        <function f at 0x00C45070>
        >>> D.f              # Get from a class becomes an unbound method
        <unbound method D.f>
        >>> d.f              # Get from an instance becomes a bound method
        <bound method D.f of <__main__.D object at 0x00B18C90>>


    輸出說明綁定和未綁定方法是兩種不一樣類型,PyMethod_Type在 Objects/classobject.c 中實際的C實現是一個具備有兩種不一樣表現形式的單一對象,依賴於im_self是set仍是null(等價C中的None)

    一樣,調用方法對象的效果依賴於im_self,若是set(綁定),原函數(存儲在im_func中)被調用,它的第一個參數設置爲實例.
    若是unbound,全部的參數不作改變的傳給原函數,instancemethod_call()的C實現由於包含一些類型檢查會複雜一些

靜態方法和類方法
    無數據描述符提供一種簡單的機制將函數綁定爲方法

    簡單地說,函數的__get__()方法會將函數被做爲屬性訪問時轉換爲方法,非數據描述符將 obj.f(*args) 調用爲f(obj, *args).調用 klass.f(*args)變爲f(*args)

    下面的表格彙總了綁定和它常見的兩種變化

        Transformation    Called from an Object    Called from a Class
        function          f(obj, *args)                 f(*args)
        staticmethod     f(*args)                   f(*args)
        classmethod        f(type(obj), *args)            f(klass, *args)

    調用c.f 或者 C.f等價於 object.__getattribute__(c, "f") 或者 object.__getattribute__(C, "f"),無論從對象仍是類中,這個函數均可以訪問到

    不須要self變量的方法適合使用靜態方法
    例如:一個統計包可能包括用於實驗數據的容器類,該類提供了用於計算依賴於數據的平均、平均值、中值和其餘描述性統計數據的常規方法.但是可能有一些概念相關但不依賴數據的函數.
    例如:erf(x)是一個不依賴於特定數據集的函數,它能夠從一個類或者函數調用:s.erf(1.5) --> .9332 或者 Sample.erf(1.5) --> .9332

    靜態方法返回原始函數:翻譯

        >>> class E(object):
        ...     def f(x):
        ...         print x
        ...     f = staticmethod(f)
        ...
        >>> print E.f(3)
        3
        >>> print E().f(3)
        3


    python版本使用非數據描述符的實現方法:設計

        class StaticMethod(object):
            "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

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

            def __get__(self, obj, objtype=None):
                return self.f

 


    與靜態方法不一樣,類方法在調用函數以前先將類的引用預添加到參數列表中.調用者不論是對象仍是類,這種格式是相同的

        >>> class E(object):
        ...     def f(x):
        ...         print x
        ...     f = staticmethod(f)
        ...
        >>> print E.f(3)
        3
        >>> print E().f(3)
        3


    這種行爲在函數只須要有類引用且不關心任何底層數據的狀況下是有用的,類方法的一個用途是用來建立不一樣的類構造器,在python2.3中,類方法dict.fromkeys()可使用一個key的列表來建立字典,python的實現方式:

        class Dict(object):
            . . .
            def fromkeys(klass, iterable, value=None):
                "Emulate dict_fromkeys() in Objects/dictobject.c"
                d = klass()
                for key in iterable:
                    d[key] = value
                return d
            fromkeys = classmethod(fromkeys)

 

    如今能夠這樣建立一個字典:

        >>> Dict.fromkeys('abracadabra')
        {'a':None, 'r':None, 'b':None, 'c':None, 'd':None}


    classmethod()使用無數據描述符協議實現:

        class ClassMethod(object):
            "Emulate PyClassMethod_Type() in Objects/funcobject.c"

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

            def __get__(self, obj, klass=None):
                if klass is None:
                    klass = type(obj)
                def newfunc(*args):
                    return self.f(klass, *args)
                return newfunc

 

本文翻譯原文地址:https://docs.python.org/2/howto/descriptor.html#id9

相關文章
相關標籤/搜索