Table Of Contents

Previous topic

10. Nothing to the left?

Next topic

12. Putting it all together!

This Page

11. Fixing the leak

As you know, Reeborg has an oil leak. It is time to fix it. Let’s go back to using Python instead of Javascript and review the code that triggers the oil leak.

r = UsedRobot()
dir_js(r.body)

You should see something like:

x
y
objects
orientation
_is_leaky
_prev_x
_prev_y
_prev_orientation

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:

reeborg = 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.

11.1. Introducing __init__

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()

11.2. More about __init__

As you just saw (you should really run the above code), it did not work at all, and generated an error message. 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.

11.2.1. Positional arguments

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 determined 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

11.2.2. Derived classes

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:

Important

Rule # 3
When writing computer programs, do not repeat yourself. I repeat: do not repeat yourself!

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.

11.3. Finally fixing the leak

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.