Functions and Classes in Python

Objects

Overview:

  • Teaching: 10 min
  • Exercises: 20 min

Questions

  • What is an object?
  • How should I use objects?
  • What are the components of an object?

Objectives

  • Know how to define an object
  • Understand that objects are used to package data and functionality

What are objects?

In the last session you learned how to package up useful code into functions. This is a really useful idea, as it lets you re-use useful code in your own scripts, and to then share useful code with other people.

However, it is normal for functions to rely on data. For example, consider the Morse code encode and decode functions in the last lesson. These only work because of the data contained in the letter_to_morse dictionary. The functions would break if anyone changes the data in this dictionary.

In [1]:
letter_to_morse = {'a':'.-', 'b':'-...', 'c':'-.-.', 'd':'-..', 'e':'.', 'f':'..-.',
                   'g':'--.', 'h':'....', 'i':'..', 'j':'.---', 'k':'-.-', 'l':'.-..', 'm':'--',
                   'n':'-.', 'o':'---', 'p':'.--.', 'q':'--.-', 'r':'.-.', 's':'...', 't':'-',
                   'u':'..-', 'v':'...-', 'w':'.--', 'x':'-..-', 'y':'-.--', 'z':'--..',
                   '0':'-----', '1':'.----', '2':'..---', '3':'...--', '4':'....-',
                   '5':'.....', '6':'-....', '7':'--...', '8':'---..', '9':'----.',
                   ' ':'/' }
In [2]:
def encode(message):
    morse = []
    for letter in message:
        morse.append( letter_to_morse[letter.lower()] )
    return morse
In [3]:
encode("Hello")
Out[3]:
['....', '.', '.-..', '.-..', '---']

The above encode("Hello") has worked. However, if we change the data in letter_to_morse, e.g. swapping l from .-.. to -.--, then we get ['....', '.', '-.--', '-.--', '---'], which is wrong. We can make even larger changes, which would completely break the function...

While such changes are easy to spot in this example, they become more difficult to find in larger programs. In addition, as you share code, you will find that people using your code will do weird things to the data on which it depends, which can introduce weird bugs and problems.

The solution is to package a function together with the data on which it depends into a single object. This idea is the foundation of object orientated programming. To explore this, let us start with a simple example that packages the encode function together with the letter_to_morse dictionary on which it depends.

In [4]:
class Morse:
    def __init__(self):
        self._letter_to_morse = {'a':'.-', 'b':'-...', 'c':'-.-.', 'd':'-..', 'e':'.', 'f':'..-.',
                   'g':'--.', 'h':'....', 'i':'..', 'j':'.---', 'k':'-.-', 'l':'.-..', 'm':'--',
                   'n':'-.', 'o':'---', 'p':'.--.', 'q':'--.-', 'r':'.-.', 's':'...', 't':'-',
                   'u':'..-', 'v':'...-', 'w':'.--', 'x':'-..-', 'y':'-.--', 'z':'--..',
                   '0':'-----', '1':'.----', '2':'..---', '3':'...--', '4':'....-',
                   '5':'.....', '6':'-....', '7':'--...', '8':'---..', '9':'----.',
                   ' ':'/' }
        
    def encode(self, message):
        morse = []
        for letter in message:
            morse.append( self._letter_to_morse[letter.lower()] )
        return morse

Objects and Classes

Above, we have packaged the data (letter_to_morse) together with the encode function into what we call a class. A Class describes how data and functions are combined together. An instance of a class is called an object, which we can create by calling Morse().

In [5]:
m = Morse()

m is an object of the class Morse. It has its own copy of letter_to_morse within it, and its own copy of the encode function. We can call m's copy of the encode function by typing m.encode(...), e.g.

In [6]:
m.encode("Hello World")
Out[6]:
['....', '.', '.-..', '.-..', '---', '/', '.--', '---', '.-.', '.-..', '-..']

To create a new class, you use the class keyword, followed by the name of your class. In this case, class Morse defined a new class called Morse. You then add a colon, and write all of the functions that should be part of the class indented below. At a minimum, you must define one function, called the constructor. This function has the signature def __init__(self, arguments...). The first argument, self, is a special variable that allows an object of the class to access the data that belongs to itself. It is the job of the constructor to set up that data. For example, let's now create a new class that provides a simple guessing game.

In [7]:
class GuessGame:
    def __init__(self, secret):
        self._secret = secret
        
    def guess(self, value):
        if (value == self._secret):
            print("Well done - you have guessed my secret")
        else:
            print("Try again...")

In this class, the constructor __init__(self, secret) takes an extra argument after self. This argument is saved as the _secret variable that is part of the self of the object. Note that we always name variables that are part of a class with a leading underscore. We can construct different object instances of GuessGame that have different secrets, e.g.

In [8]:
g1 = GuessGame("cat")
In [9]:
g2 = GuessGame("dog")

Here, the self._secret for g1 equals "cat". The self._secret for g2 equals "dog".

When we call the function g1.guess(value), it compares value against self._secret for g1.

In [10]:
g1.guess("dog")
Try again...
In [11]:
g1.guess("cat")
Well done - you have guessed my secret

When we call the function g2.guess(value) it compares value against self._secret for g2.

In [12]:
g2.guess("cat")
Try again...
In [13]:
g2.guess("dog")
Well done - you have guessed my secret

Exercises

1

Edit the GuessGame example so that it records how many unsuccessful guesses have been performed. Add a function called nGuesses() that returns the number of unsuccessful guesses. Once you have made the changes, check your class by creating an object of your class and using it to make some successful and unsuccessful guesses.

Solution

2

Edit the constructor of your GuessGame class so that the user can optionally specify a maximum number of allowable guesses. If the maximum number of guesses is not supplied, then set the default value to 5.

Create a maxGuesses() function that returns the maximum number of allowable guesses.

Finally, edit the guess() function so that it will not let you make more than the maximum number of guesses (e.g. if the number of guesses exceeds the maximum number, then print out "Sorry, you have run out of guesses.").

Check that you code works by creating an object of GuessGame that only allows three guesses, and see what happens if you guess incorrectly more than three times.

Solution

Key Points:

  • Objects are used to package data and functionality
  • We do this by creating a class
  • Obejcts are instances of the class
  • The __init__ function sets the initial state of the object