Helper classes design question

Discussion in 'Python' started by John O'Hagan, Aug 23, 2010.

  1. John O'Hagan

    John O'Hagan Guest

    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,

    John
     
    John O'Hagan, Aug 23, 2010
    #1
    1. Advertising

  2. John O'Hagan

    Peter Otten Guest

    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
     
    Peter Otten, Aug 23, 2010
    #2
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Simon Harvey
    Replies:
    4
    Views:
    562
    Jon A. Cruz
    Jul 22, 2003
  2. nospam
    Replies:
    8
    Views:
    409
    Roedy Green
    Aug 20, 2005
  3. Mark Buxbaum
    Replies:
    2
    Views:
    415
    David Rubin
    Jul 18, 2004
  4. Replies:
    6
    Views:
    427
    Default User
    Nov 29, 2006
  5. kevin
    Replies:
    1
    Views:
    382
    Kairi Zikpin
    Jul 21, 2006
Loading...

Share This Page