Inheritance Question

J

Jackson

I've got an inheritance question and was hoping brighter minds could
guide me. I am in the strange situation where some of the methods in a
subclass are actually more general than methods in a superclass. What
is the preferred way to handle such situations. My original thought was
to do something like this:

class AA(object):
def general_method(): pass

class A(AA):
# redefine general_method() to call a
# restricted version of AA.general_method()

class B(A,AA):
# redefine general_method() to call AA.general_method()

This seems ugly to me, and I am wondering if there is a better method.
So any suggestions would be appreciated.


Thanks!






-----------

For a more "concrete" example:

Suppose all the animals in the world have only 1 or 2 legs.

class Legs(object)
def run(): pass
def walk(number_of_legs):
# lots of commands
# that do not depend on the
# number of legs but definitely
# have to do with walking

if number_of_legs == '1':
# blah blah

if number_of_legs == '2':
# blah blah

# more commands

class HasAtLeastOneLeg(Legs):
def walk():
# Legs.walk(number_of_legs=1)

class HasTwoLegs(HasAtLeastOneLeg,Legs):
def walk()
# Legs.walk(number_of_legs=2)

# isinstance(HasTwoLegs, HasAtLeastOneLeg) --> True
 
G

Gabriel Genellina

I've got an inheritance question and was hoping brighter minds could
guide me. I am in the strange situation where some of the methods in a
subclass are actually more general than methods in a superclass. What
is the preferred way to handle such situations. My original thought was
to do something like this:

class AA(object):
def general_method(): pass

class A(AA):
# redefine general_method() to call a
# restricted version of AA.general_method()

class B(A,AA):
# redefine general_method() to call AA.general_method()

This seems ugly to me, and I am wondering if there is a better method.
So any suggestions would be appreciated.

(Note that even using your design, B doesn't have to inherit from
both A and AA, just inheriting from A is enough to be able to call
AA.general_method)

It's hard to tell in this abstract terms, but maybe you should
consider whether really B "is an" A, and A "is an" AA.
Other kind of relationships are possible, like delegation ("behaves
like") or the strategy pattern ("now behaves like...", at runtime).
For a more "concrete" example:

Ahhhh! I didn't notice this when I read your post.
Suppose all the animals in the world have only 1 or 2 legs.

I would not consider walk a method of Legs, but of Animal. An Animal
"has" Legs; it may have OneLeg or TwoLegs. An Animal walks "using" its legs.

class Animal:
def __init__(self, number_of_legs):
# an animal has legs
self.legs = CreateLegs(self, number_of_legs)

def walk(self):
# an animal uses its legs to walk
self.legs.walk()

class Legs:
def walk():
raise NotImplementedError # an abstract Legs doesn't have how to walk

class OneLeg(Legs): # a monopod? like in Plinius?
"http://en.wikipedia.org/wiki/Monopod_(creature)"
def walk():
# implement walking with one leg
print "Look ma, just one leg!"

class TwoLegs(Legs):
def walk():
# implement walking with two legs
print "Left, rigth, left, right..."

def CreateLegs(animal, number_of_legs):
# legs might depend on animal too
if number_of_legs==1: return OneLeg()
elif number_of_legs==2: return TwoLegs()
raise ValueError, "Invalid number of legs: %d" % number_of_legs

If walking in general, have some common structure, you can put the
"sketch" on Legs and let the derived classes "fill the gaps". This is
known as "Template Method Pattern" - look for it.


--
Gabriel Genellina
Softlab SRL

__________________________________________________
Correo Yahoo!
Espacio para todos tus mensajes, antivirus y antispam ¡gratis!
¡Abrí tu cuenta ya! - http://correo.yahoo.com.ar
 
G

George Sakkis

Gabriel said:
If walking in general, have some common structure, you can put the
"sketch" on Legs and let the derived classes "fill the gaps". This is
known as "Template Method Pattern" - look for it.

Or if you'd rather see a concrete example, here's how your toy example
would look refactored using the template pattern:

class Creature(object)
def run(): pass
def walk():
# lots of commands
# that do not depend on the
# number of legs but definitely
# have to do with walking

self._specialized_walk(*args, **kwds)

# more commands

def _specialized_walk(self, *args):
raise NotImplementedError


class UnipedalCreature(Creature):
def _specialized_walk(self, *args, **kwds):
# blahblah


class BipedalCreature(Creature):
def _specialized_walk(self, *args, **kwds):
# blahblah


HTH,
George
 
F

Frank Millman

Jackson said:
I've got an inheritance question and was hoping brighter minds could
guide me. I am in the strange situation where some of the methods in a
subclass are actually more general than methods in a superclass. What
is the preferred way to handle such situations. My original thought was
to do something like this:

class AA(object):
def general_method(): pass

class A(AA):
# redefine general_method() to call a
# restricted version of AA.general_method()

class B(A,AA):
# redefine general_method() to call AA.general_method()

This seems ugly to me, and I am wondering if there is a better method.
So any suggestions would be appreciated.

I don't have an answer, but I have a similar question, so I hope you
don't mind if I add it to this thread. Hopefully there will be some
commonality in the responses.

Continuing your analogy of animals, assume a class A with a 'walk'
method and an 'eat' method.

Most animals walk the same way, but a few don't, so I create a subclass
AW and override the walk method.

Most animals eat the same way, but a few don't, so I create a subclass
AE and override the eat method.

How do I create an instance of an animal that both walks and eats
differently?

This is how I do it at present.

class A(object): # walks normally, eats normally
def walk(self):
normal walk
def eat(self):
normal eat

class AW(A): # walks differently, eats normally
def walk(self):
different walk

class E(object): # abstract class
def eat(self):
different eat

class AE(E,A): # walks normally, eats differently
pass

class AWE(E,AW): # walks differently, eats differently
pass

So I use multiple inheritance instead of subclassing to override the
eat method. It works, but it feels ugly. Is there a cleaner solution?

Thanks

Frank Millman
 
G

Gabriel Genellina

Continuing your analogy of animals, assume a class A with a 'walk'
method and an 'eat' method.

Most animals walk the same way, but a few don't, so I create a subclass
AW and override the walk method.

Most animals eat the same way, but a few don't, so I create a subclass
AE and override the eat method.

How do I create an instance of an animal that both walks and eats
differently?

This is how I do it at present.

class A(object): # walks normally, eats normally
def walk(self):
normal walk
def eat(self):
normal eat

class AW(A): # walks differently, eats normally
def walk(self):
different walk

class E(object): # abstract class
def eat(self):
different eat

class AE(E,A): # walks normally, eats differently
pass

class AWE(E,AW): # walks differently, eats differently
pass

So I use multiple inheritance instead of subclassing to override the
eat method. It works, but it feels ugly. Is there a cleaner solution?

Answer 1) Move *both* ways of walk, and *both* ways of eating, to
another base class. Best when this is pure behavior - no new
attributes are involved.

