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