Adding modified methods from another class without subclassing

John O'Hagan research at johnohagan.com
Mon Aug 22 07:04:45 CEST 2011


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

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

Regards,

John



More information about the Python-list mailing list