class A(object): # does not know how to walk, neither how to eat
def walk(self):
raise NotImplemented
def eat(self):
raise NotImplemented

class WalkNormallyCapability(object): # knows how to walk normally
def walk(self):
normal walk

class WalkDifferentCapability(object): # knows how to walk different
def walk(self):
different walk

class EatNormallyCapability(object): # knows how to eat normally
def eat(self):
normal eat

class EatDifferentCapability(object): # knows how to eat different
def eat(self):
different eat

class
AWnEn(A,WalkNormallyCapability,EatNormallyCapability): # walks
normally, eats normally
pass

class
AWdEn(A,WalkDifferentCapability,EatNormallyCapability): # walks
different, eats normally
pass
....etc.

The xxxCapability classes are usually referred as "mixin class" -
they add a new capability to an existing class, without adding new
attributes (they could, but initialization gets more complicated, you
must rewrite __init__ and so...). They may inherit from a common base
class, too, but that's not required in Python.


Answer 2) Use an instance of another class to define how to walk and
how to eat. Advantage: it can later be modified at runtime (strategy pattern).

class A(object):
def __init__(self, walker, eater):
self.walker=walker
self.eater=eater
def walk(self):
self.walker.walk()
def eat(self):
self.eater.eat()

class NormalWalker(object): # knows how to walk normally
def walk(self):
normal walk

class DifferentWalker(object): # knows how to walk different
def walk(self):
different walk

class NormalEater(object): # knows how to eat normally
def eat(self):
normal eat

class DifferentEater(object): # knows how to eat different
def eat(self):
different eat

a=A(NormalWalker(), DifferentEater())
or define a factory function when you decide which walker an which
eater to use based on its arguments. As above, they may inherit from
a common base class (Walker, Eater, by example).



--
Gabriel Genellina
Softlab SRL

__________________________________________________
Correo Yahoo!
Espacio para todos tus mensajes, antivirus y antispam ¡gratis!
¡Abrí tu cuenta ya! - http://correo.yahoo.com.ar
 
F

Frank Millman

Gabriel said:
Continuing your analogy of animals, assume a class A with a 'walk'
method and an 'eat' method.

Most animals walk the same way, but a few don't, so I create a subclass
AW and override the walk method.

Most animals eat the same way, but a few don't, so I create a subclass
AE and override the eat method.

How do I create an instance of an animal that both walks and eats
differently?
[snip]

Answer 1) Move *both* ways of walk, and *both* ways of eating, to
another base class. Best when this is pure behavior - no new
attributes are involved.

[snip details]
Answer 2) Use an instance of another class to define how to walk and
how to eat. Advantage: it can later be modified at runtime (strategy pattern).

