在很多的語言中,實例的屬性都有對應的實例變量與之對應,但在Python中,還可以使用其他的方式:
-
Properties:
即通過使用Python中內置方法property爲一個Attrbute名綁定對應的getter、setter、deletter方法,或者通過@property裝飾器,這樣,就可以直接通過變量名對實例變量進行訪問。
-
Descriptors:
一個描述器是一個具有綁定行爲的對象屬性,其訪問控制被描述器協議重寫。這些方法包括__get__()
,__set__()
, 和__delete__()
方法,只要重寫了這三個方法中的任何一個,就是實現了描述器協議。
1. 直接通過實例變量訪問
通常情況下,都是直接通過instance.variablename來對實例中的對象進行訪問。
例子1:
>>> class A(object):
... pass
...
>>> a = A()
>>> dir(a)
['__class__', '__delattr__', '__dict__', '__doc__',
'__format__', '__getattribute__', '__hash__', '__init__',
'__module__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__']
>>> a.__dict__
{}
>>> a.attr1 = 1
>>> dir(a)
['__class__', '__delattr__', '__dict__', '__doc__',
'__format__', '__getattribute__', '__hash__', '__init__',
'__module__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', 'attr1']
>>> a.__dict__
{'attr1': 1}
>>>
從上面的例子中,可以看出,直接對實例添加實例變量,是直接在實例中__dict__
添加的。因此,這種是直接進行訪問。
2. 通過Property
property是一種創建數據描述器的簡潔方式,使得在訪問屬性時,會對觸發對應的方法調用。
class C(object):
def getx(self):
print 'getx'
return self.__x
def setx(self, value):
print 'setx'
self.__x = value
def delx(self):
print 'delx'
del self.__x
x = property(getx, setx, delx, "I'm the 'x' property.")
instances = C()
instances.x = 5
print instances.x
del instances.x
output:-----
setx
getx
5
delx
在上面的代碼中,如果將x = property(getx, setx, delx, "I'm the 'x' property.")
換成x = property(fget=getx, fdel=delx, doc="I'm the 'x' property.")
,那麼調用instances.x會報錯AttributeError: can't set attribute
.
3. 通過Descriptors
Descriptor只對新式類有效
如果一個對象定義了__set__
和__get__
兩個方法,那可以成爲是一個data-descriptor,如只有__get__
,那麼被成爲non-data descriptor。在data-descriptor中,訪問屬性時,優先使用data-descriptor,而在non-data descriptor中優先使用的是字典中的屬性。
技巧: 如果想做一個只讀的descriptor,那麼就可以同時定義
__get__()
and__set__()
, 其中 調用__set__()
時拋出AttributeError異常即可。
3.1 調用順序
__getattribute__()
- data-descriptor
- 實例字典
- non-data descriptor
-
__getattr__
,處理查詢不到的屬性。
這裏可以做很多很多文章。後面進行整理。
data-descriptor 查詢順序:類-基類-實例字典
non-data descriptor 查詢順序:實例字典-類-基類
3.2 示例
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
重點:
- descriptors方法被
__getattribute__()
方法調用,每次訪問都會進行調用__getattribute__()
方法。 - 重寫
__getattribute__()
可以阻止系統子自帶的descriptor調用 -
__getattribute__()
只能被新式的類和實例有效,對於類和實例,調用方法object.__getattribute__()
andtype.__getattribute__()
時,對調用__get__()
方法是不一樣的. - data descriptors優先於實例字典。
- non-data descriptors 可能被實例字典重寫,因爲實例字典的優先級總是高於non-data descriptors。
- descriptor的實例自己訪問自己是不會觸發get,而會觸發call,只有descriptor作爲其它類的屬性纔有意義。
參考文檔