python 描述符解析
什么是描述符
python描述符是一個“綁定行為”的對象屬性,在描述符協議中,它可以通過方法重寫屬性的訪問。這些方法有 __get__(), __set__(), 和__delete__()。如果這些方法中的任何一個被定義在一個對象中,這個對象就是一個描述符。
描述符的調用
描述符作為屬性訪問是被自動調用的。
對于類屬性描述符對象,使用type.__getattribute__,它能把Class.x轉換成Class.__dict__[‘x’].__get__(None, Class)。
對于實例屬性描述符對象,使用object.__getattribute__,它能把object.x轉換為type(object).__dict__[‘x’].__get__(object, type(object))。
描述符講解
下面我們具體通過實例來詳細說明描述符的使用
先定義一個描述符
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者之間有一些區別,下面會講到。
再定義一個調用描述符對象的類
class MyClass(object):
x = RevealAccess(10, 'var "x"')
y = 5
printMyClass.x
訪問 MyClass.x 輸出
Retrievingvar "x"
10
發現訪問x會去調用描述符的__get__方法。這就達到了描述符的作用,可以改變對象屬性的訪問,使用描述符的方法。因為如果解析器發現x是一個描述符的話,其實在內部是通過type.__getattribute__(),它能把MyClass.x轉換為 MyClass.__dict__[“x”].__get__(None,MyClass) 來訪問。
printMyClass.dict["x"].get(None, MyClass)
輸出
Retrievingvar "x"
10
</code></pre>
描述符的對象定義為類屬性,如果定義成對象屬性會有什么不同嗎?下面我們試驗一下
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>;
"""
</code></pre>
從上面的輸出,可以看到訪問類屬性的確調用了描述符的__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)
printtest.x
輸出
"""
Updating var "x"
Retrieving var "x"
100
"""
</code></pre>
可見依然調用了描述符的方法。按照常理,應該訪問 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__': <attribute '__dict__' of 'MyClass' objects>,
'x': <;__main__.RevealAccessobject at 0x1005da310>,
'__weakref__': <attribute '__weakref__' of 'MyClass' objects>,
'__doc__': None, '__init__': <function__init__at 0x1005ccd70>}
“”“
print MyClass.x
"""
Retrieving var "x"
10
"""
從上面的輸出,可以看出非數據描述符不會覆蓋掉實例屬性。而且優先級比實例屬性低。這也是和數據描述符的一個區別。
綜上所述,對于描述符的調用有以下幾點需要注意
- 描述符被 getattribute 方法調用
- 覆蓋__getattribute__會讓描述符無法自動調用
- 描述符只適用于新式類,即繼承object的類
- object . getattribute 和 type . getattribute 調用__get__方法不一樣
- 數據描述符優先于實例的字典,對于相同名字的會覆蓋
- 實例的字典優先于非數據描述符。但不會覆蓋。
- 對于數據描述符,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
defget_x(x):
print("static")
return x
printMyClass.get_x(100)
"""
static
100
“”“
調用MyClass.get_x(100)相當于
MyClass.__dict__["get_x"].__get__(None, MyClass)(100)
我們知道在python中,一切皆是對象。每一個定義的方法其實都是一個對象。在這里我們可以通過dir()查看每一個方法里的屬性和方法。看下面
class Desc(object):
deftest1(self):
print("test1")
deftest2():
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__方法。
來自:http://python.jobbole.com/86953/