John O'Hagan wrote: > I want to know the best way to organise a bunch of functions designed to > operate on instances of a given class without cluttering the class itself > with a bunch of unrelated methods. > > What I've done is make what I think are called helper classes, each of > which are initialized with an instance of the main class and has methods > which are all of the same type (insofar as they return a boolean, or > modify the object in place, or whatever). > > I'm not sure if I'm on the right track here design-wise. Maybe this could > be better done with inheritance (not my forte), but my first thought is > that no, the helper classes (if that's what they are) are not actually a > type of the main class, but are auxiliary to it. > > Here's what I've done: > > I have a class MySequence which is initialized with a number sequence (in > a list), which has a bunch of methods which deal with various (musical) > properties of the sequence. Toy example: > > class MySequence(object): > """MySequence, a representation musical sequences as numbers. > Its methods return various characteristics of the sequence.""" > def __init__(self, sequence): > self.pitches = sequence[:] > def pcset(self): > """Example method: The pitch class set > derived from the sequence""" > return sorted(list(set([ i % 12 for i in self.pitches]))) > > A generator function spits out MySequence objects, and I want to filter > them (i.e. reject those which do not meet certain criteria) and then be > able to modify them in various ways. For that I have two classes; toy > examples: > > class SeqTest(object): > """SeqTest, initialized with a MySequence object. Its methods > return the boolean result of tests against the Sequence object.""" > def __init__(self, myseq_obj): > self.seq = myseq_obj > def degrees(self, degrees): > """Example method: Test for certain members, passed as list""" > return all(i in self.seq.pcset() for i in degrees) > > class SeqMod(object): > """A SeqMod object's methods modify in place > the MySequence object with which it is initialized """ > def __init__(self, myseq_obj): > self.seq = myseq_obj > def rotate(self, num): > """Example method: Rotate pitches by n steps""" > self.seq.pitches = self.seq.pitches[-num:] + > self.seq.pitches[:-num] > > > And here is a toy version of how I'm using them with the generator: > > def seq_factory(generator_func, test_opts, mod_opts): > """Yields Sequence objects, filtered and modified. > Opts are dictionaries.""" > for sequence in generator_func: > seq = MySequence(sequence) > tester = SeqTest(seq) > if any (not getattr(tester, opt)(value) > for opt, value in test_opts.items()): > continue > modifier = SeqMod(seq) > for opt, value in mod_opts.items(): > getattr(modifier, opt)(value) > yield seq > > > Used, say, like this: > > generator_func = (range(n, n+5) for n in range(5)) > test_opts = {'degrees': [5,7]} > mod_opts = {'rotate': 3} > > for i in seq_factory(generator_func, test_opts, mod_opts): > print i.pitches > > Which yields: > > [5, 6, 7, 3, 4] > [6, 7, 8, 4, 5] > > It actually works well, so there's no real problem apart from wanting to > know if this is a good way to do what I want. > > Thanks for any wise words,
As far as I can see the SeqMod and SeqTest classes don't keep any state apart from the sequence instance. Therefore functions instead of methods would do as well: from functools import partial class MySequence(object): def __init__(self, sequence): self.pitches = sequence[:] def pcset(self): return sorted(list(set([ i % 12 for i in self.pitches]))) def degrees(seq, degrees): return all(i in seq.pcset() for i in degrees) def rotate(seq, num): seq.pitches = seq.pitches[-num:] + seq.pitches[:-num] def seq_factory(sequences, tests, modifications): for seq in sequences: if all(test(seq) for test in tests): for modify in modifications: modify(seq) yield seq sequences = (MySequence(range(n, n+5)) for n in range(5)) tests = [ partial(degrees, degrees=[5,7]), # ... ] modifications = [ partial(rotate, num=3), # ... ] for i in seq_factory(sequences, tests, modifications): print i.pitches When you see that your module becomes too messy move the testing and modifying functions into separate modules that are part of the same package. Peter -- http://mail.python.org/mailman/listinfo/python-list