How do I do this without class methods ?

Alex Martelli aleaxit at yahoo.com
Wed May 2 18:15:50 CEST 2001


"Jacek Generowicz" <jmg at ecs.soton.ac.uk> wrote in message
news:g0lmofre79.fsf at scumbag.ecs.soton.ac.uk...
> Jacek Generowicz <jmg at ecs.soton.ac.uk> writes:
>
> > (Full version performing to spec. below . . .)
> >
> > -----------------------------------------------------------
> >
> > class woderwick:
> >     bwian = [ 'zero', 'one', 'two', 'many' ]
> >     _wodger_index = 0 # default
> >     def __getattr__ ( self, name ):
> >         if name == 'wodger':
> >             return self.bwian[self._wodger_index]
>
> What should I do if the operation which is to be performed here is
> very expensive, and hence should only be performed once and cached for
> future use ?

__getattr__ will only be invoked for attributes whose names
are *NOT* found otherwise, so if you change the __getattr__
code to set the attribute in the instance it won't be invoked
again automatically.  This, of course, means an object's
"wodger" attribute is defined by the setting of _wodger_index
*at the time it's first queried*, which may or may not be the
semantics you want -- such are always the joys and sorrows of
caching.  You may need to keep watch on situations where the
caches need to be invalidated, etc, etc.


> I've invented a different example, which more closely reflects what I
> need. Unfortuanately, with all the alternatives and comments, it has
> turned out a little longer that I would like . . . sorry.
>
>
> import math
>
> # (A hierarchy of) objects whose behaviour changes as a function of a
> # globally settable parameter
> class polygon:
>     def __init__ ( self, size ):
>         self.radius = size
>
>     # Called once for each parameter setting, used by all instances
>     # Hence it would seem natural to implement it as a CLASS method
>     def set_class_cache ( self, N ):
>         # Imagine that this is an expensive operation, and hence
>         # should only be called once for a given choice of N
>         # Consequently, I can't use the trick of using __getattr__ to
>         # calculate the value each time.

No, but you *can* find yourself computing it only at the FIRST query,
as above.  Since math.sin, math.pi, and N, aren't all that subject
to change, you may not in fact have the caching worries whereof we
spoke above.

>         self.__class__.unit_area = N * math.sin( 2 * math.pi / N )
>
>     # Called once for each instance, at a particular parameter setting
>     def set_instance_cache ( self ):
>         # Imagine that this is expensive too;
>         # so calculate it only once for each instance
>         # after the paremeter has been set
>         self.area = self.radius * self.unit_area

Again, you may, if you wish, perform this "just in time", by setting
the instance's self.area in the __getattr__ for attribute name 'area'.
Python will ensure this __getattr__ is not needlessly called twice on
any given instance, if the first time the attribute name->value
correspondence IS recorded in the instance's __dict__.


>     # Called frequently for any particular instance
>     def use_instance ( self, coefficient ):
>         # Called many times for each instance
>         # should use the instance's cached value
>         return self.area * coefficient

With the __getattr__-setting-the-thing strategy, you may code
method use_instance() this way and (all times except the
first one) it will just fetch self.area quite fast.


> # Almost elegant but horribly slow solution: getattr
> # Slow because unit_area is recalculated repeatedly
> # but (almost) never changes
> # Inelegant, because this is a daft place to put the calculation of
> # unit_area
> def ga ( object, name ):
>     if name == 'unit_area':
>         return  object.N * math.sin( 2 * math.pi / object.N )

Change to:
      if name == 'unit_area':
          setattr(object, 'unit_area', object._really_compute_unit_area() )

Elegant AND fast, no?  As long as __getattr__ knows what names of
attributes need to be computed the first time, and what method of
an object computes the attribute for it and is to be called that
one first time, it just oversees the name -> computation delegation
(ONCE).  It also works for class-attributes, by setattr'ing to
object.__class__, of course.

If you need this often, you may want to keep a per-class mapping of
special-attribute-name -> function-computing-it-to-be-called-once,
and have __getattr__ (e.g. from a mixin class) just rely on that
mapping to organize ('oversee') the computations.  Data-driven
programming IS often simpler.

> Class methods would make this all so simple :-)

How would they further simplify the solution approach I suggest?
Simplicity is always worth pursuing, of course.


Alex






More information about the Python-list mailing list