Descriptors
Table of Contents
Overview
descr.__get__(self, obj, type=None) # -> value
descr.__set__(self, obj, value) # -> None
descr.__delete__(self, obj) # -> None
from weakref import WeakKeyDictionary
class Grade(object):
def __init__(self):
self._values = WeakKeyDictionary()
def __get__(self, instance, instance_type):
if instance is None:
return self
else:
return self._values.get(instance, 0)
def __set__(self, instance, value):
# Do something special
self._values[instance] = value
- Descriptors are invoked by the
__getattribute__()
method - Overriding
__getattribute__()
prevents automatic descriptor calls __getattribute__()
is only available with new style classes and objectsobject.__getattribute__()
andtype.__getattribute__()
make different calls to__get__()
.
Functions and Methods discussion
For classes, method definitions are just functions. For instances, these functions need to be bound to the instance. To support method calls, functions include the __get__()
method for binding methods during attribute access.
class Function(object):
. . .
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
return types.MethodType(self, obj, objtype)
Data descriptors vs. non-data descriptors discussion
- Data descriptor: defines both
__get__()
and__set__()
- Non-data descriptor: defines only
__get__()
- Data descriptors always override instance dictionaries.
- Non-data descriptors may be overridden by instance dictionaries.
- Following example shows that data descriptor(
@property
) takes precedence over non-data descriptor(Descriptor
)
>>> class Descriptor(object):
... def __init__(self, name):
... self.name = name
... def __get__(self, instance, cls):
... print 'Getting %s, with instance %r, class %r' % (self.name, instance, cls)
...
>>> class Foo(object):
... _spam = 'eggs'
... @property
... def spam(self):
... return self._spam
... @spam.setter
... def spam(self, val):
... self._spam = val
...
>>> Foo().spam
'eggs'
>>> foo = Foo()
>>> foo.__dict__['spam'] = Descriptor('Override')
>>> foo.spam
'eggs'