Adding modified methods from another class without subclassing

John O'Hagan research at johnohagan.com
Mon Aug 22 16:20:09 CEST 2011


On Mon, 22 Aug 2011 11:32:18 +0200
Peter Otten <__peter__ at web.de> wrote:

> 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?

"Now is better than never"?

I know it looks crazy to take something apart and put it back together, as in this simplified example, but it does have a meaningful use: reducing a series of musical notes to a unique irreducible form "__pc", ("prime form" in pitch-class set theory), but keeping track of the actual current manifestation of that form via the writeable "order" attribute. That's why "pitches" must be called with each reference, because it may change, unlike "__pc", which can only change if the instance is reinitialised.

I am open to more elegant suggestions, of course. :)
[...]

> > 
> > 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():
> > .....

[...]

>
> 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 ;)


It looks to me like that total_ordering bug is a result of using comparison operators on objects which may not implement them; I'm pretty sure the listmeth_to_attribute function above can only allow comparing lists with lists or raising an exception. Although I must admit that this creates an unexpected bug/feature whereby lists and MySeqs can be successfully compared, so I take your point! 

Thanks for your reply,

John



More information about the Python-list mailing list