Confused about class relationships

J

John O'Hagan

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? If so, which way
around? Or is it fine as is?

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...)


John O'Hagan
 
B

bieffe62

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? If so, which way
around? Or is it fine as is?

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...)

John O'Hagan


if you need the engine to generate a (potentially) infinite sequence
of bars, this approach seems the most linear
you could do ...
Inheritance is for classes which share attribute/methods, which is not
the case ...

then, there are always many way to to the same thing, and sometime
they are equivalent but for the
programmer taste ...

Ciao
 
T

Tim Roberts

John O'Hagan said:
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? If so, which way
around? Or is it fine as is?

You need to put on a philosophical hat for this. Inheritance is used for
an "is-a" relationship, where one thing "is-a" special type of another
thing. Is "Engine" a special type of "Bar"? Is "Bar" a generic type of
"Engine"? I think the answer is no, and because of that inheritance is not
the right answer.
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...)

Why do you think this is a trap? Engine creates Bars. Bars need to know
about the Engine. I think you have expressed that relationship properly.

When I have these kinds of philosophical debates over my own code, I always
try to summarize them in the comments inside the module, just so others can
get "inside my head" to understand the thinking that led to my design.
 
C

Carl Banks

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
 
J

John O'Hagan

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.

Thanks for such a detailed reply...I actually had bar_builder as a plain
function originally, but because I didn't know how to pass attributes around
like you have above, I was using globals to do it and was trying to solve
that by classing everything in sight. Although the program doesn't work quite
as you have deduced above, I can use the approach you suggest to simplify it
immensely. (I hope.)
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. :)
[...]
Believe me, the whole thing is more complicated and messy, but you don't want
to see my dirty laundry! :)

Regards,

john
 
C

Craig Allen

what you have is a totally acceptable factory system. Not sure why
you are using a generator, but that's another matter.

I agree with the previous replies regarding inheritance... this is not
a case for inheritance. You could, however, have Bar be a borg with
the Bar factory built in as a class method to the Bar class itself if
you want the whole job done by one component. Questions to lead to or
away from that: will Engine every create things besides Bars?
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top