Python descriptor的理解


相对于decorator, iterator, generator, metaclass 这些概念,descriptor是python所有概念中最难理解的一个了。尽管python documentation对其描述不多(基本上是一笔带过),以至于很多人忽略了这个概念。事实上,自己以前也忽略了它,只不过最近重温python时无意间发现了他,觉得非常有必要深入研究它。
自己的理解是建立在前人的基础之上的。不过,网上关于descriptor的资料实在是有限,下面是自己认为有参考价值的几个链接:
1.http://users.rcn.com/python/download/Descriptor.htm       (最主要的参考资料)
2.http://www.python.org/peps/pep-0252.html                  (太难理解了)
3.http://www.python.org/~jeremy/weblog/030425.html
4.http://www.python.org/download/releases/2.2.2/descrintro/
5.Python25 documentation (Python Reference Manual, Chapter 3.4.2)
6.Python25的源代码
1.假如"x"是普通属性。
  每个class均有一个__dict__,该class的obj也有一个__dict__。
  1)对于obj.x,优先查找obj.__dict__["x"],如果找不到,再查找class.__dict__["x"];
  2)对于class.x,直接查找class.__dict__["x"];  
  3)obj.__dict__与class.__dict__可以拥有同名的属性;
  4)obj.x=10,将无条件的更新obj.__dict__;class.x=10,将无条件的更新class.__dict__。
class C(object):
x = 10
b = C()  #b.__dict__不包含"x",C.__dict__包含"x"
b.x  #10
C.x  #10
b.x=100  #b.__dict__也包含一个"x",对应的值为100;C.__dict__对应的值还是10
b.x  #100
C.x  #10
t=C()
t.x  #10
C.x=99  #C.__dict__["x"]变成了99
t.x
b.x
C.x
2.假如"x"是一个descriptor
  为简化,假设obj.__dict__没有"x"项。
class RevealAccess(object):
def __init__(self, initval, name):
  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 C(object):
x = RevealAccess(10,"A test variable")
>>> b = C()    # case1
>>> b.x
Retrieving A test variable
10
>>> C.x
Retrieving A test variable
10
>>>
>>> b.x=100    # case2
Updating A test variable
>>> b.x
Retrieving A test variable
100
>>> C.x
Retrieving A test variable
100
>>>
>>> C.x=1000    # case3
>>>
>>> b.x
1000
>>> C.x
1000
>>>     # case4
>>> C.x = RevealAccess(999,"The second var")
>>> b.x
Retrieving The second var
999
>>> C.x
Retrieving The second var
999
>>>
上述结果可以通过Python25 Documentation (Python Reference Manual, Chapter 3.4.2.2)解释:
The following methods only apply when an instance of the class containing the method (a so-called descriptor class) appears in the class dictionary of another new-style class, known as the owner class. In the examples below, ``the attribute'' refers to the attribute whose name is the key of the property in the owner class' __dict__. Descriptors can only be implemented as new-style classes themselves.
1)__get__( self, instance, owner)
Called to get the attribute of the owner class (class attribute access) or of an instance of that class (instance attribute access). owner is always the owner class, while instance is the instance that the attribute was accessed through, or None when the attribute is accessed through the owner. This method should return the (computed) attribute value or raise an AttributeError exception.
(注意:obj.x、class.x均会导致该调用)
2)__set__( self, instance, value)
Called to set the attribute on an instance instance of the owner class to a new value, value.
(注意:仅obj.x=ttt均会导致该调用;class.x=ttt直接绑定到另外一个对象上)
3)__delete__( self, instance)
Called to delete the attribute on an instance instance of the owner class.
为简化以及方面理解,在实际应用中,尽量避免obj.__dict__与class.__dict__拥有同名的属性。
3.假如"x"是一个descriptor,而且obj.__dict__也有一个"x"项。非常复杂。
For instance bindings, the precedence of descriptor invocation depends on the which descriptor methods are defined. Data descriptors define both __get__() and __set__(). Non-data descriptors have just the __get__() method. Data descriptors always override a redefinition in an instance dictionary. In contrast, non-data descriptors can be overridden by instances.
详细的描述还得参考:
http://users.rcn.com/python/download/Descriptor.htm
1)从该文档可以知道,descriptor与__getattribute__密切相关。基础类型object、type均定义了__getattribute__,并有缺省实现。上面所说的descriptor的各种特性其实就是这些__getattribute__的行为。也就是说,如果自己重新定义了__getattribute__,那么这个函数具有最高的优先级。
2)另外,__getattr__/__setattr__/__delattr__也与属性的取值有关,见Python25 documentation (Python Reference Manual, Chapter 3.4.2)。它们的级别比较低。建议它们所对应的属性不要为descriptor。
class RevealAccess(object):
def __init__(self, initval, name):
  self.val = initval
  self.name = name

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

class C(object):
x = RevealAccess(10,"A test variable")
def __init__(self,val):
  self.x = val
>>> b = C(100)
>>>
>>> C.x
Retrieving A test variable
10
>>> b.x
100
>>>
4.根据
http://users.rcn.com/python/download/Descriptor.htm
的描述,descriptor更多的被python自己用来实现一些语言上的特性。比如:function/property/staticmethod/classmethod/均是通过descriptor实现的。