Any new style class that implements at least one of those method is called a descriptor:
__get__(self, instance, owner=None) -> value,
__set__(self, instance, value) -> None,
__delete__(self, instance) -> None (descriptor protocol).
We can think of a descriptor as an agent that presents object attributes,
Descriptors which do not implement __set__ are called non-data descriptor or method descriptor, descriptors which implement both __get__, __set__ are called data descriptors.
The differences between data descriptor and non-data descriptor is "Data descriptors always override a redefinition in an instance dictionary. In contrast, non-data descriptor can be overridden by instances" which means "If an instance's dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence. If an instance's dictionary has an entry with the same name as a non-data descriptor, the dictionary entry takes precedence."
Properties, bound, unbound methods, static methods, class methods are all based on the descriptor protocol
2) Invoking descriptor
obj.x => if x is a descriptor and obj is a instance, then object.__getattribute__() translates obj.x into type(obj).__dict__['x'].__get__(obj, type(obj))
(1) type(obj) gets the class of this obj
(2) type(obj).__dict__['x'] is looking the attribute 'x' against the class of obj, it returns "x" descriptor
(3) type(obj).__dict__['x'].__get__(obj, type(obj)) is calling __get__ against the "x" descriptor
cls.x => if x is a descriptor and cls is a class, then type.__getattribute__() translates cls.x into cls.__dict__['x'].__get__(None, cls)
Pure python emulation
def __getattribute__(self, key):
v = object.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
return v
The important points are:
(1) descriptors are invoked by the __getattribute__ method
(2) overriding __getattribute__ prevents automatic descriptor calls
(3) __getattribute__ is only available with new style classes and objects
(4) object.__getattribute__ and type.__getattribute__ make different calls to __get__
(5) data descriptors always override instance dictionaries
(6) non-data descriptors may be overridden by instance dictionaries
The object returned by super() also has a custom __getattribute__ method for invoking descriptors. The call super(B,obj).m() searches obj.__class__.__mro__ for the base class A immediately following B and then returns A.__dict__['m'].__get__(obj, A). If not a descriptor, m is returned unchanged. If not in the dictionary, m reverts to a search using object.__getattribute__.
(3) Example:
Non-data descriptor:
---
class DefaultAlias(object):
def __init__(self, name):
self.name = name
def __get__(self, inst, cls):
if inst is None:
return self
return getattr(inst, self.name)
class Book(object):
def __init__(self, title, stitle=None):
self.title = title
if stitle is not None:
self.short_title = stitle
short_title = DefaultAlias('title')
b = Book('Ghost') # this book instance has a descriptor called short_title (Note "stitle" parameter is None)
b.short_title is translated into type(b).__dict__['short_title'].__get__(b, type(b))
(1) type(b) => Book (class)
(2) type(b).__dict__['short_title'] => Book.__dict__['short_title'] => Book.short_title (the short_title descriptor)
(3) type(b).__dict__['short_title'].__get__(b, type(b)) => Book.short_title.__get__(b, Book) =>('title') DefaultAlias.__get__(b, Book)
Precedence explanation:
---
b2 = Book('Ghost', 'G') # this book instance has a descriptor called short_title and a attribute called short_title (string) (Note "stitle" parameter is not None)
b2.short_title => 'G', attribute of an instance precedes the Non-data descriptor
class Alias(DefaultAlias):
def __set__(self, inst, value):
setattr(inst, self.name, value)
def __delete__(self, inst):
delattr(inst, self.name)
class Book(object):
def __init__(self, title, stitle=None):
self.title = title
if stitle is not None:
self.short_title = stitle
# If we uncomment out the following line, "self.short_title" in the above line (self.short_title = short_title)
# is actually the data descriptor in the following line.
#short_title = Alias('title')
b3 = Book('Ghost', 'G') # this book instance only has a attribute called short_title (string) (Note "stitle" parameter is not None)
b3.short_title => 'G'
Book.short_title = Alias('title') # assign the data descriptor which has the same name as the attribute "short_title", now we have both 'short_title' string attribute and data descriptor
b3.short_title => type(b3).__dict__['short_title'].__get__(b3, type(b3)) => 'Ghost', data descriptor precedes attribute of instance
b3.__dict__['short_title'] => 'G' # explicitly access the attribute 'short_title' of instance b3
(4) How property() implements the descriptor protocol
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
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)
---
backup contents, usage of "property"
class C(object):
def __init__(self):
self._x = None
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.")
ORclass C(object):
def __init__(self): self._x =None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
Reference:
http://users.rcn.com/python/download/Descriptor.htm