J
John O'Hagan
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
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