Attribute descriptor with lazy initialization and caching
A Descriptor (or subclass) instance is a Python attribute descriptor,
similar to the ones created using the Python property built-in type.
However, where property instances invoke a function on every access to
the attribute, binding.Descriptor instances only call their
computeValue() method once, caching the result in the containing object's
dictionary. Thereafter, the value in the dictionary is reused, instead of
calling the function each time. If the attribute is deleted (e.g. via
del anObj.anAttr ) or the value is cleared from the dictionary, the
computeValue() method will be called again, the next time that the
attribute is retrieved.
Because the instance dictionary is used to store the attribute value, a
Descriptor needs to know its name, so that it knows what key to store its
value under. Unfortunately, Python does not provide a way for descriptors
to know what name(s) they are accessible under in a class, so you must
explicitly provide an attrName value, either as a constructor keyword
argument, or by defining it in a subclass. Many Descriptor subclasses
have the ability to guess or detect what attrName should be used, but you
must still explicitly provide it if they are unable to do so automatically.
Failure to supply a correct attrName will result in a TypeError when
the attribute is used.
Descriptor instances have the following attributes which may be set by
keyword arguments to the class' constructor:
-
attrName
- sets the name which the descriptor will use to get/set
its value in the instance dictionary. Ordinarily this should be the
same as the name given to the descriptor in its containing class, but
you may occasionally wish it to be something else.
-
computeValue(obj, instDict, attrName)
- a function that will be
called whenever the attribute is accessed and no value for the
attribute is present in the object's instance dictionary. The function
will be passed three arguments: the object that owns the attribute, the
object's instance dictionary, and the descriptor's
attrName . The value returned will be treated as the attribute's value. It will
also be cached in the object's instance dictionary to avoid repeat
calls, unless the descriptor's noCache attribute is True .
Note that the default implementation of computeValue() simply raises
an AttributeError . Also note that the call signature of
computeValue() is the same as that of binding.IRecipe . That is,
any object that provides IRecipe may be used as a computeValue
attribute. Descriptor does not perform any adaptation, however, so
if your desired object must be adapted to IRecipe , you should do so
before assigning it to the computeValue attribute.
-
noCache
- if set to
True , the descriptor will not cache its
computeValue() results in the owning object's instance dictionary.
This makes the descriptor's behavior more similar to the Python
property type. But, computeValue() will not be called when the
attribute is currently set (i.e., a value is in the object's instance
dictionary under attrName ), so the behavior is still quite different
from the property type.
-
onSet(obj, attrName, value)
- a function that will be called
whenever the attribute is set, or when the result of
computeValue()
is to be cached. The function will be passed three arguments: the
object that owns the attribute, the descriptor's attrName , and the
value that is to be set. The function may return the passed-in
value, or change it by returning a different value. To prevent the
value from being set, the function should raise an exception. If you
do not set this attribute, its default implementation simply returns
the value unmodified.
-
ofClass(attrName, klass)
- a function that will be called whenever
the descriptor is retrieved from its class. The default implementation
simply returns the descriptor.
-
permissionNeeded
- a
peak.security.IAbstractPermission , or None .
This is a convenience feature for use with the peak.security package.
You can override the methods described above in subclasses. Keep in mind,
however, that you will then need to add a self parameter in addition to
the ones described above. The self argument will refer to the
Descriptor instance.
The Descriptor class is rarely used directly; normally you will use one
of its subclasses, which have many additional features for more convenient
use. However, many of those subclasses themselves use Descriptor
instances as part of their definition. That's why this exists as a
separate base class: to make it possible to use descriptors as part of the
definition of descriptor classes.
Methods
|
|
__init__
|
|
__init__
|
__init__ ( self, **kw )
Create a descriptor from keyword arguments
You may pass any keyword arguments, as long as they represent existing
attributes defined by the Descriptor class (or subclass, if
this constructor is used by the subclass).
|
|