[snip details]

Many thanks for this, Gabriel.

I have seen explanations like this before, but my eyes usually glaze
over before I have finished, and I end up more confused than when I
started.

With a combination of my subconscious slowly getting an understanding
of this, and your clear explanation, I think I have finally got it.

Obviously my real world situation is quite a bit more complex than this
simple example, but with the help you have given me I can now
experiment with different ideas and decide on the best strategy.

Thanks again

Frank
 
C

Carl Banks

Jackson said:
For a more "concrete" example:

Suppose all the animals in the world have only 1 or 2 legs.

class Legs(object)
def run(): pass
def walk(number_of_legs):
# lots of commands
# that do not depend on the
# number of legs but definitely
# have to do with walking

if number_of_legs == '1':
# blah blah

if number_of_legs == '2':
# blah blah

# more commands

class HasAtLeastOneLeg(Legs):
def walk():
# Legs.walk(number_of_legs=1)

class HasTwoLegs(HasAtLeastOneLeg,Legs):
def walk()
# Legs.walk(number_of_legs=2)

Well, I would have done this differently. If the only difference in
behavior between one- and two-legged creatures is their gait, I
probably wouldn't have bothered breaking Legs into subclasses. I'd
just pass the number of legs into __init__:

class Legs(object):
def __init__(self,number_of_legs):
self.number_of_legs = number_of_legs
def walk(self):
# non-legs-dependent stuff
if self.number_of_legs == 1:
# one-legged gait
elif self.number_of_legs == 2:
# two-legged gait
else:
raise ValueError("invalid number of legs")
# more non-legs-dependent stuff

Then, when defining some sort of monster class, I'd do something like
this:

class Human(object):
def __init__(self):
self.legs = Legs(2)
# etc.

class Dufflepud(object):
def __init__(self):
self.legs = Legs(1)
# etc.

If there's more to it than just gait (say for example, one-legged
creatures attack differently, jump higher, and can't turn invisible
because they don't have enough limbs to cast Level 4 Invisibility),
then I would factor out differences in behavior into subclasses.

class Legs(object):
def walk(self):
# non-leg-related stuff
self.set_gait()
# more non-leg-related stuff

class OneLeg(Legs):
def set_gait(self):
# set the one-legged gait

class TwoLegs(Legs):
def set_gait(self):
# set the two-legged gait

class Human(object):
def __init__(self):
self.legs = TwoLegs()

class Dufflepud(object):
def __init__(self):
self.legs = OneLeg()


ISTM you have been missing out on the true power of inheritance.
Behavior exclusive to creatures with two legs should be implmented in
the TwoLegs class, but in your example you define a HasTwoLegs class
yet still implement this behavior in the base class Legs. That's just
poor use of inheritance. My examples implement behavior exclusive to
two-legged creatures inside the TwoLegs class, where it belongs.

In fact, BEHAVIOR is the key to arranging class hierarchies. (The
classical advice, the inheritance represents the "is a" relationship,
can be very misleading and I don't recommend using it. The focus
should be on behavior.) Anyways, in your universe, there are three
kinds of behaviors:

A. behaviors exclusive to one-footed creatures
B. behaviors exclusive to two-footed creatures
C. behaviors common to both

Once you consider behaviors in this way, how to arrange the class
hierarchy becomes obvious. Each kind of behavior shoud be implemented
in its own class: common behaviors go into base classes, exclusive
behaviors into subclasses. The hierarchy should look like this:

class Legs -- this should implement all common behavior
class OneLeg(Legs) -- should implment all behavior exclusive to
one-legged creatures
class TwoLegs(Legs) -- should implment all behavior exclusive to
two-legged creatures

Note that there's no need for an AtLeastOneLeg class. There are only
one- and two-legged creatures in this univserse, so behaviors exclusive
to creatures with at least one leg apply are in fact common to all
creatures. But suppose we add zero-footed creatures to the universe.
Now we identify five kinds of behaviors:

A. behaviors common to all creatures
B. behaviors exclusive to zero-footed creatures
C. behaviors common to one- and two-footed creatures
D. behaviors exclusive to one-footed creatures
E. behaviors exclusive to two-footed creatures

Now we need an AtLeastOneLeg class in our hierarchy:

class Legs
class ZeroLegs(Legs)
class AtLeastOneLeg(Legs)
class OneLeg(AtLeastOneLeg)
class TwoLegs(AtLeastOneLeg)

But, notice that we still have a OneLeg class, because there's still
behavior exclusive to one-legged creatures. It belongs in OneLeg, not
in AtLeastOneLeg.

Hope this long-winded advice helps.


Carl Banks
 

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,774
Messages
2,569,596
Members
45,143
Latest member
SterlingLa
Top