python 描述符解析

什麼是描述符

python描述符是一個「綁定行爲」的對象屬性,在描述符協議中,它能夠經過方法重寫屬性的訪問。這些方法有 __get__(), __set__(), 和__delete__()。若是這些方法中的任何一個被定義在一個對象中,這個對象就是一個描述符。html

描述符的調用

描述符做爲屬性訪問是被自動調用的。python

對於類屬性描述符對象,使用type.__getattribute__,它能把Class.x轉換成Class.__dict__['x'].__get__(None, Class)。
對於實例屬性描述符對象,使用object.__getattribute__,它能把object.x轉換爲type(object).__dict__['x'].__get__(object, type(object))。ssh

描述符講解

下面咱們具體經過實例來詳細說明描述符的使用code

先定義一個描述符orm

class RevealAccess(object):

    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

上面實現了__get__和__set__。因此這是一個描述符對象。並且是一個數據描述符對象,非數據描述符對象只實現__get__方法。這2者之間有一些區別,下面會講到。htm

再定義一個調用描述符對象的類對象

class MyClass(object):
    x = RevealAccess(10, 'var "x"')
    y = 5

print MyClass.x

訪問 MyClass.x 輸出繼承

Retrieving var "x"
10

發現訪問x會去調用描述符的__get__方法。這就達到了描述符的做用,能夠改變對象屬性的訪問,使用描述符的方法。由於若是解析器發現x是一個描述符的話,其實在內部是經過type.__getattribute__(),它能把MyClass.x轉換爲MyClass.__dict__[「x」].__get__(None,MyClass)來訪問。ip

print MyClass.__dict__["x"].__get__(None, MyClass)
# 輸出
Retrieving var "x"
10

描述符的對象定義爲類屬性,若是定義成對象屬性會有什麼不一樣嗎?下面咱們試驗一下get

class MyClass(object):

    x = RevealAccess(10, 'var "x"')

    def __init__(self):
        self.y = RevealAccess(11, 'var "y"')

print type(MyClass.x)
# 輸出
"""
Retrieving var "x"
<type 'int'>;
"""
test = MyClass()
print test.y
# 輸出 
"""
<__main__.RevealAccess object at 0x1004da410>;
"""

從上面的輸出,能夠看到訪問類屬性的確調用了描述符的__get__方法,看到輸出的結果是int類型。而調用實例屬性並無訪問__get__方法。而是直接返回描述符的實例對象。之因此是這樣是由於當訪問一個實例描述符對象時,object.__getattribute__會將test.y轉換爲type(test).__dict__[‘y’].__get__(test,type(test))
而MyClass類中沒有「y」屬性,因此沒法訪調用到_get__方法,這裏會有一個判斷的過程。但這個實例對象仍然是一個描述符對象。因此最好定義描述符對象爲類屬性。固然不是不能夠定義爲實例屬性,請看下面

當定義的類屬性描述符對象和實例屬性有相同的名字時

class MyClass(object):
     
    x = RevealAccess(10, 'var "x"')

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

而後調用

test = MyClass(100)
print test.x
# 輸出
"""
Updating var "x"
Retrieving var "x"
100
"""

可見依然調用了描述符的方法。按照常理,應該訪問 test.__dict__['x'],而後是type(test).__dict__['x']。因爲咱們定義了實例屬性x。應該只輸出100。可這裏從輸出結果看的的確確的訪問了描述符的方法。那麼這是爲何呢?

其實這裏主要是由於當python發現實例對象的字典中有與定義的描述符有相同名字的對象時,描述符優先,會覆蓋掉實例屬性。python會改寫默認的行爲,去調用描述符的方法來代替。咱們能夠輸出類和實例對象的字典看看

test = MyClass(100)
print test.__dict__
"""
輸出 {}
"""
print MyClass.__dict__
"""
輸出 {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'MyClass' objects>, 
'x': <__main__.RevealAccess object at 0x1004da350>, 
'__weakref__': <attribute '__weakref__' of 'MyClass' objects>, 
'__doc__': None, '__init__': <function __init__ at 0x1004cce60>}
"""

從輸出中發現實例對象的字典中根本就沒有x對象,即便咱們在類中定義了self.x。而類的字典中則有x描述符對象。這主要就是由於描述符優先。

上面咱們定義的描述符有__get__和__set__2個方法,因此是一個數據描述符,非數據描述符只有一個__get__方法,一般用於方法。此外,非數據描述符的優先級低於實例屬性。下面看一個例子,咱們去掉__set__方法。

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

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

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

class MyClass(object):
    x = RevealAccess(10, 'var "x"')

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

test = MyClass(100)
print test.x
「」「
100
「」「
print test.__dict__
「」「
{'x': 100}
「」「
print MyClass.__dict__
「」「
{'__module__': '__main__', '__dict__': &lt;attribute '__dict__' of 'MyClass' objects&gt;,
'x': <;__main__.RevealAccess object at 0x1005da310>, 
'__weakref__': <attribute '__weakref__' of 'MyClass' objects>, 
'__doc__': None, '__init__': <function __init__ at 0x1005ccd70>}
「」「
print MyClass.x
"""
Retrieving var "x"
10
"""

從上面的輸出,能夠看出非數據描述符不會覆蓋掉實例屬性。並且優先級比實例屬性低。這也是和數據描述符的一個區別。

綜上所述,對於描述符的調用有如下幾點須要注意

  1. 描述符被 getattribute 方法調用

  2. 覆蓋__getattribute__會讓描述符沒法自動調用

  3. 描述符只適用於新式類,即繼承object的類

  4. object . getattribute 和 type . getattribute 調用__get__方法不同

  5. 數據描述符優先於實例的字典,對於相同名字的會覆蓋

  6. 實例的字典優先於非數據描述符。但不會覆蓋。

  7. 對於數據描述符,python中property就是一個典型的應用。

對於非數據描述符,其主要用於方法。如靜態方法和類方法。看源碼能夠看到只實現了描述符協議中的__get__方法,而沒有實現__set__和__del__。

以下面這樣模擬靜態方法

class StaticMethod(object):
    def __init__(self, f):
        self.f = f

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

class MyClass(object):

    @StaticMethod
    def get_x(x):
        print("static")
        return x

print MyClass.get_x(100)
"""
static
100
「」「

調用MyClass.get_x(100)至關於

MyClass.__dict__["get_x"].__get__(None, MyClass)(100)

咱們知道在python中,一切皆是對象。每個定義的方法其實都是一個對象。在這裏咱們能夠經過dir()查看每個方法裏的屬性和方法。看下面

class Desc(object):
    def test1(self):
        print("test1")

def test2():
    print("test2")
print(dir(test2))
"""輸出太長不貼了,但從輸出中能夠看到有__get__"""
print(dir(Desc.test1))
"""
['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__func__',
 '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', 
'__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', 
'__str__', '__subclasshook__', 'im_class', 'im_func', 'im_self']
"""

從dir的輸出,能夠看到,每一個方法對象都包含一個__get__方法。所以能夠說每個方法都是一個非數據描述符。一般咱們經過點操做符調用方法時,內部都是調用這個__get__方法。

參考 https://docs.python.org/2.7/h...

以上就是本人對描述符的一些理解,有什麼不正確的地方還請不吝指出,謝謝!

相關文章
相關標籤/搜索