[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!