Confused about class relationships

Carl Banks pavlovevidence at gmail.com
Sat Nov 29 09:07:36 CET 2008


On Nov 26, 11:20 pm, John O'Hagan <resea... at johnohagan.com> wrote:
> Apologies if this is a D.Q., I'm still learning to use classes, and this
> little problem has proved too specific to find in the tutorials.
>
> I have two classes with a relationship that I find confusing.
>
> One is called Engine, and it has a method (bar_builder) which generates
> instances of the other class, called Bar (not as in Foo but as in bar of
> music; inherits from list).
>
> Also, Bar takes the generating instance of Engine as an argument to its
> __init__ method:
>
> class Bar(list):
>
>         def __init__(self, a_bar, args, engine):
>                 list.__init__ (self, a_bar)
>                 self[:] = a_bar        
>                 self.args = args
>                 self.engine = engine
>                 #more instance attributes...
>
>         #methods...
>
> class Engine:
>
>         def __init__(self, args):
>                 self.args = args                        
>                 #more instance attributes...
>
>         def bar_builder(self):
>                 #body of method generates lists...
>                 yield Bar([generated_list], args, self)
>
>         #more methods...
>
> #(other stuff...)
>
> def main(args):
>
>             engine = Engine(args)
>             bars = engine.bar_builder()
>             for a_bar in bars:
>                 #play the music!...
>
> While this works (to my surprise!) and solves the problem which motivated it
> (i.e. Engine instances need to pass some attributes to Bar instances ), it
> seems too convoluted. Should one class inherit the other?

No.  (I wonder if you meant, "Should one class reference the other?",
as in, "Should Engines only reference bars or Bars only reference
engines, but not both?", in which case the answer is a non-commital,
"It depends.")

Inheritance is not something you do because object A wants to access
the attributes/properties/etc. of B.  Actually, that is something that
happens pretty often and when it does, there's a couple things you
should think about:

1. How you want A to access the attributes of B.  Should you just let
A poke around in B, or should you define methods of B for A to call,
and those methods access the internals of B?  It's a judgment call
most of the time.

2. Whether some behavior of B ought to be defined as part of A's class
instead, or vice versa.  There is sometimes ambiguity over what class
a method that uses the attributes of two different objects should
belong to.

Inheritance isn't something you need to think about here.

Inheritance is something you consider when you have two objects, A and
B, that should act very similar or the same in certain situations.
Engines and Bars don't seem to share any behavior at all.  Contrast
this to Bars and lists, which do share behavior.  x[1] does pretty
much the same thing whether x is a Bar or a list, I'd imagine, so
there's reason for an inheritance relationship there.


> If so, which way
> around? Or is it fine as is?

One thing you might want to consider is whether Engine instance could
be simply passed into the Bar's play method (or whatever other method
accesses the Engine attributes), and whether the Engine needs to be
involved in generating bars at all.  For instance:


class Bar(list):
    def __init__(self, a_bar, args):
        list.__init__(self, a_bar)
        self[:] = a_bar
        self.args = args
        # etc.
    def play(self, engine):
        use(engine.time_signature).somehow()
        # instead of use(self.engine.time_signature).somehow()


def bar_builder(self,args):
    # plain function, not part of Engine class
    # or perhaps a method of some other class
    yield Bar([generated_list],args)


def main():
    engine = Engine(args)
    bars = bar_builder(self,args)
    for a_bar in bars:
        a_bar.play(engine)


One benefit of doing it this way is that you've freed Bars and Engines
from each other.  Conceivably you could create a second engine object
(maybe of a different type altogether), and play the very same Bar
objects with that engine instead.


> I'm hoping this is a common trap I've fallen into; I just haven't been able to
> get my head around it. (I'm a musician...)

Well I think it looks pretty good for a musician newbie.  There's a
few organizational decisions I'd make differently, but overall it's
clean and readable and not too complicated how you did it.  At least
not what you've showed us. :)


Carl Banks



More information about the Python-list mailing list