Adding modified methods from another class without subclassing

Peter Otten __peter__ at web.de
Mon Aug 22 05:32:18 EDT 2011


John O'Hagan wrote:

> I have a class like this:
> 
> class MySeq():
>     def __init__(self, *seq, c=12):
>         self.__c = c
>         self.__pc = sorted(set([i % __c for i in seq]))
>         self.order = ([[self.__pc.index(i % __c), i // __c] for i in seq])
>         #other calculated attributes
> 
>     @property
>     def pitches(self):
>         return [self.__pc[i[0]] + i[1] * self.__c for i in self.order]
> 
>     #other methods

That makes me dizzy. Are there any maxims in the Zen of Python that this 
piece doesn't violate?

> The "pitches" attribute initially reconstructs the "seq" arguments but can
> be modified by writing to the "order" attribute.
> 
> The "pitches" attribute represents the instances and as such I found
> myself adding a lot of methods like:
> 
> 
> def __getitem__(self, index):
>     return self.pitches[index]
> 
> def __len__(self):
>     return len(self.pitches)
> 
> def __iter__(self):
>     return iter(self.pitches)
>     
> def __repr__(self):
> return str(self.pitches)
> 
> and so on, and calling a lot of list methods on the "pitches" attribute of
> MySeq instances elsewhere. I thought of making MySeq a subclass of list
> with "pitches" as its contents, but then I would have had to override a
> lot of methods case-by-case, for example to ensure that any alterations to
> "pitches" were reflected in the other calculated attributes.
> 
> So I wrote this function which takes a method, modifies it to apply to an
> instance attribute, and takes care of any quirks:
> 
> def listmeth_to_attribute(meth, attr):
>     def new_meth(inst, *args):
>         #ensure comparison operators work:
>         args = [getattr(i, attr)  if isinstance(i, inst.__class__)
>             else i for i in args]
>         reference = getattr(inst, attr)
>         test = reference[:]
>         result = meth(test, *args)
>         #ensure instance is reinitialised
>         #if attribute has been changed:
>         if test != reference:
>             inst.__init__(*test)
>         #ensure slices are of same class
>         if isinstance(result, meth.__objclass__):
>             result = inst.__class__(*result)
>         return result
>     return new_meth
> 
> and this decorator to apply this function to all the list methods and add
> them to MySeq:
> 
> def add_mod_methods(source_cls, modfunc, modfunc_args, *overrides):
>     """Overides = any methods in target to override from source"""
>     def decorator(target_cls):
>         for name, meth in vars(source_cls).items():
>             if name not in dir(target_cls) or name in overrides:
>                 setattr(target_cls, name, modfunc(meth, *modfunc_args))
>         return target_cls
>     return decorator
> 
> a kind of DIY single inheritance, used like this:
> 
> @add_mod_methods(list, listmeth_to_attribute, ('pitches',), '__repr__')
> class MySeq():
> .....
> 
> Now I can call list methods transparently on MySeq instances, like
> subclassing but without all the overriding. If this works it will simplify
> a lot of code in my project.
> 
> But the fact that I haven't seen this approach before increases the
> likelihood it may not be a good idea. I can almost hear the screams of
> "No, don't do that!" or the sound of me slapping my forehead when someone
> says "Why don't you just...". So before I put it in, I'd appreciate any
> comments, warnings, criticisms, alternatives etc..

In the standard library functools.total_ordering uses that technique and I 
find the implications hard to understand:

http://bugs.python.org/issue10042

Your decorator looks even more complex; I'd only recommend using it if 
you're absolutely sure you're clever enough to debug it ;)




More information about the Python-list mailing list