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.
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(message):
morse = []
for letter in message:
morse.append( letter_to_morse[letter.lower()] )
return morse
encode("Hello")
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.
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
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.
m.encode("Hello World")
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.
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.
g1 = GuessGame("cat")
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
.
g1.guess("dog")
g1.guess("cat")
When we call the function g2.guess(value)
it compares value
against self._secret
for g2
.
g2.guess("cat")
g2.guess("dog")