Automagical attribute dependency tracking.
This module implements a framework for dependency-driven computation for
object attributes. You simply inherit from `Invalidator` and wrap functions
for computing attributes with `autodep` instead of `property`. The value of
such an attribute is cached and deleted when any attribute it depends on is
set or deleted. Moreover, the dependencies are automagically detected by
noticing which attributes you accessed from your function.
This module works as designed but I feel it could be designed better...
||Mixin class supporting attribute dependencies.|
Setting or deleting any attribute triggers a check for
`self._Invalidator__target_map[attr]`. If there it has an entry for this
attribute, the value should be an iterable of attribute names which are
all deleted (which can cause a cascade of deletions).
If `self._Invalidator__active` is a non-empty list, the last value is an
attribute which is currently being computed by an `autodep` property.
While this is so, getting any attribute records a dependency from it to
the active attribute.
Note that this scheme can miss a lot of other dependencies, most notably
any mutation of other objects, even if they are attributes of `self`,
isn't detected as long as you don't change thier identity; it's your
responsibility to make them visible to the system, e.g. by setting and
accessing phony attributes.
||Methods defined here:|
- __delattr__(self, attr)
- __getattribute__(self, attr)
- __setattr__(self, attr, value)
Data and other attributes defined here:
- __dict__ = <dictproxy object>
- dictionary for instance variables (if defined)
- __weakref__ = <attribute '__weakref__' of 'Invalidator' objects>
- list of weak references to the object (if defined)
- autodep(compute, name=None)
- Wrap a function as a descriptor with caching and dependency detection.
`compute` should be a funtion receiving self and returning some value.
`autodep()` returns a descriptor that can be used in a subclass of
`Invalidator` for computing an attribute. The name of the attribute must
be known; it's assumed to be `compute.__name__` and you can override it by
providing the `name` argument.
When the attribute will be accessed, `compute()` will be called once and
it's value stored as `obj.__dict__[attr]`; thus it'll be re-used by
subsequent accesses, until deleted. It can be deleted by virtue of
`Invalidator` when any attribute it depends on is set or deleted. While
it runs, any accessed attributes are recorded as its dependencies.
- Chained dependencies are handled correctly.
- Cyclic dependencies might or might not work, untested.