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 ;) -- http://mail.python.org/mailman/listinfo/python-list