Friday, February 03, 2006

Fixing Reeborg: introducing classes

For those familiar with rur-ple lessons, I thought I would share the first draft of my introduction to classes. Some points to remember from preceding lessons:
• I used the analogy of synonyms to talk about variables
• Students have already seen how to define new object [like Reeborg = UsedRobot()]

Fixing up Reeborg

As we have seen, we can create new robots by using the notation

Reeborg = UsedRobot()
Erdna = UsedRobot(2, 2)

However, these robots are just like the original nameless one: they
can only turn left. To teach them how to turn right, we could define
a function similar to what we did before:

def Reeborg_turn_right():
for i in range(3):
Reeborg.turn_left()

However, there are at least two problems with this approach:

• We need to define a similar function for every single robot we create (Reeborg, Erdna, ...).

• Reeborg and turn are separated by an underscore character "_" for the function we defined [to turn right] and by a dot "." for the built-in method [to turn left]. This would not look right...

A better approach is to define a new class that would inherit from the class UsedRobot. This is
what we do in the next section.

Designing a new class

I will show you first how we can fix our robot so that it knows how to turn right, and explain what I did afterwords.

class RepairedRobot(UsedRobot):
def turn_right(synonym_used_to_refer_to_this_object):
for i in range(3):
synonym_used_to_refer_to_this_object.turn_left()

Here's how we can then use this new class of objects:

newReeborg = RepairedRobot()

newReeborg.turn_left() # as before
newReeborg.turn_right() # new method!

newErdna.turn_right() # this one works too!

And now, it is time to explain. The Python keyword class indicates that we are going to define a new type of "function", one that creates objects. What follows class is: RepairedRobot(UsedRobot).

RepairedRobot is the name of our new class; by writing UsedRobot between the parentheses, we ensure that the new class RepairedRobot inherits all the methods and attributes that UsedRobot had. Thus, when we write:

newReeborg = RepairedRobot()

we create a new robot "named" newReeborg which can do (at least all) the same things that the old Reeborg = UsedRobot() could do.

Next, inside the new class, as indicated by the indented block, we define a new method, turn_right(). By defining it inside the class, we take the first step to insure that all the robots that are created by calling RepairedRobot() will be able to turn right!

The second step that is required is to tell Python that the method will "belong" to the particular object that has been created. To do so, we use the variable synonym_used_to_refer_to_this_object which will refer to newReeborg, newErdna, etc., depending on what object is created. When we write

newReeborg = RepairedRobot()

Python creates a new instance of the class RepairedRobot and defines all the methods, replacing the first argument of the method (synonym_used_to_refer_to_this_object) by the name of the
instance (newReeborg).

Now, synonym_used_to_refer_to_this_object is rather a long name to type. By convention, another variable name is used: self. Thus, to follow the normal convention, I should have
written:

class RepairedRobot(UsedRobot):
def turn_right(self):
for i in range(3):
self.turn_left()