描述符定義
一般來說,描述符是一個具有“binding behavior”的對象屬性,它的屬性訪問被描述符協議里的方法重寫。這些方法是__get__()
,__set__()
,__delete__()
,如果這三個方法中任意一個被某個對象實現,那么該對象就被成為描述符。
屬性訪問的默認行為是從對象的詞典中get、set或者delete屬性。例如,a.x
則依次從a.__dict__['x']
、type(a).__dict__['x']
,type(a)
的除元類外的基類的__dict__
中查找。然后最終的查找結果是描述符的話,那么Python可能會重寫默認行為并調用描述符方法,這取決于定義了哪些描述符方法。Note that descriptors are only invoked for new style objects or classes (a class is new style if it inherits from object
or type
).
描述符是一個強大的通用協議,它們是屬性,方法,靜態方法,類方法和super()的機制。它們被用于整個Python本身,以實現版本2.2中引入的新風格類。描述符簡化了底層C代碼,為日常Python程序提供了一套靈活的新工具。
描述符協議
descr.__get__(self, obj, type=None) --> value
descr.__set__(self, obj, value) --> None
-
descr.__delete__(self, obj) --> None
這是描述符協議的全部,定義其中的任意一個就會被視為描述符并且當作為屬性被查找時會改變其默認行為。
如果一個對象定義了__get__()
和__set__()
那么它被稱為“data descriptor”,只定義__get__()
被稱為‘non-data descriptors’。
如果一個實例的__dict__
里有和‘data descriptor’同名的條目,那么描述符優先;如果實例的__dict__
里有和‘no-data descriptors’同名的方法,那么字典條目優先。
寫個例子試一下:class Desc(object): def __init__(self, x): self.x = x def __set__(self, obj, value): self.x = value def __get__(self, obj, type=None): print 'invoking __get__' return self.x class A(object): d = Desc(12) a = A() a.__dict__['d'] = 'dict_d' print a.__dict__ print a.d
執行后輸出:
{'d': 'dict_d'}
invoking __get__
12
如果把類Desc
的__set__()
注釋掉,那么它就從“data descriptor”變成了“no-data descriptor”。這樣再次執行一次,輸入:
{'d': 'dict_d'}
dict_d
同時定義__get__()
和__set__()
,然后使__set__()
方法拋出AttributeError
異常,這就成了只讀的描述符
調用描述符
描述符可以直接通過方法名調用,比如d.__get__(obj)
.
另外,更常見的是通過屬性訪問自動調用,比如:obj.b
則從obj
的字典中查找b
,如果b
定義了__get__()
方法,__get__(obj)
將會通過下面列出的優先級規則被調用。
調用的細節取決于obj
是對象還是類。無論是那種,但必須是新式對象或新式類(object的子類是新式類)。
如果是對象,該機制由object.__getattribute__()
實現,當b.x
時實際執行的是type(b).__dict__['x'].__get__(b, type(b))
。優先級是這樣的:"data descriptions" > 實例變量 > "no-data descriptions" > getattr_().
如果是類。該機制由type.__getattribute__()
實現,它將B.b
轉變為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__()
,__getattr__
,object
,type
)
重點是:
- 描述符由
__getattribute__()
調用 - 重寫
__getattribute__()
可以阻止描述符自動調用 -
__getattribute__()
只可以在新式類和對象中使用 -
object.__getattribute__()
andtype.__getattribute__()
make different calls to__get__()
. - “data descriptions”優先級永遠優于實例字典
- “non-data descriptors”可能被實例字典覆蓋
Properties
調用property()是一種簡潔的構建數據描述符的方法,可以在訪問屬性時觸發函數調用:property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
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.")
x
相當于一個描述符
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()適用于向用戶開放了讀權限,但修改值需要用方法來處理的時候。
class Cell(object):
. . .
def getvalue(self):
"Recalculate the cell before returning value"
self.recalc()
return self._value
value = property(getvalue)
然后就可以通過Cell("B2").value
來獲取值了。
還有這種裝飾器用法:
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value