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

Reply via email to