原文地址:Descriptor HowTo Guide
描述符定义
一般来说,描述符是一个具有“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