As you know, Reeborg has an oil leak. It is time to fix it. Let’s go back and review the code that determines the oil leak.
r = UsedRobot()
inspect(r.body)
You should see something like:
x
y
tokens
orientation
_is_leaky
_prev_x
_prev_y
_prev_orientation
triangle
square
star
We recognize the private (i.e. starting with an underscore character) variable is_leaky. Let’s see what value it has.
r = UsedRobot()
print(r.body._is_leaky)
And the result is True. This suggest that we can take care of the leak by assigning the value of False to this variable. Let’s do a quick test.
Try this!
Select world Empty and run the following code:
r = UsedRobot()
reeborg.body._is_leaky = False
reeborg.move()
reeborg.body._is_leaky = True
reeborg.move()
reeborg.body._is_leaky = False
reeborg.move()
reeborg.body._is_leaky = True
reeborg.move()
The above test should have confirmed our hypothesis. (Remember to try it; I might have changed the code powering Reeborg’s site since this tutorial was written.) So, all we need to do when creating a robot it so immediately set its _is_leaky body attribute to False, as follows:
r = UsedRobot()
r.body._is_leaky = False
This is not very elegant. One would think that it should be possible to do it when creating the robot instance ... and indeed that is the case. Let’s see how.
Python has a special method named __init__ that is called when an instance is created. Let’s quit the robot world (you can close the World panel but keep the Diary open) for a short while to learn about this special method.
Try this!
Run the following code:
class MyClass(object):
def __init__(self):
self.x = 1
my_object = MyClass()
print(my_object.x)
By running the above code, you might think that you know what to do to fix our robot so that it is not leaky.
Try this!
Does the following code fixes the leak?
class FixedRobot(UsedRobot):
def __init__(self):
self.body._is_leaky = False
r = FixedRobot()
r.move()
As you just saw (you should really run the above code), it did not work at all. In fact, as you saw, no robot was created. (You might need to use world Empty to see this clearly.) So, let’s not be so hasty this time: we will look at a few more examples of creating classes before going back to the robot world.
Try this!
Run the following code:
class MyClass(object):
def __init__(self, x):
self.x = x
my_object = MyClass(1)
print(my_object.x)
#my_other_object = MyClass()
#print(my_other_object.x)
If you uncomment the last two lines, it will not work; make sure your try it.
What we have used in the above code is known as a positional argument. To understand the name better, try the following example
Try this!
def my_function(x, y, z):
print(x, y, z)
print(z, x, y)
my_function(1, 2, 3)
class MyClass(object):
def __init__(self, x, y):
self.x = x
self.y = y
my_object = MyClass(4, 5)
print(my_object.x)
print(my_object.y)
Positional arguments are required. If we want an optional argument, we use a named argument, which is an argument that is given an optional value.
Important
Positional argument must be listed first, followed by named arguments. When calling a function or method, if the argument name is not given, its value is determine by the position at which it occurs.
Try this!
Run the following code:
def test(a, b, c=3, d=4):
print(a, b, c, d)
test(1, 2)
test(1, 2, 5, 6)
test(1, 2, d=7)
test(1, 2, d=8, c=9) # different order of named arguments
It is time to go back to classes.
Try this!
Try to guess the result before you run the following code:
class MyClass(object):
def __init__(self, x=1):
self.x = x
self.y = True
def print_me(self):
print(self.x, self.y)
class MyOtherClass(MyClass):
pass
class YetAnotherClass(MyClass):
def __init__(self, x=1):
self.x = x
self.y = False
a = MyClass()
a.print_me()
b = MyClass(2)
b.print_me()
c = MyOtherClass(3)
c.print_me()
d = YetAnotherClass()
d.print_me()
This approach suggests that, all we need to do when we want to change the initialisation of derived classes is to recopy all of the content of the parent’s __init__ method and change what we need to change. However, remember Rule #3 of the beginner’s tutorial:
Important
Python has a special function that can help us avoiding repetition; it is called super and can be used as shown in the following example that you must try.
Try this!
Run the following code:
class MyClass(object):
def __init__(self, x=1):
self.x = x
self.y = True
def print_me(self):
print(self.x, self.y)
class YetAnotherClass(MyClass):
def __init__(self, my_x=4):
super().__init__(x=my_x)
self.print_me() # x and y from parent
self.y = False
d = YetAnotherClass()
d.print_me()
In the above example, I used a variable my_x for the derived class; this is not right but it was to help you understand the proper way of writing the code as it has the weird assignement: x=x in the list of arguments.
Try this!
Run the following code:
class MyClass(object):
def __init__(self, x=1):
self.x = x
self.y = True
def print_me(self):
print(self.x, self.y)
class YetAnotherClass(MyClass):
def __init__(self, x=1):
super().__init__(x=x)
self.print_me() # x and y from parent
self.y = False
d = YetAnotherClass()
d.print_me()
We are now ready to go back to fix Reeborg’s leak. However, before we do so, I should mention another special Python method: __str__.
As you saw in the above code, we found it useful to write a special method to print all the relevant information about our instances. Python has a standardized way to do this using __str__. In this special method, programmers create a string of characters that is used when printing information they deem to be useful about a given instance of a class.
Try this!
Run the following code:
class MyClass(object):
def __init__(self, x=1):
self.x = x
self.y = True
def __str__(self):
return str(self.x) + " " + str(self.y)
a = MyClass()
print(a)
Note that we made use of the special function str which converts its argument into a string of caracters.
We are finally ready to fix the oil leak. We know that we want to change the value of a single attribute (_is_leaky) while keeping everything else the same. This is how we can do it.
Try this!
Fix the leak as follows in the world Empty:
class RepairedRobot(UsedRobot):
def __init__(self, x=1, y=1, orientation='e', tokens=0, leaky=False):
super().__init__(x=x, y=y, orientation=orientation, tokens=tokens)
self.body._is_leaky = leaky
fixed = RepairedRobot(3, 3)
leaky = RepairedRobot(5, 5, leaky=True)
fixed.move()
leaky.move()
Note that, rather than simply fixing the leak once and for all, we chose to add another named argument to give us the option to have a leaky robot. This can be useful when writing programs: we activate the leak while debugging the program, and turn it off when everything is working correctly.