[Tutor] classes and inheritance [a walk on flatland]

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Sun, 29 Sep 2002 02:38:22 -0700 (PDT)


On Sun, 29 Sep 2002, Magnus Lycka wrote:

> >Say I have a class Animal, Mamal, and Human.  Then go on to say that
> >Mamal inherits from Animal and that Human does from Mamal.  Can you
> >make an object from Mamal that then is a generic Mamal that doesn't
> >have the attributes of Human and is this a good programming practice?
>
> Well... Yes and no...

A good elvish answer.  *grin*


> It's really pointless to dicuss classes without knowing what kind of
> problem you are trying to solve. Classes are not written to model the
> world, but to serve some kind of function.


Hi Cameron,

Just as a side note: it's my feeling that too many OOP examples in
textbooks put way more emphasis on inheritance than is necessary.


Let's pretend that we're doing a simulation of the universe.  Hmmm...
that's a bit too big.  Let's do something simpler: let's simulate a flat
line.


What we can imagine are objects that are walking on a straight line: each
object can decide to either step to the left, or to the right.  It's like
an ant farm... except in 2d.  *grin*

Let's call these things "Walkers".

A walker has a simple life: it can move either "L"eft or "R"ight.  For
this particular problem, we'll say that it's nonsense to construct a
vanilla one --- we want people to subclass new walkers, to give them
color, and we can force the issue by raising an exception:

###
class ImplementationException(Exception): pass

class Walker:
    def walk(self):
        """Returns either 'L' or 'R', depending if the walker is shifting
        to the left or to the right.  Overwritten by subclasses."""
        raise ImplementationException, "not implemented.  Override me."
###



That is, any subclass of a Walker here will be forced to write an
implementation of walk() or face extinction by exception.

###
>>> w = Walker()
>>> w.walk()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "/tmp/python-830twU", line 18, in walk
__main__.ImplementationException: not implemented.  Override me.
###

This may not be an appropriate thing to do in other circumstances!  So
that's why your initial question is a "yes" and "no" sort of thing: it
really depends on the problem.  Here, we just want to force the issue, so
we'll say that it should be impossible to instantiate a vanilla Walker.



Going back to this Flatland... we can make particular kinds of walkers:

    1.  Walkers that walk in a single direction.
    2.  Walkers that pace around.  They're impatient.
    3.  Walkers that just go in random directions.


A concrete example of #2 could be:

###
class RandomWalker(Walker):
    """A slightly sillier walker that just goes in a random direction."""
    def walk(self):
        return random.choice(['L', 'R'])
###


And now since we've defined how a RandomWalker() walks, we should be all
good here.



I've gone through and written a program to show these poor walkers pacing
around.  Here it is for your enjoyment:


###
"""A toy example of classes and inheritance.

Danny Yoo (dyoo@hkn.eecs.berkeley.edu)

This reminds me of an old computer game.  *grin*
"""



import random

class ImplementationException(Exception): pass

class Walker:
    def walk(self):
        """Returns either L or R, depending if the walker is shifting
        to the left or to the right.  Overwritten by subclasses."""
        raise ImplementationException, "not implemented.  Override me."


    def label(self):
        """Returns the character label of the walker.  Overwritten by
        subclasses."""
        return "?"



class LeftWalker(Walker):
    """A boring implementation of a walker that always moves to the
    left."""
    def walk(self):
        return "L"

    def label(self): return "l"


class RandomWalker(Walker):
    """A slightly sillier walker that just goes in a random direction."""
    def walk(self):
        return random.choice(['L', 'R'])

    def label(self): return "!"


class PacingWalker(Walker):
    def __init__(self, length=20):
        self.path_length = length / 2
        self.delta = 1
        self.counter = 0

    def walk(self):
        self.counter = self.counter + 1
        if self.counter > self.path_length:
            self.counter = 0
            self.delta = -self.delta
        if self.delta == 1: return "L"
        return "R"

    def label(lelf): return 'p'


def clamp(x, low, high):
    """Forces x to be clamped between a high and low boundary."""
    if x < low: return low
    if x > high: return high
    return x



class WalkerSimulation:
    def __init__(self, line_width=80):
        self.walkers = []
        self.positions = []
        self.line_width = line_width

    def addWalker(self, w):
        self.walkers.append(w)
        self.positions.append(self.line_width / 2)


    def render(self):
        line = list(' '*self.line_width)
        for i in range(len(self.walkers)):
            pos = clamp(self.positions[i], 0, self.line_width-1)
            line[pos] = self.walkers[i].label()
        return ''.join(line)


    def step(self):
        for i in range(len(self.walkers)):
            self._stepSingleWalker(i)


    def _stepSingleWalker(self, i):
        next_step = self.walkers[i].walk()
        if next_step == 'L':
            self.positions[i] = self.positions[i] - 1
        elif next_step == 'R':
            self.positions[i] = self.positions[i] + 1
        else:
            pass  ## no movement




def driverLoop(walkers):
    simulation = WalkerSimulation()
    for w in walkers: simulation.addWalker(w)
    while 1:
        print simulation.render(),
        user_input = raw_input()
        if user_input == 'q': break
        simulation.step()


if __name__ == '__main__':
    print "Hold down enter to watch the walkers walk around."
    print "To quit, type 'q' and then enter."
    print "*" * 50
    driverLoop([RandomWalker(), PacingWalker(7), PacingWalker(30),
                LeftWalker(), RandomWalker()])
###



Anyway, sorry about going off on this tangent!  But this shows a concrete
example of interface inheritance in action, and should be easy to
experiment with.


Good luck!