Interlude – Comments on Learning and Debugging

Hello. Hello people, and welcome to ‘It’s a Tree’. We have some really exiting guests for you this evening. A fabulous spruce, back from a tour of Holland, three gum trees making their first appearance in this country, scots pine and the conifers, and Elm Tree Bole – there you go, can’t be bad – an exiting new American plank, a rainforest and a bucket of sawdust giving their views on teenage violence, and an unusual guest for this programme, a piece of laminated plastic.

No tutes for while now – this part of the year gets very busy, so I’m sorry that we haven’t been having updates.  Part of the reason was that I was working on a short project (lunar lander) to use as an example here, but it seems to have run out of control with too much complexity – so I probably won’t be using it – or I will have to pare it back a fair bit.  Tutes will probably be sparse until after Christmas.

In an earlier tutorial I talked about how to make the code here your own.  I wanted to expand on those comments a little here.  First, the best way to learn is by doing.  When you simply listen or read something you don’t actually understand what is involved.  This is because when you listen only you will not understand the complexities or interactions that are going on.  Nor will you be making mistakes, because you’re not doing anything.  When I was working on the lunar lander I realised how many mistakes I was making when coding – and they were mistakes you will never see so ones you won’t learn from.

A corollary of the first principle is that you should be doing the homework I set.  When you do the homework you should keep in mind that I expect that you will be able to work it out based on the tutorial itself – or, in some cases, using earlier tutorials. So keep in mind that the homework is not super hard (those  parts marked “extra points” may well be too hard).  It is doable.  Revisit the tutorial itself and see what bits of it seem relevant.  Then try to think how you can apply those bits.  Never start from the position that you need to know some extra, mystical piece of knowledge other than what has been covered already – and in the vast majority of cases, other than what is covered in that specific tutorial.  If you really can’t work it out, post a comment.

Try and try again! No one gets everything right on the first try.  While Python syntax is not difficult, it can still take some time to master. You need to go through the pain of getting it wrong in order to learn how it works.  Sooner or later it will become second nature.  The pain of working it out the first time will pay off.

When working on your own project, think big, but start little.  Try to map out what will be involved, so far as you can.  If you can’t foresee each step, that’s not a problem, it will come to you.  Implement the part of the project that you feel confident doing, and then expand it from there.  It is often the case that the intial work on getting something going is much harder than improving it later. This might be because with new projects there is a period in which you can’t even run the code to show results. In this period you are working only on your faith in your own ability.  However, once you get the initial work done and can see something to show for your effort, expanding on that work is much easier, and more satisfying because a comparatively small amount of work can produce a substantially different result.  You will find that you will need to “refactor” the code (that is rewrite it, usually restructuring it) from time to time.  The amount by which you need to refactor will be dependent on what you knew and how much you planned before you started.  Refactoring is, by and large, unavoidable.

Use Python’s help() introspection to get help about a function.  When you are using a function or a method, make sure that the arguments you pass to the method are the same as the function is expecting and also that they are in the same order as the function is expecting.   Where I give a solution to a problem, try to understand why they solution works.

Adopt code that does some of what you want and expand from it, making changes here and there.  This, of course, depends on there being such code to adopt and your being able to find it.  If you are able to do this it is a good way to learn.  For example, if you look on the web, you’ll find a space invaders type tutorial.  Extending it means you can focus on just the extension, with the rest having been done for you.  You can, nevertheless, improve on it one piece at a time and get some gratification from achieving those things.

Look at my tute on debugging.  When you get something wrong, look at the message that Python gives you.  These messages can be cryptic, but they do have information in them that you can extract if you’re careful. Also, carefully compare what you have done to the code in the tute.

Finally, and most importantly: trust in yourself.  I am confident you are able to do this work. All you need is to commit to getting it done.

Classy Methods, a Sense of Self (Classes Part 2)

Ratcatcher     Hello – Mr and Mrs Concrete?
Both     Yes.
Ratcatcher     Well, well, well, well, well, well, well, well, well, well, well, how very nice. Allow me to introduce myself. I am Leslie Ames, the Chairman of the Test Selection Committee, and I’m very pleased to be able to tell you that your flat has been chosen as the venue for the third test against the West Indies.
Mrs Concrete     Really?
Ratcatcher     No, it was just a little joke. Actually, I am the Council Ratcatcher.

In our last tutorial we had our first look at classes and their attributes.  The attributes of a class are the data which are stored in the class.   The great thing about classes though is that we can use them to relate data to functions.  Just as the data of a class are called attributes, the functions of a class have a special name as well.  They are called “methods”. We have had a brief meeting with methods earlier.

Let’s (re)create the class from the previous tutorial:

>>> class allDads(object):
 ...    pass
 ...

Now, let’s add a method to it:

>>> allDads.r = range
>>> allDads.r(6)
 [0, 1, 2, 3, 4, 5]

Here we’ve added Python’s “built in” range() function to the allDads class.  Just as with attributes, when you create an instance of a class, the methods of the class are also inherited by the instance:

>>> myDad = allDads()
 >>> myDad.r(6)
 [0, 1, 2, 3, 4, 5]

Changing an instance’s method does not change the class’s method:

>>> myDad.r = repr
>>> myDad.r(6)                                                                                                                                                           '6'

So, we have replaced the r function for the instance myDad of the class allDads with Python’s “built in” repr function.   We have briefly met repr before – what it does is (tries to) convert the object passed to it to a string so that you can print it.  You can see in this example that the number 6 has been converted into the string ‘6’ (note the inverted commas).

Just for good measure, I’ll show that defining the method r doesn’t create a function of the same name:

>>> r(6)                                                                                                                                                                 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'r' is not defined

Overriding

You might not have noticed it, but we just covered one of the most powerful, mind blowing aspects of classes.   It’s called “overriding”.  Overriding occurs where an object which inherits from a class assigns a different method to the method the class has.  We will return to overriding a little later, but make a note for now that it’s important.  We don’t talk about overriding attributes, probably (I don’t actually know) because with attributes you’re just changing a value rather than substituting programming code which will be executed.

The r method of allDads is a bit silly.  The range method doesn’t have any meaning for allDads – nor, for that matter, does repr.  You would not normally add methods to a class after it has been defined.  It would be more normal to have all of the class’s methods set out as part of the definition of the class itself.

Init and a sense of self

There is a special method for classes (called __init__ [1]) which seems like it is silly (how do you __init__ a dad?), but it turns out to be very important.  The __init__ method initialises an instance of the class.  That is to say, whenever the class is instantiated, the __init__ method runs (once).  Because it is a method (function), arguments can be passed to it, so that the instance of the class can be customised.

However, in order for the class to customise itself at the time it is being instantiated, it needs some way of referring to the particular instance, rather than to the attributes of the class.  So, in the previous tutorial, after we had created an instance, we could assign different values to the attributes inherited from the class by that instance.  However, if __init__ is part of the class definition, it is written before any instances are made, so it doesn’t know about any instances.  How does it refer to an instance it doesn’t know about – especially when there may be multiple instances of a class?

The answer is the “self” variable.  Self refers to… itself.  That is, when an instance inherits from a class, and the class refers to self, the instance reads that reference as a reference to “myself the instance” not “the class’s self”.  Using “self” is actually “only” a convention and it could be called something else (other languages use “my”).  You should always use “self” and not some other name.

Let’s have an example:

>>> class allDads(object):                                                                                                                                           
...    def __init__(self,age=28):                                                                                                                             
...       self.age = age                                                                                                                                                 
...                                                                                                                                                                                                                                                                                                                             
>>> dad1 = allDads()
>>> dad1.age
28                                                                                                                                                                       
>>> dad2 = allDads(35)                                                                                                                                                   
>>> dad2.age
35                        
>>> dad1.age
28

I printed dad1.age again to demonstrate  that, when we initiatlised dad2, self referred to dad2, and had no impact on the instance dad1.

You need to get comfortable using self and __init__ because you will be using them a lot.

Exercise:

Rewrite the allDads class to add an attribute self.appearance, which is initialised by __init__().  Make appearance default to “Hairy”.  Test it by making two instances dad1 and dad2, passing an argument to appearance for dad1, but not for dad2.  Print out dad1.appearance and dad2.appearance to confirm it worked properly.

Notes:

[1] Pronounced (among other ways) “dunder init” – see the Introspection post for some comments on pronunciation.

Classy Attributes (Classes Part 1)

Suitably classy music starts. Mix through to Wilde’s drawing room. A crowd of suitably dressed folk are engaged in typically brilliant conversation, laughing affectedly and drinking champagne.
Prince     My congratulations, Wilde. Your latest play is a great success. The whole of London’s talking about you.
Oscar     There is only one thing in the world worse than being talked about, and that is not being talked about.
There follows fifteen seconds of restrained and sycophantic laughter…

Ok, now it’s time to draw breath a little and talk about something I mentioned a couple of tutorials ago – classes.   We’ve used heaps of classes so far without noticing them.  Classes are all round useful things,  and there’s absolutely no avoiding classes if you do any but the most trivial of programs in Python.  But that’s no problem because classes are really really neat and using them (generally) will improve your programming and your programs.  Once you start using them you’ll wonder how you ever did anything without them.

Let’s start by making a class:

>>> class allDads(object):                                                                                                                                                               
...   pass                                                                                                                                                                               
...                                                                                                                                                                                      
>>> allDads
<class '__main__.allDads'>

This defines a class called allDads (the pass statement is necessary, because Python expects a class to have statements setting out the innards of the class.  We have used pass as a sort of do nothing innard.  We will do a more interesting example in a later tute).  The class allDads is said to “inherit from” object (ie the thing named in the brackets).  Which is to say, all the functions and data that object has, allDads has as well. As everything in Python is an object, so too allDads is also an object. However, allDads is just a definition, it is not an instance.  We can make an “instance” of allDads as follows:

>>> myDad = allDads()                                                                                                                                                                    
>>> myDad                                                                                                                                                                                
<__main__.allDads object at 0x7fa2c150f610>

Nota bene: until you’re more familiar with classes, you should always define your class as inheriting from object.  That is always put “(object)” after the name of the class.

At the moment we’ve made a pretty boring class and a pretty boring instance.   The difference between classes and instances is that a class is a general description, while an instance is a particular.  Thus “all dads” is a class (that is, the class of men who are parents), but “my dad” is a particular instance of that class.

When classes store data it is called an attributeAttributes are referenced by joining the class name with the attribute name by a dot.  Here’s an example:

>>> allDads.appearance = "Hairy"

Here “appearance” is the name of the attribute, right before we first try to assign a value to it Python creates it.  It has been assigned the value “Hairy”.  Let’s retrieve the value we’ve stored there:

>>> allDads.appearance
'Hairy'

What’s more, since myDad is an instance of allDads, it has inherited the attribute from the assignment to the class:

>>> myDad.appearance
'Hairy'

However, the class doesn’t inherit from the instances (which makes sense – just because my dad has certain attributes doesn’t mean all dads do):

>>> myDad.description = "My dad is, like, so the best dad in the world.  What's more, he's particularly self effacing."
>>> myDad.description
"My dad is, like, so the best dad in the world.  What's more, he's particularly self effacing."
>>> allDads.description
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'allDads' has no attribute 'description'

As far as Python is concerned the class allDads has no attribute called “description“, even though an instance of it (myDad) does.  Let’s make another instance:

>>> thatSmellyKidsDad = allDads()
>>> thatSmellyKidsDad.appearance
'Hairy'
>>> thatSmellyKidsDad.description
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'allDads' object has no attribute 'description'

Here we’ve made a second instance of allDads called thatSmellyKidsDad.  thatSmellyKidsDad inherits the attribute appearance that the class has, but it doesn’t have the attribute we gave to the specific instance myDad.  The inheritance occurs at the time the instance is created.  If you change the value of an instance later it doesn’t affect the attribute stored in any other instance, even if the attribute is inherited from the same class attribute.

Exercise 1: assign a different value to myDad.appearance and see if it changes thatSmellyKidsDad.appearance

Exercise 2: assign a different value to allDads.appearance and see if it changes myDad.appearance and thatSmellyKidsDad.appearance

Exercise 3: make some other attributes for myDad or thatSmellyKidsDad and assign values to them.

Hint:

Use this function to show all the attributes you’ve been adding:

def showAttributesOfInstance(c):
  for i in dir(c):
    if i[:2] != "__":  # not a reserved attribute or function
      if not callable(i): # not actually necessary as you can't define methods yet
         print "%s:\t%s"%(i,c.__getattribute__(i))

>>> myDad.age = "28 and some months"
>>> showAttributesOfInstance(myDad)
age:    28 and some months
appearance:     Hairy
description:    My dad is, like, so the best dad in the world.  What's more, he's particularly self effacing.

Extra Extra Bonus Points: understand how showAttributesOfInstance() uses introspection to do what it does.

Looking Forward

Just as you can store data in a class you can also store functions in a class.  When data is stored in a class it’s called an attribute.  When a function is stored in a class, it’s called a method. – But more on that later.

Side Track – Global and Local Variables

… once a week there’s an excursion to the local Roman ruins where you can buy cherryade and melted ice cream and bleedin’ Watney’s Red Barrel, and one night they take you to a local restaurant with local colour and colouring

In the previous tutorial, I used a strange and cumbersome approach to storing the data that I needed to keep track of the image files in the directory, the total number of images and what image we were up to.  Instead of just saving the current image number in a variable called currentImage I instead created a dictionary called indexes and a key called currentImage, then assigned the value to that key.  I did the same thing for totalImages as well.  You might, justifiably, be thinking – why didn’t we just use two variables called currentImage and totalImages?  This tutorial is going to try to explain that.

Let's set up the dictionary using code from the previous tutorial:
>>> indexes = {}  # create a dictionary
>>> indexes['totalImages'] = 17 # 'totalImages is a key in the dictionary
>>> # 17 is the perfectly random number (honest, no number is more random!)
... indexes['currentImage'] = 1

Now let’s assign this value to a variable called currentImage:

>>> currentImage = 1

Confirm what we’ve typed:

>>> indexes
{'currentImage': 1, 'totalImages': 17}
 >>> currentImage
 1

So far, so good.  The problem comes when we try to refer to these in a function.  Let’s define a function which tries to print them:

>>> def printIndexes():
 ...   print indexes
 ...
 >>> printIndexes()
 {'currentImage': 1, 'totalImages': 17}

Now, let’s do function which tries to print the value of currentImage:

>>> def printCurrentImage():
 ...     print currentImage
 ...
 >>> printCurrentImage()
 1

So far we haven’t had a problem.  However, in our slideshow application we needed to increment the value of currentImage, so let’s change our functions to do that:

>>> def incrementIndexes():
 ...   indexes['currentImage']+=1
 ...

Confirm the starting values, run the function, then see what the ending values are after calling the function:

 >>> indexes
 {'currentImage': 1, 'totalImages': 17}
 >>> incrementIndexes()
 >>> indexes
 {'currentImage': 2, 'totalImages': 17}

Now, do the same for the variable currentImage:

>>> def incrementCurrentImage():
 ...    currentImage += 1
 ...
 >>> currentImage
 1
 >>> incrementCurrentImage()
 Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "<stdin>", line 2, in incrementCurrentImage
 UnboundLocalError: local variable 'currentImage' referenced before assignment

This worked for the dictionary, but it all blew up when we tried to do the same thing with the variable.  Of note is the error message: local variable ‘currentImage’ referenced before assignment.  This error can be avoided by use of the global keyword – but don’t avoid it this way:

>>> def incrementCurrentImage():
 ...    global currentImage
 ...    currentImage +=1
 ...
 >>> currentImage
 1
 >>> incrementCurrentImage()
 >>> currentImage
 2

The reason that the function failed originally is that Python was looking for a ‘local’ variable.  That is, a variable whose name only makes sense while the program is within the function. Local variables serve an important function in allowing you to modularise your code and re-use the same variable names but with different meanings in different contexts.  Thus, in a function which is processing strings the variable currentValue can be doing something different to a variable with the same name called currentValue but in a different function processing (eg) dictionaries.   This is especially true with recursive functions -but don’t expect to see any recursive functions here!

Unless you expressly tell Python otherwise, if a variable is changed inside a function, it is a local variable (if a variable is only read, then Python is happy to assume it’s a global variable – in a sense, it has to be because if it’s only read, it must have got its value somewhere outside the function).  This means it must be initialised within the function (since its name has no meaning outside the function).  If it hasn’t been initialised Python gets confused.  Hence the UnboundLocalError: it gave us.

How about this:

>>> aVariable = 1
 >>> def incrementLocally():
 ...   aVariable = 1
 ...   aVariable += 1
 ...   print aVariable
 ...
 >>> print aVariable
 1
 >>> incrementLocally()
 2
 >>> print aVariable
 1

In this example, despite doing stuff to aVariable inside the function, the variable called aVariable that we started with has been unchanged.  Now try this:

>>> def incrementLocallyAgain(aVariable):
 ...    aVariable += 1
 ...    print aVariable
 ...
 >>> print aVariable
 1
 >>> incrementLocallyAgain(aVariable)
 2
 >>> print aVariable
 1

The same thing happened, even though we passed in aVariable as an argument to the function.   Within the function aVariable was a local variable.  It got a value when the function was started and that value was assigned locally.  Because local variables must be assigned a value before they are referenced it also follows that each time a function is called, its local variables are reset or reinitialised.  Indeed, each time the function is called, a whole new piece of memory is allocated to the function to run in.  This also means that local variables are not remembered from one invocation of the function to another (and even if they were, the need to initialise the variable would end up forgetting the old value anyway).

So, why did it work for a dictionary?  This is because dictionaries provide a level of indirection to the values stored in them.  The local functions never tried to assign a value to the name used by the dictionary, they only ever tried to assign a value to a key within the dictionary.  That is, they never said

indexes = 5

Rather, they said

indexes[‘someKey’]=5.

So Python only looks up the dictionary.  When it doesn’t find it locally, it assumes it (the dictionary) is global.  From there it can access and change the values of keys in the dictionary.

You can see what values are local and global using two specific functions called locals() and globals() respectively.  These functions return a dictionary with the names and values of local and global variables respectively.  They are also additional examples of Python’s introspection as we discussed in our earlier tute.

>>> def incrementLocally():
...    aVariable = 1
...    aVariable += 1
...    print aVariable
...    print 'locals = ',locals()
...
>>> aVariable = 6
>>> incrementLocally()
2
locals =  {'aVariable': 2}
>>> aVariable
6

Usually you won’t need to worry about whether something is local or global because it will naturally be determined by the way you do your code.  As a rule of thumb you should avoid using ‘global’ variables.  Unless you are very careful about their use, they can make your code very hard to understand and maintain.  If you need to make reference to a variable from within a function, then pass the variable in as a parameter to the function.

Really GUI (more on Buttons, images and handlers)

Back to the photo sequence and music. Each photo is on the screen for only two seconds, and in between each there is a click as of a slide projector changing or even the sound of the shutter of a camera. The photos show in sequence: Anthony Barber, Katy Boyle, Edgar Allan Poe, a loony head and shoulders. He has ping-pong ball eyes, several teeth blocked out, a fright wig and his chest is bare but across it is written ‘A Loony’, Reginald Maudling, Tony Jacklin. A buzzer sounds.

In the last few tutorials we have been looking at various parts of what make up a graphical user interface.   We’ve created labels and buttons and have attached images to a label (and a button if you did the homework).  We have also associated an action with pressing a button.   In this tutorial we’re going to put these together to make a simple image viewer application.  As with the previous tutorials, this will only work with images which are saved as a “gif” format.  Also, a word of warning – you need to do pretty much all of the code before any of it will work.

Here are some images for you to use.  As with the earlier tutorial, you can either use ‘save as’ to download a copy, or you can use the Python code in the note below to download them (if you use Python you should be in your python4kids directory when you do the download otherwise the files will get saved somewhere else).  A gold star if you use Python for the download.

Planning

When writing a program it is a good idea to plan out what tasks the program is intended to do first.  Then, when you write the program the plan can guide you from one component to another.  Also, you can write a plan as a comment at the start of your program and it can serve as a docstring – at least until you’ve gone back and written a proper docstring for your code.

Exercise: Use your text editor to make a list of the things that would need to happen in order for a slide show program to run.   Save the list to a file called ‘p4kSlideshow.py’ in your python4kids directory.   Make your edits to this file.  Enclose the list in triple quotes ->

'''
item 1  (and explanation)

item 2 etc

...

'''

Solution:

The things that I think a slideshow should do are:

  • make a list of all the images in the directory
  • get the first image and display it
  • on user input, display the next image (in this case, user input will be a button click)
  • when all images have been displayed exit.

My list of things will likely be different from your list of things.  For example, you might want the images to be displayed in a random order, or when you get to the end of the images you might want to start again from the beginning, or you might want the user to choose the image to show from a list.  There are many ways of approaching it.  I’m showing you something simple, but if you’ve got other things on your list, extend the work we do here and implement them in your own program.

Here’s my note:

'''
 p4kSlideshow
 When run in a directory this program:
 * searches the directory for files with a '.gif' extension
 * displays the first image it finds
 * when a user clicks, it displays the next image
 * if user clicks last image, the program exits.
'''

The benefit of doing this is it tells us what to do (and, hopefully, also in what order – in our case we are dealing with a handler so it’s a little mixed up). It’s our map to completing the program.  Let’s do the first one.

Coding

We are going to use a function from the os module called os.listdir (we have used this before) and another called os.path.splitext.  Splitext splits a file into its base file name and its extension so thisfile.txt would become [‘thisfile’,’.txt’].  We do this to identify which files are gif files.  We could have also used the slicing operator [-3:] (reminder – see here):

import os

IMAGE_EXTENSIONS=['.gif']
listOfFiles = os.listdir('.')
# list dir doesn't necessarily give alphabetical order so we sort
listOfFiles.sort()
listOfImages = []
for f in listOfFiles:
  if os.path.splitext(f)[-1] in IMAGE_EXTENSIONS:
    listOfImages.append(f)

Note that there’s a ‘[-1]’ after splitext().  This is because splitext returns  a list, with the last entry in the list ([-1] means last in the list) being the extension.  This code stores a list of images in the list named listOfImages.

Now we do the GUI bit.  We need to add the Tkinter import at the start of the file:

import os
from Tkinter import *

We also need to keep track of how many images there are, and which image it is that we are currently up to:

indexes = {}
'''
indexes is a dictionary containing some values
that we need to keep track of.
I am using a dictionary rather than two variables (say totalImages and currentImage)
so that I don't have to explain global and local variables to you yet (maybe next tute?)
'''
indexes['totalImages'] = len(listOfImages)
if indexes['totalImages'] <1:
 sys.exit()

After that we set up a button:

# set up the GUI

displayButton = Button(None,text= "If you can see this text, there's something wrong")
# we shouldn't ever see the text
# we could leave the text= parameter out

displayButton.pack()

Now we create a function to handle clicks.  Later we will need to point the button’s ‘command’ key at this function.

def nextImage():
  indexes['currentImage'] += 1 
  # += 1 says "add one to the current value of this variable"
  if indexes['currentImage'] == indexes['totalImages']:
    # if past last image, then exit
    # as the list starts at index 0 and runs to the number of entries -1
    # it will reach the last one on total entries -1, if it's equal to totalImages
    # then when we added one, we went past the last one, so exit instead of displaying.
    sys.exit()
  else:
    imageIndex = indexes['currentImage']

  imageFile = listOfImages[imageIndex]
  print "imageFile = %s"%imageFile
  # this is just for debugging, you can remove the print statement if you want
  imageObject = PhotoImage(file=imageFile)
  displayButton['image']=imageObject
  displayButton.image=imageObject # NOTE: WON'T WORK WITHOUT THIS BIT OF SORCERY!
  displayButton.pack()

Note the note!  This assignment really is sorcery.  If you remove it your program (probably) won’t work!  Moreover, if I didn’t tell you there’s no way you’d work it out for yourself.  What this line does is it forces Python to keep a reference to the imageObject.  Because of the way Tkinter does the assignment displayButton[‘image’], Python does not keep a reference to the object imageObject.  Because it has not kept a reference, Python thinks the object is not being used (even though it is). Python therefore throws the imageObject out in order to save memory.  This process is called “garbage collection” and is happening all the time in Python.  When we do the assignment displayButton.image=imageObject we force Python to remember the object and therefore not to throw it out with the rest of the garbage.

Exercise: once you’ve finished the tute comment out the line displayButton.image=imageObject run it again and see what happens.

We are just about ready now.   For the function nextImage() to work the entry indexes[‘currentImage’] needs to be initialised. We also need to display the first image and hook up the function to the button click.  These are all pretty easy:

# initialise the index we will use to display the images.
indexes['currentImage'] = -1

We use -1 here so that on the first call to nextImage 1 will be added to it, giving 0 (-1+1=0) and 0 is the first entry of the list of file names we just created.

Display the first image:

nextImage()

Hook up the nextImage function to be run when the button is clicked:

displayButton['command']=nextImage

Now, we need to start the program waiting for mouse clicks.  This has not been something we’ve done in the past few tutes because we were using an interactive Python session.  However, now we’re going to need it.  We do this by calling the ‘mainloop()’ method:

displayButton.mainloop()

Ta da! We’re done (exercise: check that we’ve implemented everything in our original list).  Run this program from your command line like this:

> python p4kSlideshow.py

and you should have a simple slideshow.  You must make sure you’ve got the gif files saved in the same directory though!

When the program hits the mainloop() function it sort of slips into a Tkinter Netherworld, in which you don’t really see what it’s doing.  It is only when Tkinter notices a mouse click on the button that control comes back to your program (executing the handler function nextImage()).  Even then, when nextImage() finishes running, program execution passes back into Tkinter and you can’t see what’s happening there.

There are a lot of limitations with this program.  Most obviously it only works with .gif files! Argh!  However, it has other problems.  First, you can’t go backwards – it’s one way from start to finish, and then the program ends.  You might want to start the slideshow from the start again.  Second,  it is quite fragile.  If one of the files is deleted, renamed or moved half way through your slide show (ie after running os.listdir() but before displaying the file) then you’re going to have a problem.  The program doesn’t check that the filename remains valid.  The program is unhelpful if there are no gif files in the directory (simply exiting).  There are many ways the program could be improved.  If you are game, have a go!

Exercise: change the program so that, once it displays all of the images it begins again with the first one.

Hint: at the end of the function nextImage() test to see if you’re at the end of the images and, if so, assign a certain value to indexes[‘currentImage’].

This was quite a complex program because we had to knit three components together.  Moreover, handlers are pretty difficult conceptually.  If you can follow what’s happening and get it running, give yourself a gold star.

Notes:

Downloading the images:

>>> imageLinks = ["https://python4kids.wordpress.com/wp-content/uploads/2011/08/p4kbuttonimage1.gif", "https://python4kids.wordpress.com/wp-content/uploads/2011/08/p4kbuttonimage2.gif", "https://python4kids.wordpress.com/wp-content/uploads/2011/08/p4kbuttonimage3.gif", "https://python4kids.wordpress.com/wp-content/uploads/2011/08/p4kbuttonimage4.gif"]
>>> import urllib
>>> for i in imageLinks:
...     filename =  i[-19:]   # [-19:] was trial and error and based on a set length of the filename
...     urllib.urlretrieve(i,filename)

The complete code:

# -*- coding: utf-8 -*-
'''
p4kSlideshow
When run in a directory this program:
* searches the directory for files with a '.gif' extension
* displays the first image it finds
* when a user clicks, it displays the next image
* if user clicks last image, the program exits.
'''

import os
import sys
from Tkinter import *

IMAGE_EXTENSIONS=['.gif']

listOfFiles = os.listdir('.')
# list dir doesn't necessarily give alphabetical order so we sort
listOfFiles.sort()
listOfImages = []
for f in listOfFiles:
  if os.path.splitext(f)[-1] in IMAGE_EXTENSIONS:
    listOfImages.append(f)

# some print statements I used for debugging the code:
#print listOfFiles
#print listOfImages

# if there are no image files, then stop!

indexes = {}
'''
indexes is a dictionary containing some values
that we need to keep track of.
I am using a dictionary rather than two variables (say totalImages and currentImage)
so that I don't have to explain global and local variables to you yet (maybe next tute?)
'''

indexes['totalImages'] = len(listOfImages)
if indexes['totalImages'] <1:
  sys.exit()

# set up the GUI

displayButton = Button(None,text= "If you can see this text, there's something wrong")
# we shouldn't ever see the text
# we could leave the text= parameter out

displayButton.pack()

# create a function to handle clicks

def nextImage():
  indexes['currentImage'] += 1
  # += 1 says add one to this variable
  if indexes['currentImage'] == indexes['totalImages']:
    # if past last image, then exit
    # as the list starts at index 0 and runs to the number of entries -1
    # it will reach the last one on total entries -1, if it's equal to totalImages
    # then when we added one, we went past the last one, so exit instead of displaying.
    sys.exit()
  else:
    imageIndex = indexes['currentImage']

  imageFile = listOfImages[imageIndex]
  print "imageFile = %s"%imageFile
  # this is just for debugging, you can remove the print statement if you want
  imageObject = PhotoImage(file=imageFile)
  displayButton['image']=imageObject
  displayButton.image=imageObject # NOTE: WON'T WORK WITHOUT THIS BIT OF SORCERY!
  displayButton.pack()

# initialise the index we will use to display the images.
indexes['currentImage'] = -1
'''
-1 is a bit of a magic number
We use -1 because the nextImage function below adds one each time it is called
so the first time it is called it will display the image at location 0 (-1+1 =0)
the next time it is called it will display the image at location 1 and so on
'''

# display the first image
nextImage()

# set the button to run the nextImage function when clicked
displayButton['command']=nextImage

# Now start the program waiting for mouse clicks
# when you click the button it will run the nextImage function
displayButton.mainloop()

Using Images in the GUI

Gladys leaps over to the tape deck, presses levers and switches. Sound of tape reversing. There is a hum and lights flash on and off. A blurred image of a lady in the street comes up on one of the monitors.

In this tutorial we look at using images in a GUI environment.  One of the possible attributes which can be assigned to a Label or Button widget is an image.   You will surely have seen many buttons with images on them.   Every time you see a toolbar in a program, each of the images on it is itself on a button widget.  In order to do this tutorial though there are a couple of issues:

  • first, you need to have images in the Python4kids directory we set up several tutorials ago.  You also need to start Python from within that directory.
  • second, unless you install something called the Python Imaging Library (PIL), Python can only handle a few limited types of image file.  One of the file types which will work is .gif, which we will be using here. This tutorial will not work with jpeg files.
  • third, you need to have the gif files we’re going to use in your python for kids directory.

If you have some gifs that you’d prefer to use, by all means copy them into your python for Kids directory.  However, if you can’t, here is an outrageously boring gif  (it is  just a screencapture from the previous tutorial) for you to download and save:

Download and save this gif image by right-clicking the images in the previous tutorial and selecting “Save Link As” (you may also be able to drag and drop images from the web page into your folder) [* or do it in Python (below)].  Remember to save it to the Python for kids directory that you set up in this tutorial.  You don’t need to change the file name.   If you have your own image file(s) you’d like to use, then, by all means put them in your Python4kids directory instead but make sure it is a ‘.gif’ file.  You can convert images from .jpg to gif using third party programs such as GIMP.

As before we import from Tkinter:

>>> from Tkinter import *
>>> labelWidget = Label(None)
>>> # Notice we haven't set the text?

Next, we make a PhotoImage object from the file (taking note of the file’s name as we downloaded it)

>>> imgFile = 'p4ktkinter110726b.gif'
>>> imageObject = PhotoImage(file = imgFile)

Watch the “file = ” syntax here.  We haven’t attached the file to the labelWidget yet.  We’ll do that very soon.  However, now if we:

>>> labelWidget.pack()

You’ll see that we get a really small labelWidget with nothing in it. You might need to resize it to see much of it at all.

So let’s put the image we have into the labelWidget:

>>> labelWidget['image']=imageObject

Note here that we are using the dictionary associated with labelWidget, you should be able to tell that because of the square brackets and the key which is inside single quotes.  At this point the object should automagically appear in the widget – if you haven’t resized the window yet you may need to in order to see the image:

It looks like there is a window inside the window, but there isn’t.  It’s just an image (try opening up the gif file and changing it with a paint package if you have one and then redoing the tutorial).  Clicking the buttons won’t have any effect.

Homework:

Display an image on a Button

* Extension: Downloading the file using Python:

>>> url = "https://python4kids.wordpress.com/wp-content/uploads/2011/08/p4ktkinter110726b.gif"
>>> fileName = url[-21:]
>>> fileName
'p4ktkinter110726b.gif'
>>> import urllib
>>> urllib.urlretrieve(url,fileName)
('p4ktkinter110726b.gif', <httplib.HTTPMessage instance at 0x82695ac>)

This relies only on knowing what the url of the image file is.  We’re using the urlretrieve function of the urllib module.  It will work for urls which are not images as well.  In order to download a file it needs to know the url of the file (ie what you would type into a web browser to surf to the file) and a file name to save the to on your computer.

A word of warning though – sometimes a network is set up so you that this will not work.  If you have trouble, better to just save as with your browser.

Button it up (Buttons and event handlers)

Cut to the gift department. A large lady is standing by counter holding a large cylinder with a rose attachment.
Lady     Yes this looks the sort of thing. May I just try it?
Assistant     Certainly, madam.
The lady presses button and a sheet of flame shoots out across the hall.
Lady     Oh! Sorry! So sorry! (she is happy though) Yes that’s fine.

We’re having a look at another kind of widget today, as well as seeing some stuff about “event handling”. The widget we’re looking at is the Button – it’s what has “Ok” on it when you get a pop up dialog.   The process is much the same as what we did last time for labels:

>>> from Tkinter import *
>>> b1 = Button(None, text="This is a button.  Can you see it has some edges that the label didn't have?")
>>> b1.pack()

This should give you a button that looks like this:

Now let’s do something zany  and add another one:

>>> b2 = Button(None, text="Click me!")
>>> b2.pack()

Here’s the pic:

Can you see that the second button has appeared below the first button?  It is also in the middle, rather than on either of the sides.  Notice now that the pack() method has brackets (which indicates it’s a function?).   Have a look at the function’s help info to see more details about the parameters that the pack() method accepts:

>>> help(b2.pack)

Let’s try one of them (make sure you can see the window with the buttons in it when you hit enter for this line):

>>> b2.pack(side=LEFT)

Did you see the Button move?

You can try side=RIGHT on your own.

You can change the text of these buttons if you want:

>>> b2['text']='hi'

Each of these buttons has associated with it a dictionary – which you should have noticed because of the use of the square brackets [] with an index which is not a number.  What we did above was change the value of the key ‘text’ in b2’s dictionary.  To see all of the keys we in the dictionary use the standard .keys() method of dictionaries:

>>> b2.keys()
['activebackground', 'activeforeground', 'anchor', 'background', 'bd', 'bg', 'bitmap', 'borderwidth', 'command', 'compound', 'cursor', 'default', 'disabledforeground', 'fg', 'font', 'foreground', 'height', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'image', 'justify', 'overrelief', 'padx', 'pady', 'relief', 'repeatdelay', 'repeatinterval', 'state', 'takefocus', 'text', 'textvariable', 'underline', 'width', 'wraplength']

Changing the values of these keys will change various characteristics of the Button.  Let’s put the text back the way it was:

>>> b2['text']="Click me!"

Now, if you actually click one of these buttons you’ll see that it changes visually (more or less as you would expect a button to – it may look a little different from the buttons you’re used to because its styling comes from Tkinter not your operating system).  However, you’ll also notice that it doesn’t actually do anything.  This should not come as a surprise, given that we have just created a button but haven’t told Python what to do when the button is pressed.

So, let’s do that.  In the list of keys above, there is a key called ‘command’.  It’s currently empty:

>>> b2['command']
 ''

This is where we tell Python what to do when the button is pressed.   The ‘command’ key expects to receive the name of a function, so we need to create a function, then set the ‘command’ key as the name of that function.  I start by setting up two constants in order to avoid spelling errors:

>>> B2BASETEXT="Click me!"
>>> B2ALT_TEXT="Click me again!"
>>> def b2Click():
 ...    if b2['text']== B2BASETEXT:
 ...       b2['text']=B2ALT_TEXT
 ...       b2.pack(side=RIGHT)
 ...    else:
 ...       b2['text']=B2BASETEXT
 ...       b2.pack(side=LEFT)

Once that is done, we set the command:

>>> b2['command']=b2Click

Now clicking the button should bounce it from one side of the window to the other, changing the text each time*[see note below]:

However, we could have put anything at all in that function b2Click(), and that’s a really powerful idea.  The only problem is that Tkinter won’t let you pass arguments to the function.  However, not only is this not actually a problem for us at the moment (it’s not something we need to do), there’s also a way around it so it’s not really a problem at all.

So, let’s add quit button:

>>> b3 = Button(None,text="Click here to quit (without further warning)", command=quit)
>>> b3.pack()

If you click this new button, Python’s quit() function is run.  That will close the windows – and, indeed, exit Python as well.  This buggers up my console (new lines don’t work properly).  If you have this problem the reset command from the console should work (:> reset).  Normally if you have a “quit” command it may result in users losing data they haven’t saved, so you will usually give users a warning to confirm they really want to quit.  You would probably “wrap” this call to quit() inside a separate function which asks the user to confirm that they want to quit.

Clicking a Button (or, indeed any other part of the window) is called an “event”.  The function which does something when you click the button is called a “handler” (also “event handler” or “callback handler”).

Another thing to notice here is where the third button ended up (since we didn’t specify a side to pack it on).  All I’m going to say about this is that laying out a user interface with Tkinter can be a little difficult to understand.  We’ll talk more about layout later.

Notes:

* the proper way to do this is to start up Tkinter’s mainloop() method.  We will do this later (and if your clicks don’t seem to work let me know!) This method does the job of listening for events (clicking the mouse on a button is an “event”) generating messages and communicating them to callback handlers (in this case the function b2Click is a callback handler).

Tkinter tinkering (Graphical User Interfaces)

Man     Shut up! (It goes quiet next door) That’s better.
He walks to a side wall and hangs his club on a hook beneath big old-fashioned art-nouveau sign clearly labelled `The Burlington Wall-banger’. He goes across to bed and gets in. In the bed are a party of four Japanese businessmen in suits with lapel badges, two lady American tourists with rain hats and cameras, three other moustached English gentlemen in pyjamas, four Tour De France riders, three Swedish businessmen, and Winston Churchill.

So far we have been dealing with a command line interface for working with our Python programs.  While command lines are good for a lot of things, they’re usually pretty bad for interfacing with a general user of the program who doesn’t know how the various pieces work.  What we are going to look at now is a different way of presenting the program to a user (and probably a way that you are more familiar with) – GUIs.   GUI stands for “Graphical User Interface”.

In order to use a GUI, we need to use an external module to do all the grunt work behind making the interface components and presenting them to the end user.  There are a number of different modules which can be used.  We are going to use one called “Tkinter“.  We are going to use Tkinter because it should come as part of every Python installation, so there’s no additional downloading to do, nor any need to get the Responsible Adult involved – but leave a comment if you have problem with Tkinter.

To start using Tkinter is pretty easy.  You do this:

>>> from Tkinter import *

and… absolutely nothing should happen!

DANGER WILL ROBINSON!

A word of warning here: as a general rule using “from X import *”  is considered really bad form in Python because it means every object (* is a “wildcard” and means everything) is imported by its own name into your program, rather than part of the X namespace.   So, in our previous examples we’ve used import random, then accessed the randint() function via the random namespace: random.randint(). Had we used from random import *, then we could have said randint() and not random.randint().  However, this would be a bad thing to do.  If you have two packages X and Y, each of which has its own object called x, then, if you use “import *” the two objects X.x and Y.x will both end up in your program called ‘x’.  As you don’t have the X and Y namespaces to distinguish them, they ‘collide’.

So, why am I using “import *” for you?  Because Tkinter is an exception.  The Tkinter package has been designed so that as few objects as possible are imported into your program, and because their names are specifically GUI related, so there is less risk of a naming collision.  If you are feeling uneasy about all this do multiple imports of the specific Tkinter components you need.

Hello World in the GUI

Graphical User Interfaces use a specific, common set of graphical components to display information to a user and to get feedback from them.  These components are called “widgets”.  As you are using a graphical interface all of the time, you are actually already aware of widgets, but you just don’t realise that they’re there.  So, for example, whenever you are presented with an “Ok/Cancel” dialog on the computer, the text of the dialog (“About to delete all your files”) is presented in a “Label” widget, while the “Ok” and “Cancel” are “Button” widgets.

So let’s do a label:

>>> labelWidget = Label(None,text="Hello Python4Kids!")  # note: None has a capital

When you hit return you should see something like this (note: if you are running python-idle, this won’t work – at least not yet, run Python from a command line shell.  If you don’t know what python-idle is, you can ignore this note):

A Tkinter window

Python has told Tkinter it’s going to need a window to put a Label widget in.  Tkinter has asked the operating system to give it a window and the operating system has given it a default window.  Along the top of the window are a number of widgets which have been provided by your operating system (not Python).  Your window may look a little different if you’re running an alternative operating system.   On my system above, there are 6 widgets – from left to right, a menu widget (X), a button (to pin this window across multiple desktops), a label ‘tk’, and three more buttons (minimise, maximise and close).  You might have a different set of operating system widgets.

Where is the label we defined? Well, it exists:

>>> labelWidget
<Tkinter.Label instance at 0x7fcf9966e368>

However, it’s not visible yet.  It’s not visible yet because we haven’t defined a “geometry” for it.   We do this through a function which is part of the widget called .pack() (.pack() is called a ‘method’ of the widget, but we haven’t done that yet).

Debugging tip:  If you can’t see your widget, make sure you’ve .pack()ed it.

So let’s .pack() it:

>>> labelWidget.pack()

The .pack() method can take a heap of arguments, which define how the widget is to be placed within the window.  As we only have a single widget, we just use the default packing, and therefore put no arguments in.   You should see the tk window above change to look something like this:

One of the widgets in the title bar is obscured here (only four are visible), but grabbing the bottom right hand corner of the window will resize it allowing you to see the lost widgets:

To close the window click the close button in the top right corner.

That’s it for now, more on GUIs in the coming tutes.

PS:

Did you notice that we did a basic GUI interface in only three lines of code (including the import statement)???  Is that amazing?

PPS:

Hello to visitors from the Podnutz webcast.

Talk to your Teddy (Debugging)

Presenter     Good evening. Tonight ‘Spectrum’ looks at one of the major problems in the world today – that old vexed question of what is going on. Is there still time to confront it, let alone solve it, or is it too late? What are the figures, what are the facts, what do people mean when they talk about things? Alexander Hardacre of the Economic Affairs Bureau.
Cut to equally intense pundit in front of a graph with three different coloured columns with percentages at the top. He talks with great authority.
Hardacre     In this graph, this column represents 23% of the population. This column represents 28% of the population, and this column represents 43% of the population.

One of the irritating things about writing programs is that, often, when you type your program in perfectly, it still doesn’t work.   You’ve got bugs!  (Apparently, one of the first errors in the operation of a computer occurred because a moth got stuck in amongst the componentry. Its removal thus started the act of debugging computer programs.)

The good news is that, in every tutorial I’ve done on this blog I have had at least one, and often many, bugs in the programs I’ve written – even the ones which are only 2 or 3 lines long!!!!  Getting things wrong is a natural part of writing programs.  We need to know how to get things right after they are wrong.  So in this tutorial I wanted to talk about bugs and debugging and things to look for when trying to get your programs running.

The two main causes of bugs we’re going to look at are syntax errors and logical errors.

Syntax errors

A syntax error means that you’ve said something to the computer which doesn’t make grammatical sense.  Thus, if you were to say to your sibling, “Yesterday I the car,” they’d have absolutely no idea what you were talking about.  That’s because this sentence doesn’t fit with English rules of grammar (it lacks a verb).   Similarly, Python programs have their own “grammar”.  If you deviate from that grammar the Python interpreter is unable to work out what you mean.

Often a syntax error is caused by a single typo somewhere in the code.  Here are some examples:

>>> def aFunction
File "<stdin>", line 1
def aFunction
^
SyntaxError: invalid syntax

Can you see here that the interpreter is trying to tell you where the problem is?  The caret ^ shows where Python thinks that there is a problem.  The interpreter is not always right, but this is a good place to start.

>>> def aFunction()
File "<stdin>", line 1
def aFunction()
^
SyntaxError: invalid syntax

First, the interpreter expects a function to have a list of parameters, so is looking for a pair of parentheses -> (). Then, it also expects to see a colon -> : to mark where the function’s code starts.

>>> def aFunction():
...
File "<stdin>", line 2

^
IndentationError: expected an indented block

Here, the interpreter sees that you’ve tried to define a function, but hasn’t found any code for the function.  Code for a function is indented, hence, if it’s not there, the interpreter thinks that there’s a problem with the indentation.

>>> def aFunction():
...   print "this is a function"
...

Note that you need to press return on a blank line to finish the function’s definition if you are in the interpreter.  This is not the case if you are using a text file for your program. Leaving out a colon or having the wrong indentation are perhaps two of the most frequent syntax errors.   As a refresher, Python uses indentation to mark off code blocks.  This means that all the code which is to be executed at the same code level must be indented to the same indent level.  There is no “right” level of indentation, as long as the indentation is consistent.   So these are both ok:

>>> def aFunction():
...   print "First line of code"
...   print "second line of code"
...
>>> def bFunction():
...      print "first line of code"
...      print "second line of code"
...
>>> aFunction()
First line of code
second line of code
>>> bFunction()
first line of code
second line of code

However, the function below won’t work because the level of indents (ie the number of spaces before the text starts) is different for each line within the same code block.  A code block is (always? <- not sure) marked off by a colon : in the previous line.

>>> def cFunction():
...     print "first line of code"
...        print "second line of code"
File "<stdin>", line 3
print "second line of code"
^
IndentationError: unexpected indent

Where you have a function you need to pass it the right number of arguments:

>>> range()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: range expected at least 1 arguments, got 0

>>> range(3)
[0, 1, 2]

The range() function is interesting in that it can take between 1 and 3 arguments (the second and third arguments are optional).  However if you pass no arguments, or more than 3, then the interpreter will complain to you.

Logical Errors

Logical errors are usually harder to diagnose.  When you write the program, because you have used the right syntax, the Python interpreter doesn’t tell you that there’s an error.  Nevertheless, when you run it, you get the wrong answer or the program crashes half way through.  Logical errors often happen with loops, for example, where you forget that the range() function starts at zero, or you have got some other offset incorrect.  They can also happen where you use a function, but don’t understand what it does, or what output it is supposed to produce.

When faced by a logical error, the usual way to debug is to break down the program into different components and to inspect each in turn.   When you’re more advanced, you can put logging messages into the code and Python will log the progress of the program to a separate log file.  For the time being however, the easiest thing to do is to put a print statement in.  You put a number of print statements in and sooner or later one of them will print the wrong thing.  Then you know that the error is before that print statement, but after the previous one.

Others

Usually if you make a mistake it will show up as one of these two errors.  However, sometimes you’ve just made a typing mistake but haven’t realised it – like typing * instead of + somewhere.

Lastly and Most Importantly – Talk to your Teddy

I’ve left the most important bit to last.  When you have a debugging problem that you really just can’t solve, get your teddy bear (I’m serious, get your bear), bring it to the monitor and explain (in words, out loud) to your teddy what the problem is and why it isn’t working.

This is a sure-fire, clinically proven way to solve whatever debugging problem you have.  As I mentioned earlier, I’m absolutely serious about sitting your teddy down beside you and talking (with your voice, not in your head) to it. Now, it doesn’t absolutely have to be a teddy.  It could be a Lego figure, or an action figure, a parent or a sibling (if they’re willing) or even a balloon with a face and ears drawn on in pen.

The important thing is that you explain aloud in words what the problem is, because that helps you identify it for yourself.

Review, Formatting Silly Sentences

Voice Over     Well it’s a straight fight here at Leicester…On the left of the Returning Officer (camera shoes grey-suited man) you can see Arthur Smith, the Sensible candidate and his agent, (camera pans to silly people) and on the other side is the silly candidate Jethro Walrustitty with his agent and his wife.
Officer     Here is the result for Leicester. Arthur J. Smith…
Voice Over     Sensible Party
Officer     30,612…
Jethro Q. Walrustitty…
Voice Over     Silly Party
Officer     32,108.

We have been covering lots of new concepts very quickly. So now is the time to slow down a bit and go over some of the things we’ve already done, to have a look at them in a little more depth. In particular I want to try to give you some direction on how to make these code samples your own.

I also noticed that I did some things in the last tute which we haven’t encountered yet.  In particular, I:

  • assigned a function to an object r = random.randint.  Isn’t Python neat?  You can make these assignments, then the name r points at the function random.randint, so you can use r wherever you use the function.  Sometimes you can use this pattern to make your program run faster (honest!).  However, it might be confusing so I’ll stop it now!
  • used tab characters to format a print out: ‘\t’.  There are some “control” characters which modify the usual output of a set of printed characters.  Of particular interest are ‘\n’ (which makes a new line) and ‘\t’ which adds  a tab space. Try printing some strings with \n and \t in them to see what they do.

In this tutorial we’re going to look at formatting strings. In the previous tute we constructed a string by adding (using the ‘+’ operator) them together. Building strings this way can be a little challenging. Another way to do it, is to use formatting strings. We put place markers into a string, and match the place markers with values to go in there. The place markers are identfied by a % sign in the string, and values are added at the end after  a % sign. Here is a simple example:

>>> print '%s'%'hi'
hi

The string ‘hi’ is inserted in place of the %s (%s (the s is important – not just the % by itself) says insert a string here).  There are a number of different options for formatting other than %s (eg %f) but we’re not going into them here.  Read the documentation for more details.

 >>> print '->%s<-'%'This is the value which is inserted'
 ->This is the value which is inserted<-

We can define the format string and the value differently:

>>> formatString="...tell that to the young people of today, and %s."
>>> print formatString%"they won't believe you"
...tell that to the young people of today, and they won't believe you.

Then you can use the same format string with a different value.  This is useful when you have to print something repetitive, where only a part of it changes:

>>> print formatString%"they will fall about laughing"
 ...tell that to the young people of today, and they will fall about laughing.

You can put multiple values into the format string, but the values you supply must:

  • be in brackets ()  (this makes a thing called a tuple, but we haven’t seen them yet); and
  • have a comma between each of the values; and
  • there must be the same number of values as there are %s in the format string.
So, in the previous tute, we might have done something like this:
 >>> formatString="%s x %s = %s"
 >>> print formatString%(2,2,4)
 2 x 2 = 4

We can also pass variables to be printed as values to the formatting string:

 >>> i=2
 >>> j=3
 >>> print formatString%(i,j,i*j)
 2 x 3 = 6

So here’s the times table again:

 >>> for i in range(12):
 ...    for j in range(12):
 ...       print formatString%(i+1,j+1,(i+1)*(j+1))

Silly Sentences

Let’s adapt the silly sentence example from last tutorial.
Set up:
1. open your text editor,
2. open a new file
3. “save as” this empty file with the name “sillySentences110614.py”, save it in the p4k directory we’ve created to hold our stuff.

Now copy and paste this:

import random
nouns = ["apple","toy car","pumpernickel","dinner","pillow","homework","little finger","house","pencil","letterbox","hamster"]
verbs = ["ate","played","smelled","lost","forgot","dropped","hid","erased","infuriated","planted","ripped up","tripped over","dusted","talked to"]
v = len(verbs)-1
n = len(nouns)-1
numberOfSillySentences = 10
formatString = "I was very taken aback when Mrs Pepperpot %s my %s."

for i in range(numberOfSillySentences):
    verb = verbs[random.randint(0,v)]
    noun = nouns[random.randint(0,n)]
    print formatString%(verb,noun)

And save the file.  Can you see how much easier it is to get the spacing right?

Run the file by going to a console and typing:

> python sillySentences110614.py

Funny?

Now you need to make this code your own.

Exercise: First, change some of the verbs and nouns. You will see the pattern – for verbs you need to substitute doing words, and they need to be in the past – ate rather than eat.  For nouns you need to substitute words which name things.   Save the file, then run the program again from the console with:

> python sillySentences110614.py

Next, we’re going to change it to add some feelings in, but you have to supply the feelings:

Exercise: add some feeling words to the feelings arrray and run again:

import random
nouns = ["apple","toy car","pumpernickel","dinner","pillow","homework","little finger","house","pencil","letterbox","hamster"]
verbs = ["ate","played","smelled","lost","forgot","dropped","hid","erased","infuriated","planted","ripped up","tripped over","dusted","talked to"]
feelings=["very taken aback","very happy","quite sad"] # add more feelings to the array here 
v = len(verbs)-1
n = len(nouns)-1
f = len(feelings)-1 
numberOfSillySentences = 10
formatString = "I was %s when Mrs Pepperpot %s my %s."

for i in range(numberOfSillySentences):
  verb = verbs[random.randint(0,v)]  # choose a verb,noun, feeling  by calculating a random integer
  noun = nouns[random.randint(0,n)]  # between 0 and v,n,f (number of verbs,nouns, feelings)
  feeling = feelings[random.randint(0,f)]  
  print formatString%(feeling,verb,noun)  # substitute into the format string and print

Making this Code Your Own

Shopkeeper No, I’m afraid not actually guv, we’re fresh out of parrots. I’ll tell you what though … I’ll lop its back legs off, make good, strip the fur, stick a couple of wings on and staple on a beak of your own choice. (taking small box and rattling it) No problem. Lovely parrot.

While I’ve been giving you coding examples, I’ve assumed that generalising from them will be self evident.  I understand that that it is not.  In this tutorial we will look at ways of approaching the code with a view to giving you some guidance on how to make this code your own.

The first step is to be able to break the code down into logical units, and then to break those units into subunits.   The second step, once you have broken the code down, is to understand what each of the parts of the code is doing.  If you don’t know what the pieces are doing, it’s hard to change what they’re doing!  The final thing is this:

try, try, try

and:

take notice of what happens when you try, try, try.  Python tries to explain why things didn’t work like you thought they would.  Often you’ve just mistyped something.

Remember this: there is no reason why you can’t be a good programmer, even at a young age.

Remember this also: in all of these tutes there is code which I don’t get right the first time, or have to retype for some reason.

Let’s take an example:

>>> for i in range(10):
...     print i
...
0
1
2
3
4
5
6
7
8
9

This code has two lines, and two parts.  The first part (for i in range (10)) creates a loop.  The second part (print i) does something each time the loop executes.   Next I break the first part down and ask what range() does.   If you don’t know, use help (in my console this gets “paged”, if this happens to you, press ‘q’ when you’re finished reading it):

>>> help(range)
Help on built-in function range in module __builtin__:

range(...)
    range([start,] stop[, step]) -> list of integers

    Return a list containing an arithmetic progression of integers.
    range(i, j) returns [i, i+1, i+2, ..., j-1]; start (!) defaults to 0.
    When step is given, it specifies the increment (or decrement).
    For example, range(4) returns [0, 1, 2, 3].  The end point is omitted!
    These are exactly the valid indices for a list of 4 elements.

Based on the help file, range() gives us a list (of integers). Range tute is here.

Next, I confirm that I understand what it does by trying a concrete example:

>>> print range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

What the for statement does is makes the variable i point to each of these elements in turn (see the print out above).  So, how could we change this? Well, we could either change how the code is looping, or what it is doing in the loop.  Let’s change what it is doing:

>>> for i in range(10):
...   print 10-i ...
10
9
8
7
6
5
4
3
2
1

Now, instead of counting up, the loop counts down.  Let’s change the loop to be a multiplication table:

>>> for i in range(10):
...   print "7 x ",i," = ",7*i
...
7 x  0  =  0
7 x  1  =  7
7 x  2  =  14
7 x  3  =  21
7 x  4  =  28
7 x  5  =  35
7 x  6  =  42
7 x  7  =  49
7 x  8  =  56
7 x  9  =  63

Note: We need to use a comma (,) here in the print statement because i is a number and “7 x ” (etc) is a string.

Exercise: what would we need to change to make this give the times table for 7×1 through to 7×12?

Solution:

In order to start at 1, we need to add 1 to i.  Further for it to run to 12, we need to increase the range by 2:

>>> for i in range(10+2):
...   print "7 x ",i+1," = ",7*(i+1) # increase each i and put brackets in otherwise you'll get 1+ 7 x i
...
7 x  1  =  7
7 x  2  =  14
7 x  3  =  21
7 x  4  =  28
7 x  5  =  35
7 x  6  =  42
7 x  7  =  49
7 x  8  =  56
7 x  9  =  63
7 x  10  =  70
7 x  11  =  77
7 x  12  =  84

Exercise: How would you change this to print out the times table for all numbers from 1 through 12?

Solution:

>>> for i in range(12): ...   for j in range(12):
...     print i+1,"x",j+1," = ",(i+1)*(j+1)  # note the brackets
...   print   # just to make it more readable - try it without the print statement
[output omitted]

What I’ve done here is create two loops, one runs inside the other.  The print out will show you how i and j change in each loop.  See how it looks if you swap i and j in the first two lines:

>>> for j in range(12):
...   for i in range(12):
...     print i+1,"x",j+1," = ",(i+1)*(j+1)
...   print
...
[output omitted]

You can see that there’s an issue here with the length of the output.  So, let’s print two tables each time through the loop, and loop half as many times:

>>> for i in range(6):
...   for j in range(12):
...     print i+1,"x",j+1," = ",(i+1)*(j+1),'\t\t',i+7,"x",j+1," = ",(i+7)*(j+1) 
...   print

Now, I think that that middle of the loop looks cumbersome.  How about we simplify it by making a function:

>>> def xByy(x,y):
...   ''' given two numbers x and y, return a string
...       representing their product.  So, for example
...       for 3 and 4, this function returns a string:
...       "3 x 4 = 12"
...   '''
...   return str(x)+" x "+str(y)+" = "+str(x*y) # x and y are numbers so they need to be converted to strings first - str()
...

Test the function to see it works as you think it should:

>>> print xByy(3,4)
3 x 4 = 12

(3×4 is 12 isn’t it?)  Note, help works even on this newly created function – Try help(xByy)

Now we can “refactor” the code we had earlier:

>>> for i in range(6):
...   for j in range(12):
...     print xByy(i+1,j+1)+'\t\t'+xByy(i+7,j+1) # So, i+1 becomes x and j+1 is y, then i+7 and j+1
...   print

We could change it again to print more of the tables on a line:

>>> for i in range(4):
...   for j in range(12):
...     print xByy(i+1,j+1)+'\t\t'+xByy(i+5,j+1)+'\t\t'+xByy(i+9,j+1)# Adding another column
...   print

And so on.   The other thing we could do is recognise that range() is a list just like any other, so we could equally see what would happen if we used another list:

>>> names = ["John","Eric", "Terry","Graham","Terry","Michael","Carol","Connie","Ian"]
>>> for n in names:
...    print n
...
John
Eric
Terry
Graham
Terry
Michael
Carol
Connie
Ian

Realising this you could mix things up a little:

>>> import random
>>> r = random.randint  # You haven't seen this before, it is me cheating a little to save on typing.
>>> nouns = ["apple","toy car","pumpernickel","dinner","pillow","homework","little finger","house","pencil","letterbox","hamster"]
>>> verbs = ["ate","played","smelled","lost","forgot","dropped","hid","erased","infuriated","planted","ripped up","tripped over","dusted","talked to"]
>>> v = len(verbs)-1
>>> n = len(nouns)-1
>>> for i in range(10):
...   print "I was very taken aback when Mrs Pepperpot "+verbs[r(0,v)]+" my "+nouns[r(0,n)]+"."
...
I was very taken aback when Mrs Pepperpot infuriated my letterbox.
I was very taken aback when Mrs Pepperpot smelled my pumpernickel.
I was very taken aback when Mrs Pepperpot forgot my pencil.
I was very taken aback when Mrs Pepperpot smelled my toy car.
I was very taken aback when Mrs Pepperpot tripped over my house.
I was very taken aback when Mrs Pepperpot played my toy car.
I was very taken aback when Mrs Pepperpot lost my house.
I was very taken aback when Mrs Pepperpot forgot my pillow.
I was very taken aback when Mrs Pepperpot dropped my pumpernickel.
I was very taken aback when Mrs Pepperpot erased my house.

Exercise: What is going on here?  Why have I assigned, v = len(verbs)-1? etc?

Note: r(0,v) is shorthand for random.randint(0,v)

You can run this with a different list of verbs and nouns, just make sure, after you assign or change the verbs/nouns list that you reassign the values of v and n.

Exercise: change this code so that you have a list of feelings one of which is inserted at random – I was very x….

Final note: Syntax Errors

Often things will fail because you have a syntax error, which is computer speak for “you have made a typo”.  It is usually just a case of working through what you did to find the mistake.

Examples:

TypeError: ‘list’ object is not callable – this probably means you’ve used round brackets () when you were supposed to use square ones []

NameError: name ‘some_text’ is not defined – this probably means you are missing inverted commas around something which is meant to be a string “some_text”

TypeError: cannot concatenate ‘str’ and ‘int’ objects – you’ve tried to add a string and a number.  Convert the number to a string using str().

Conclusion

We’ve played around here with some basic building blocks of the language and found we can do some interesting stuff.  We have been able to vary things by breaking the parts of the program down into pieces, understanding what the pieces do, and, based on that understanding, changing them.

A Functioning Stand Alone Python Program

Interviewer:     The Magna Carta – was it a document signed at Runnymede in 1215 by King John pledging independence to the English barons, or was it a piece of chewing gum on a bedspread in Dorset? The latter idea is the brainchild of a man new to the field of historical research. Mr Badger, why – why are you on this programme?
[Pull back to show Mr Badger. He wears a flat cap and has a Scots accent.]
Badger:     Well, I think I can answer this question most successfully in mime. (mimes incomprehensibly)

In the last tutorial we created a file using our text editor and saved a function to it.  This file was called trivia.py and in it was the module “trivia”.  We then started Python in a console and import()ed the trivia module.  Once imported, it created a “namespace” and we could access the askQuestion() function from within the trivia namespace by using a dot – trivia.askQuestion().  In order for the module to work properly we had to include an import statement within the module itself so that everything that the module relied upon was imported within the module.  We then manually loaded our data from a pickle file we created and, manually, ran the askQuestion() function on the first question in our data store.   Finally we added docstrings to the function and the module.

In this tutorial we’re going to try to do much the same thing again, but without using the Python interpreter.  That is, we will need to take the things we did in the interpreter and implement them in our trivia.py file.  We will have a functioning (although still quite simple) stand alone Python program.

To start, open up trivia.py in your text editor.
Looking at the last tute, we can see that we used the cPickle module, so that will need to be imported.  We then opened the existing pickle file, and loaded the stored pickle.

So, first, edit the file to add import cPickle after import random:

import random
import cPickle

Next, let’s add a ‘constant'[1] to store our the filename of our pickle file.  Constants are not really all that constant in Python, in that Python will let us change their value later if we choose.  However, by convention, if you name a variable with all caps, then it is assigned a value only once (usually at the start of the program).  Add this after the imports but before the first definition:

QUESTIONS_FILENAME = 'p4kTriviaQuestions.txt'

Now, we add some code to load the questions into an array.  In theory, this code could go anywhere in the file after the variable QUESTIONS_FILENAME has been given a value.  However, it’s better if we put it at the end of the file after the function definition.  It should not be indented (remember that Python identifies code blocks by indentation):

fileObject = open(QUESTIONS_FILENAME,'r')  # note: 'r' for read
questionsList = cPickle.load(fileObject)  # load the questions
fileObject.close()   # finished reading, so close the file

If you go back through the previous tutes (eg the previous tute) you’ll see that this is the same code we used in the interpreter.   This shouldn’t be surprising because Python runs the code in this program as if it was being typed into the interpreter.[2]  Now that the questions have been loaded, let’s iterate through each of them, asking them in turn:

for question in questionsList:
    askQuestion(question)  
    # note, that because we're within the module we can just
    # use the function's name directly without being qualified by the
    # trivia namespace.  If you put trivia.askQuestion, the code would not work.

Save the file [see below for what the code should look like], then go to a console/command line – ie the thing from which you have run the Python interpreter in earlier tutes.  Do not run the interpreter itself. Rather, we run the file by typing the following and pressing the ‘enter’ or ‘return’ key:

> python trivia.py
Who expects the Spanish Inquisition?
0 .  Brian
1 .  Eric the Hallibut
2 .  An unladen swallow
3 .  Nobody
4 .  Me!
Enter the number of the correct answer: 3
Correct! Hooray!
What is the air-speed velocity of an unladen swallow?
0 .  23.6 m/s
1 .  10 m/s
2 .  14.4 m/s
3 .  What do you mean? African or European swallow?
Enter the number of the correct answer: 3
Correct! Hooray!
Is this the right room for an argument?
0 .  Down the hall, first on the left
1 .  No
2 .  I've told you once
3 .  Yes
Enter the number of the correct answer: 2
Correct! Hooray!

Note that this is from the command line – not the Python interpreter.  You can tell because there is only one > (you might have something different as your > thingamy) where the interpreter has three >>>

As you can see the two lines:

for question in questionsList:
    askQuestion(question)

Cause the program it iterate through each element in the list questionsList and, for each element, to call the askQuestion() function with that element as a parameter.  If you remember, each of these elements is, itself, a list.

Homework:  think about how we would need to change this program to keep track of the player’s score

Bonus points: actually change the program so that it keeps track of the player’s score and prints out the score after all questions have been asked.

Notes:

1.  Technically, in the code:

QUESTIONS_FILENAME = ‘p4kTriviaQuestions.txt’,

QUESTIONS_FILENAME is a variable, and ‘p4kTriviaQuestions.txt’ is the constant.  However, in the text I’m referring to the variable as if it was the constant.

2.  Actually, when you run a program from the command line Python reads the whole file and pre-compiles it first (or uses an existing pre-compiled version if you haven’t changed the file) then runs the pre-compiled version.  Have a look for file called trivia.pyc in your directory.

Source Code

The complete file should look like this:

'''
The place for a doctring which was an exercise for you to complete in the previous tute.
'''

import random
import cPickle

QUESTIONS_FILENAME = 'p4kTriviaQuestions.txt'

def askQuestion(questionList):
    '''
    Given a question in a form of a list, with the first entry being the question,
    the next entry being the correct answer to the question and one or more other
    entries, each of which is an incorrect answer, pose the question, randomising the
    answers and test whether the answer is correct
    '''
    question = questionList[0]
    answers = questionList[1:]
    numberOfAnswers = len(answers)-1
    # -1 because the first entry in the list is number 0
    correctAnswer = random.randint(0, numberOfAnswers)
    # choose an answer at random
    # then swap it with the correct answer
    spam = answers[correctAnswer]
    answers[correctAnswer] = answers[0]
    answers[0] = spam
    print question
    for i in range(len(answers)):
        print i,'. ',answers[i]
    answer = raw_input('Enter the number of the correct answer: ')
    if answer == str(correctAnswer):
        print 'Correct! Hooray!'
    else:
        print 'Wrong...'

fileObject = open(QUESTIONS_FILENAME,'r')  # note: 'r' for read
questionsList = cPickle.load(fileObject)  # load the questions
fileObject.close()   # finished reading, so close the file

for question in questionsList:
    askQuestion(question)  
    # note, that because we're within the module we can just
    # use the function's name directly without being qualified by the
    # trivia namespace.

Baby Steps with Our Text Editor

Second Interviewer     I didn’t really call you Eddie-baby, did I, sweetie?
Ross     Don’t call me sweetie!!
Second Interviewer     Can I call you sugar plum?

In this tutorial we’re going to start using the power of the text editor that I’ve been badgering you about over the last few tutorials.  We are going to go back to the earlier tutorials and resurrect some of the code there.

Exercise:
Create a new document with your text editor
Save the document to a file called trivia.py  You need to be careful that your text editor doesn’t add ‘.txt’ to the end of the file’s name as some have the habit of doing.
copy the following function into the file (originally found here, with some comments dropped):

def askQuestion(questionList):
    question = questionList[0]
    answers = questionList[1:]
    numberOfAnswers = len(answers)-1
    # -1 because the first entry in the list is number 0
    correctAnswer = random.randint(0, numberOfAnswers)
    # choose an answer at random
    # then swap it with the correct answer
    spam = answers[correctAnswer]
    answers[correctAnswer] = answers[0]
    answers[0] = spam
    print question
    for i in range(len(answers)):
        print i,'. ',answers[i]
    answer = raw_input('Enter the number of the correct answer: ')
    if answer == str(correctAnswer):
        print 'Correct! Hooray!'
    else:
        print 'Wrong...'

save the changes you’ve just made to the file
It should look something like this (notice the code highlighting in Kate.  A basic text editor like NotePad won’t do this highlighting for you):

now, start a python prompt from the command line and import the module: (note: for this to work you must be in the same directory as the trivia.py file when you start the Python prompt Google “cd change directory” if you have problems)

>>> import trivia
>>> dir(trivia)
['__builtins__', '__doc__', '__file__', '__name__', 'askQuestion']

Can you see that we’ve imported our trivia file at the Python prompt (note: not import trivia.py, Python automatically adds the .py when it’s looking for the module).  We’ve used introspection to see its attributes and methods – you can see the askQuestion() function there.  We can’t actually ask a question at the moment, because we don’t have a question to ask, so let’s recover those we were storing that pickle file from earlier tutes:

>>> import cPickle
>>> filename = 'p4kTriviaQuestions.txt'
>>> fileObject = open(filename,'r')
>>> triviaQuestions = cPickle.load(fileObject)
>>> fileObject.close()
>>> triviaQuestions
[['Who expects the Spanish Inquisition?', 'Nobody', 'Eric the Hallibut', 'An unladen swallow', 'Brian', 'Me!'], ['What is the air-speed velocity of an unladen swallow?', 'What do you mean? African or European swallow?', '10 m/s', '14.4 m/s', '23.6 m/s'], ['Is this the right room for an argument?', "I've told you once", 'No', 'Down the hall, first on the left', 'Yes']]
>>> triviaQuestions[0]
['Who expects the Spanish Inquisition?', 'Nobody', 'Eric the Hallibut', 'An unladen swallow', 'Brian', 'Me!']

Now, let’s get a single question, because that’s what the askQuestion() function is expecting:

>>> question = triviaQuestions[0]

So, in theory we should be ready to run this through our askQuestion() function.  Let’s try it:

>>> trivia.askQuestion(question)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "trivia.py", line 6, in askQuestion
    correctAnswer = random.randint(0, numberOfAnswers)
NameError: global name 'random' is not defined

Uh-oh, it didn’t work because we haven’t imported the random module. Gosh darn it.  Let’s add that at the start of our file so the first couple of lines now look like this:

import random

def askQuestion(questionList):
    question = questionList[0]
    answers = questionList[1:]

Now we go back to the Python command line and import it again:

>>> import trivia
>>> trivia.askQuestion(question)

Unfortunately, this also fails exactly as it did before – even though we know we’ve imported the random module.  The reason is that import only happens once.  If you change a module, then import it a second time, it doesn’t do anything.  However, we can update a module by using the reload() function (you can also exit the Python prompt, restart Python and then import, but reload() is easier).  Once we do that, things start working as we expect them to:

>>> reload(trivia)
<module 'trivia' from 'trivia.py'>
>>> trivia.askQuestion(question)
Who expects the Spanish Inquisition?
0 .  An unladen swallow
1 .  Eric the Hallibut
2 .  Nobody
3 .  Brian
4 .  Me!
Enter the number of the correct answer: 2
Correct! Hooray!

So, some things to notice:
* when we imported our module, we imported the name of the file less the ‘.py’
* on import it created a ‘namespace’ called trivia.  The function we defined was in the trivia namespace and we accessed it using a dot: trivia.askQuestion().  Had we just typed in askQuestion() the computer would not know what we are talking about:

>>> askQuestion(question)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'askQuestion' is not defined

We can import functions individually using a different syntax and, if we do, the function’s name is present in the base namespace and it can be called directly:

>>> from trivia import askQuestion
>>> askQuestion(question)

However, doing this is frowned upon because it ‘pollutes’ the namespace.  In particular, you may end up with collisions between functions imported into the namespace.  So if you have two modules, each with the a function of the same name, but of different effect the interpreter would have a problem if you from imported both of them.  Try to avoid using the import from syntax at least until you’re familiar with why it’s a problem.

Something our function is missing is a doc string.  Let’s add one.  Go back to your text editor and edit the function so now it reads:

def askQuestion(questionList):
    '''
    Given a question in a form of a list, with the first entry being the question,
    the next entry being the correct answer to the question and one or more other
    entries, each of which is an incorrect answer, pose the question, randomising the
    answers and test whether the answer is correct
    '''
    question = questionList[0]
    answers = questionList[1:]

Save the file
Go to the Python prompt and reload() the module, then look at the docstring:

>>> reload(trivia)
<module 'trivia' from 'trivia.py'>
>>> print trivia.askQuestion.__doc__

        Given a question in a form of a list, with the first entry being the question,
        the next entry being the correct answer to the question and one or more other
        entries, each of which is an incorrect answer, pose the question, randomising the
        answers and test whether the answer is correct

Now, the important thing to note is that we were able to change part of the function without retyping all of it.  Going forward we will be able to extend this module by adding bits to it.

Homework:
Create a docstring for the trivia module itself, describing what the trivia module will be doing (make a list of the things you think it should eventually do in order to make a trivia game).  Work out where the docstring needs to go in trivia.py.  Save the file, then reload the module from the Python prompt and print the docstring out.

Homework:
Think about what part of the Python language we might use to ask all of the questions stored in the pickle file, rather than just the first one.

Catching Our Breath

Character     Servant ho!
He then goes underwater again. The servant in the boat steps into the water and goes under. Cut to announcer, now up to his waist in sea.

In this tutorial we review what we have covered over the last few tutorials, and lay some more groundwork for working with files and using the text editor that you organised in the last tutorial.

In recent tutorials we’ve learned quite a bit:

  • pickling basics: we learnt about a way of storing data called “pickling”. We did some basic pickling. Pickles are a way of storing data. This means that, in our trivia quiz, we’ll be able to add questions from time to time, without having to retype all of the questions we have added earlier.
  • pickling data: we took our understanding of pickles and applied it to our data. We saw how we can save the data that we were using for our game into a file for later use.
  • introspection: Python objects are able to tell you about themselves. In particular we learnt about the .__doc__ string and the dir() functions. Introspection allows us to find out stuff about the program we are working with. In some cases it can be a sort of help file by telling us some stuff about an object. Introspection is part of Python ‘for free’. It is available whenever you run Python without having to (eg) import introspection in the way we imported cPickle.
  • more on pickles: how to load data that’s been stored in a file, modify it and dump it back to the file. So we can keep data that our programs rely on in a file. The point of this tutorial was to show how the pickle can be used like a locker for your data. If you put your data into a pickle not only will it be there for you to take out later, but you can take it out, change it, then replace the old data with the new data.
  • dictionaries. Dictionaries are a little like lists, except you can map a given value to an index. In the homework, your example was to map names of your friends to their favourite game). Unlike lists, which access values by knowing their position in the list, dictionaries access values by giving the value a name, called a ‘key’. In order to use a list, you are dependent upon knowing the order of the items in the list. This can cause problems if you want to change the sort of data stored in the list. In this tutorial we saw that we couldn’t add an alternative correct answer because there was literally nowhere in our list to store it (because we’d earlier decided to consider all entries in the list after a certain point to be incorrect answers. Adding an alternative correct answer would invalidate all the existing data (eg every question that you had already written to the pickle file). Because dictionaries access values through keys they do not suffer from this problem. Position is not important for dictionaries. If you have data stored in a dictionary, then to add new categories of answer is a simple question of adding a new key to identify the new category of data; and, finally,
  • in the most recent tutorial, I told you to go get a text editor and learn the basics of how to use it. Using a text editor will allow us to store the Python code that we are writing in much the same way as we have been saving data in the other tutorials. One of the benefits of this is that we don’t need to retype code all the time. If we want to modify a function we just open the text editor and modify it – without having to retype it from scratch.

Most of the things we’ve covered here have to do with files and (therefore) the file system on your computer. In order to do further tutorials we’re going to be changing the file system, and if we do that we’ve got a problem in that we may end up deleting or changing something we didn’t want to. For that reason I’d like you to talk to a responsible adult and set up a separate folder on your hard drive specifically for these tutorials. Call it python4kids (or p4k if you are a lazy typist).

Homework: have a responsible adult set up a separate folder (also called a directory) for these tutorials.  Name it python4kids or p4k.

Homework: work out (ask someone or Google) how to change directory into this new folder so that any files you make are made here. Whenever you run Python change directory to this folder first, so that Python runs in the directory.

Homework: work out how to load files from this directory using your text editor and how to save files to this directory using your text editor.

Homework: Copy the pickle file we’ve been working with to this new directory. If you have not made any or many additions to it, you can just recreate it by running the tutorials in the directory.

If you do these things and whenever you do these tutorials you do them in that folder, saving files to that folder, then I don’t have to worry about accidentally overwriting your other data, because the only data that will be there is data we’ve created in these tutorials.

In terms of Python language features – so far we have covered quite a lot of them. We know about:

  • data: strings, numbers (integers), lists and dictionaries
  • controlling program flow: if/then/else, while, for
  • some regularly used in built functions: range, len
  • subroutines: def (functions), modules, import
  • other language features: objects, introspection

There is a big one which is still missing – classes.  We will get to classes in a few more… classes. Till then… do your homework.

Keeping Code

Well, so far we’ve learnt about storing data (either “flat” in file, or as an object which Python does in pickles which are also data in files, but with some structure) but we don’t know anything about storing the code we are typing. This means that we need to go through the tedium of typing stuff in all the time (or cutting and pasting I guess).  It  is especially tedious when we mistype something.

In order to do some more complex things in Python we really need to be able to store our code somewhere so that typing mistakes we make don’t mean we have to re-type the whole kit and kaboodle. We could, if we really wanted to, open a file from within our Python prompt and write code to the file (as a flat file):

 >>> f = open('textfile.py','w')
 >>> f.write("print 'This is some python code stored in a file'")
 >>> f.close()
 >>> import textfile
 This is some python code stored in a file

What has happened here is that we’ve open()ed a file called ‘textfile.py’ (again, we’ve taken the chance that there isn’t already such a file, because open()ing it would delete the existing contents).  We’ve then written the following to the file: print ‘This is some python code stored in a file’

We can tell what’s in the file by opening it and reading it back:

>>> f = open('textfile.py','r')
>>> print f.read()
print 'This is some python code stored in a file'
>>> f.close()

Then, when we imported the file, the contents of the file were executed [1].  We can execute this file from the command line (not the Python prompt) using the python command:

>python textfile.py
This is some python code stored in a file

Note only one >, not three.

Why did we put double quotes? See note 2 below.

Now, using Python to write Python code to a file would be incredibly tedious.  Moreover, it wouldn’t solve the problem we have with the ease of editing.  So now we’re going to talk about text editors.  You may need a responsible adult to help you here.

Text editors are programs which are designed to, well, edit text.  They are generally nothing like word processors, and, for our purposes, you can’t use a word processor as a text editor (see note [3]).  There are way heaps lots of text editors.  Everyone is welcome to list their favourite in comments if they feel inclined.

When you use a text editor, there is a different cycle of work.  To date, feedback has been instant because our code has been typed directly into the Python interpreter.  However, when using a text editor your code must first be saved to a file, then you need to execute the file using the python command.  However, once you’ve saved a file you can easily modify it and re-run it.  If you mistyped a single character, then you can use the text editor to navigate to that character and change just it, then save the file and run it through the Python interpreter again.

For the following tutes you need to complete these exercises:

Exercise: See if a text editor is already installed on your computer (try Notepad on Windows, Linux systems should have Gedit and/or Kate)

Exercise: If you don’t have a text editor installed, get the permission of a responsible adult, go find a text editor and install it on your computer.

Exercise: Learn how to use it to open files (usually ctrl+O), and save files (usually ctrl+S).

Exercise: using your new found text editor skills, open a new file and save the following to it:

print "Hello world!"

Then, open a command line, cd to the directory in which you saved the file, then run the file by typing python <name of file>.  Find a responsible adult to help if you can’t work out the cd command.

Hints:

A text editor that I like is called Kate.  Kate needs a windowing system to run, but is available on all popular operating systems. One of the neat things about Kate is that it will do Python syntax highlighting for you.  That is, it will use different colours to show (eg) what are strings, what are commands etc.  Kate also has a command line within the editor, which may make life a little easier for you.  Kate will also automatically indent your code.  This is a useful feature to have and saves you typing in tab characters or spaces to line up code blocks.  This is what the file we saved earlier looks like in Kate, showing code highlighting (the red part shows a string, the black part a command):

A basic editor which works on the command line is called nano.

If you want to make life hard for yourself, but want to be able to swagger as if you were a Real Programmer(tm) try emacs or vi.  Warning: don’t try emacs or vi unless you are brave, neither is for the faint hearted.

There is a text editor which often accompanies Python distributions (it is called idle).  However, at the moment I don’t want to use idle, because it may cause problems down the track.

Notes:

[1] Python has its own function to execute strings as if they were instructions.  It’s called exec(), and that’s happened. Example:

>>> a = "print 'This is some python code stored in a file'"
>>> a
"print 'This is some python code stored in a file'"
>>> exec(a)
This is some python code stored in a file

Warning: there are security issues with using exec() don’t try this at home.
[2]  That is a bit subtle.  We had to put what was written into a string in order to write it.  We’ve had to use double quotes to make the command we want stored into a string.  Had we left the double quotes out we would have had a problem:

 >>> f = open('textfile.py','w')
 >>> f.write(print 'This is some python code stored in a file')
 File "<stdin>", line 1
 f.write(print 'This is some python code stored in a file')
 ^
 SyntaxError: invalid syntax

When we put the double quotes in, it’s not a command anymore, it’s just a string, and we can write strings to files.  However, we wanted to write an actual command to the file – the print command, and for that we needed to use single quotes.  We needed to use double quotes so that the single quotes would be made part of the string.

[3]  If you are careful to use “save as” and always choose “plain text” as the save format, then you can use a word processor, but they are best avoided.

Dictionaries, Hovercraft, Eels

Tobacconist     What?
Hungarian     (miming matches) My hovercraft is full of eels.
Tobacconist     Matches, matches? (showing some)

Dictionaries, Hovercraft, Eels

Welcome to the new look site (which, due the wonder of CSS, has been retrospectively applied to all the earlier tutes)!  This was chosen because it was the first template I found which allowed you to see the whole of the code snippets.

In our earlier tutes we needed to come up with a way to keep track of questions in our trivia game.  We chose to use a list to do that (because that was the only data structure we knew of), but there was another option open to us – a dictionary:

>>> aDictionary = {}
>>> aDictionary
{}

What this code does is create a dictionary which is empty.  We can use the introspection trick we learned in the second last tute to find out a little about our dictionary:

>>> print aDictionary.__doc__
dict() -> new empty dictionary.
dict(mapping) -> new dictionary initialized from a mapping object's
(key, value) pairs.
dict(seq) -> new dictionary initialized as if via:
d = {}
for k, v in seq:
d[k] = v
dict(**kwargs) -> new dictionary initialized with the name=value pairs
in the keyword argument list.  For example:  dict(one=1, two=2)

Homework: try dir(aDictionary)

So, a “dictionary” has “(key, value) pairs” apparently.  What are these things?  You can think of dictionaries as a generalisation of lists.  In our earlier examples we got the contents of the lists by a number.  The number 0 was used to store the question in the trivia list (if you’ve forgotten it was in this tute).  In that case the number 0 was the “key”, and the text of the question was the “value”. A list only allows you to use numbers as keys. However, dictionaries don’t limit you in that way.  This means that you can give meaningful names to your keys. Let’s add something to our dictionary:

>>> aDictionary["I would like to buy some matches"] = "My hovercraft is full of eels"
>>> aDictionary
{'I would like to buy some matches': 'My hovercraft is full of eels'}

Here the “key” is the string ‘I would like to buy some matches’.  The value is also a string‘My hovercraft is full of eels’. The print out indicates that the object aDictionary is a dictionary by enclosing the output in curly braces -> {}.  Note also that we use the key to reference a value in the dictionary by using square brackets [] in much the same way as we used square brackets to identify members of the list in earlier tutes.

The key ‘I would like to buy some matches’ is a somewhat unusual key. How about we add another (key, value) pair:

>>> aDictionary['it'] = 'The word the Knights of Ni! cannot stand'
>>> aDictionary['Monty Python'] = "A famous comedy troupe from Britain, most active in the 70s and early 80s and on whom the name of the programming language Python is based."
>>> aDictionary
{'I would like to buy some matches': 'My hovercraft is full of eels', 'Monty Python': 'A famous comedy troupe from Britain, most active in the 70s and early 80s and on whom the name of the programming language Python is based.', 'it': 'The word the Knights of Ni! cannot stand'}

Here, aDictionary is acting how you would normally expect a dictionary (no italics, here I mean the sort of dictionary you’d find on a bookshelf) to operate – give it a word (‘it’), and it gives you a meaning for the word.  You should also note here that there is no necessary ordering of a dictionary*1 – in this case ‘it’ comes after ‘Monty Python’ in the print out even though alphabetically ‘it’ should come first.

Dictionaries allow us to set up a direct relationship between the key and the value. In theory, dictionaries give you a speed advantage for looking up values, especially for a large number of keys (this is a result of how dictionaries are implemented).  The reason I use dictionaries is that they usually give me a better way to remember what I am talking about.  So for example, we used sampleQuestion[1] to refer to the correct answer in our list. However, why should ‘1’ identify the correct answer?  If it was a dictionary, rather than a list, we could, instead, have written sampleQuestion[‘correctAnswer’].  This takes more typing, but in the long run is better because it is clearer what is going on with the code.

That said, lists can be massaged to address this readability issue.  One defines constants with a meaningful name, and then refers to elements by the constants:

CORRECT_ANSWER = 1
sampleQuestion[CORRECT_ANSWER]  # (Same as sampleQuestion[1])

By convention, if a variable is to be used as a constant (ie with a value which does not change) it is given a name consisting of all UPPERCASE LETTERS, as we have done here.  If all the letters are uppercase, there’s no ability for a change in case to indicate a separate word in the name, so we use underscores _ to add meaning.

However, dictionaries still provide some other advantages.  If you ever needed to change your data structure, you would be affected by the ordering of the list. In the data structure we’ve adopted for our trivia game, you could not, for example, include an alternative correct answer because there is literally nowhere to put it in the list*2 – the first entry is the question, the second entry is the right answer and the third and subsequent entries are wrong answers.  You can’t put it at the end, because that’s where wrong answers go and there’s nowhere else left up front.  However, if sampleQuestion was a dictionary, you just add another (key,value) pair (the key might be “alternativeCorrectAnswer”).  Importantly this would not break any existing code which processed a dictionary with existing data.<- in bold because it’s important!  Existing code would look for one of the existing keys, but would not be troubled by the existence of a new key.

That said, the main reason I use dictionaries is not explicitly for this flexibility or for the potential speed advantage.  Rather, I find the concept easy to understand in the first place, and I also find it more easy to understand code which I have written previously if I’ve used a dictionary.

You can get a list of keys in a dictionary (and a list of values):

>>> aDictionary.keys()
['I would like to buy some matches', 'Monty Python', 'it']
>>> aDictionary.values()
['My hovercraft is full of eels', 'A famous comedy troupe from Britain, most active in the 70s and early 80s and on whom the name of the programming language Python is based.', 'The word the Knights of Ni! cannot stand']
>>>

More freaky however, is that you can iterate over all of the keys if you want to:

>>> for k in aDictionary.keys():
...     print "The key is ->",k,"\nand its associated value is ->",aDictionary[k]
...
The key is -> I would like to buy some matches
and its associated value is -> My hovercraft is full of eels
The key is -> Monty Python
and its associated value is -> A famous comedy troupe from Britain, most active in the 70s and early 80s and on whom the name of the programming language Python is based.
The key is -> it
and its associated value is -> The word the Knights of Ni! cannot stand

Is that cool?  In this code each key in aDictionary is assigned in turn to the variable k, so the value corresponding to that key can be accessed by aDictionary[k].

I think it’s way cool.

Homework: make a dictionary which has the name of your friends as keys, and the name of the friend’s favourite game as the value of the key.  Hint: the .__doc__ printout above shows a fast way to create a dictionary populated with keys eg: faveGames = dict(Arthur=”chess”,Hildegard=”Cortex Command”)

PS:

Here’s a site which translates “my hovercraft is full of eels” into various languages

Notes:
*1 Python has another object which will give you an ordered dictionary, but we don’t need it at the moment.

*2 You could, if you were desperate, but it would take a bit of effort, and/or not work with (ie ‘break’) your existing data.

Increasingly Trivial Questions

First Hermit     Hello, are you a hermit by any chance?
Second Hermit     Yes that’s right. Are you a hermit?
First Hermit     Yes, I certainly am.
Second Hermit     Well I never. What are you getting away from?
First Hermit     Oh you know, the usual – people, chat, gossip, you know.
Second Hermit     Oh I certainly do – it was the same with me. I mean there comes a time when you realize there’s no good frittering your life away in idleness and trivial chit-chat.

If you remember back a few tutorials ago, we learnt how to pickle objects so that we could get them back later.  To pickle the object triviaQuestions (which was a list object) we first imported the pickle module, then opened a file and dumped the object into the file, then close()d the file:

->code snippet removed to note [1] because I don't want you typing it in<- 

Note here that when we opened the file with ‘w’ this meant we were ‘w’riting to it.  If the file existed, then Python wiped it ready for us to write something new to it. You need to be careful when reading from a file that you have ‘r’ (for ‘r’ead) in the open() command, otherwise, instead of reading the data, you’ll wipe it instead!!!!
Let’s load the triviaQuestions list:

>>> import cPickle
>>> filename = "p4kTriviaQuestions.txt"
>>> fileObject = open(filename,'r')  # note the 'r' for 'read'
>>> triviaQuestions = cPickle.load(fileObject)
>>> len(triviaQuestions)
2
>>> triviaQuestions[0]
['Who expects the Spanish Inquisition?', 'Nobody', 'Eric the Hallibut', 'An unladen swallow', 'Brian', 'Me!']
>>> triviaQuestions[1]
['What is the air-speed velocity of an unladen swallow?', 'What do you mean?  African or European swallow?', '10 m/s', '14.4 m/s', '23.6 m/s']

Wow! We have preserved the list between tutorials. So, rather than continually typing out all the questions every time, we only have to add new questions.

In this code we’ve imported cPickle. We mentioned cPickle earlier.  It does (for our purposes) the same thing as pickle, only it does it faster.  cPickle and pickle behave  little differently in some special circumstances, but that’s not relevant for us.  We also used ‘r’ rather than ‘w’ when we opened the file.  Also, cPickle doesn’t care about the name the object had when it was dump()ed.  We could have loaded the pickle into an object with a different name:

>>> fileObject.close()
>>> fileObject = open(filename,'r')
>>> aList = cPickle.load(fileObject)
>>> fileObject.close()
>>> aList == triviaQuestions
True

We had to first close() the fileObject so that cPickle read from the start of the file.  If there are multiple objects pickled to a file it loads them out in the order they were dumped in.  So once it load()s an object cPickle has moves to the end of the object in the file in order to read the next object.  If there is only one object, then cPickle will be at the end of the file and won’t be able to read any more.

As we can see, we’ve load()ed the same object under a different name (aList).  You should also note here that when an object is load()ed, it doesn’t change the file (unlike when an object is dump()ed).  We could load multiple copies of the object for ever if we wanted to.
Let’s add another question and then dump the list to the file:

sampleQuestion = []
# this clears the earlier entries
# if we append without doing this
# we'll have multiple questions in the wrong list
# first the question
sampleQuestion.append("Is this the right room for an argument?")
# then the correct answer
sampleQuestion.append("I've told you once.")
# now one or more incorrect answers
sampleQuestion.append("No")
sampleQuestion.append("Down the hall, first on the left")
sampleQuestion.append("Yes")

now we append() this question to the end of the list:

triviaQuestions.append(sampleQuestion)

Finally, we dump the object back into the file.  Be careful – when we open the file to write (‘w’) we lose what’s saved in it.  That’s no problem if we fill it back up with data, so be sure to dump the question list!

>>> fileObject = open(filename,'w')  # data that was in the file is now gone!
>>> cPickle.dump(triviaQuestions,fileObject)   # ok, put new data in
>>> fileObject.close()

If you were worried about losing your data you would first dump() the object to a file with a temporary name, then when that was successful, rename the existing file as ‘p4kTriviaQuestions.bak’  and the newly created file to ‘p4kTriviaQuestions.txt’.  Then if anything went wrong with saving the data you would still have the data backed up in the “.bak” file.

Homework:

  • add some more questions to your triviaQuestions list by load()ing the list from your file, adding questions, then dump()ing the list back to the file. Make sure you use ‘r’ when you open a file you are going to load() from and ‘w’ when you open a file to dump() to.

Notes:

[1] This is repeated from the penultimate tute (or antepenultimate if you are counting this tute):

>>> import pickle  
>>> # This is just a flashback don't retype this code because it will wipe your data!!!!
>>> filename = "p4kTriviaQuestions.txt"
>>> fileObject = open(filename,'w')
>>> pickle.dump(triviaQuestions,fileObject)
>>> fileObject.close()

 

Time for Some Introspection

“…The illusion is complete; it is reality, the reality is illusion and the ambiguity is the only truth. But is the truth, as Hitchcock observes, in the box? No there isn’t room, the ambiguity has put on weight. The point is taken, the elk is dead, the beast stops at Swindon, Chabrol stops at nothing, I’m having treatment and La Fontaine can get knotted.”

Did you notice some errors in the previous tutorial? One was fatal.  The fact that no one commented on them indicates to me that no one is actually typing in the code – naughty naughty!  Type it in.  It’s important.

The errors have been corrected now, but they were:

pickle.dump(fileObject, triviaQuestions)

(the order of the arguments is wrong, the object to dump goes first, and the file object to dump it into goes next); and
there was a stray full stop at the end of one line.

If you typed in the previous tutorial you should have received the following error:

>>> pickle.dump(fileObject,triviaQuestions)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib64/python2.6/pickle.py", line 1362, in dump
Pickler(file, protocol).dump(obj)
File "/usr/lib64/python2.6/pickle.py", line 203, in __init__
self.write = file.write
AttributeError: 'list' object has no attribute 'write'

Or something like it – the exact error may be different depending on what version of python you are running.

If you receive an error like this you can always use the interpreter’s built in help function to assist:

>>> help(pickle.dump)
Help on function dump in module pickle:

dump(obj, file, protocol=None)

This is not entirely enlightening, but it does tell you that the order of the  arguments – the object first, followed by the file second, followed by a third, optional, argument (protocol).  We know it is optional because it is assigned a default value.

The object itself is also able to tell you about itself.  This is called “introspection”.  In English introspection means looking inward.  People who are introspective spend time thinking about themselves.   In Python, introspection is the ability of the program to examine, or give information about, itself.   For example, try this:

>>> print pickle.__doc__
Create portable serialized representations of Python objects.
See module cPickle for a (much) faster implementation.
See module copy_reg for a mechanism for registering custom picklers.
See module pickletools source for extensive comments.
Classes:
Pickler
Unpickler
Functions:
dump(object, file)
dumps(object) -> string
load(file) -> object
loads(string) -> object
Misc variables:
__version__
format_version
compatible_formats

This shows the “docstring” for the pickle module.  Docstring is a string which holds documentation about the object.   We have learnt from the docstring that pickle has methods for dumping object to strings as well as files.   Any object can have a docstring, for example, our triviaQuestions list had one [if you redo the previous tute to reconstruct it, since we haven’t instantiated it this time]:

>>> triviaQuestions.__doc__
"list() -> new empty list\nlist(iterable) -> new list initialized from iterable's items"

In this case, the docstring is the same for all lists (try [].__doc__).  However, some objects, particularly classes (which we haven’t met yet) and functions, are able to have their own docstrings which are particular to that object.   A docstring can be created for an object by adding a comment in triple single quotes (”’) at the start of the object’s definition (other comment forms like single quotes work, but triple single quotes are the convention so that you can include apostrophes etc in the docstring):

 >>> def square(x):
...     '''Given a number x return the square of x (ie x times x)'''
...     return x*x
...
>>> square(2)
4
>>> square.__doc__
'Given a number x return the square of x (ie x times x)'

When you write code you should also write docstrings which explain what the code does.  While you may think you’ll remember what it does in the future, the reality is that you won’t!

How did I know that pickle had it’s own docstring?  Well, I read it somewhere, like you read it here.  However, if you ever find yourself needing to work out what forms part of an object Python has a function to do it – it’s called dir().  You can use it on any object.  Let’s have a look at it on the square() function we just made up:

>>> dir(square)
[‘__call__’, ‘__class__’, ‘__closure__’, ‘__code__’, ‘__defaults__’, ‘__delattr__’, ‘__dict__’, ‘__doc__’, ‘__format__’, ‘__get__’, ‘__getattribute__’, ‘__globals__’, ‘__hash__’, ‘__init__’, ‘__module__’, ‘__name__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘func_closure’, ‘func_code’, ‘func_defaults’, ‘func_dict’, ‘func_doc’, ‘func_globals’, ‘func_name’]

I bet you didn’t realise that the function we just defined now had so many attributes/methods!!  You can see that __doc__ is one of them.  Where an attribute starts with two underscores ‘__’ it’s got a special meaning in Python.   You can pronounce the two underscores in a number of different ways including: “underscore underscore”, “under under”, “double underscore”, “double under” and, my favourite, “dunder”.

To tell whether these are methods (think functions) rather than attributes (think values) you can use the callable() function:

>>> callable(square.__repr__)
True
>>> callable(square.__doc__)
False

If it is callable, then you can add parentheses to it and treat it like a function (sometimes you will need to know what arguments the callable takes):

>>> square.__repr__()
'<function square at 0x7f0b977fab90>'

The __repr__() method of an object gives a printable version of the object.

When something goes wrong with your program you can use Python’s introspection capabilities to get more information about what might have gone wrong and why.  Also, don’t forget to check the Python docs!

Homework:

  • go over previous tutes and identify 3 objects
  • for each of these objects:
    • re-do the relevant tute to instantiate (ie create) each of these objects;
    • look at the docstring for the object (print objectName.__doc__); and
    • look at the directory listing for the object (print dir(objectName)).
  • Extra marks:
    • find some callable methods in one listing and call them.

A Big Jar of Pickles

Pither (voice over) As I lay down to the sound of the Russian gentlemen practising their shooting, I realised I was in a bit of a pickle. My heart sank as I realised I should never see the Okehampton by-pass again…

In the last tutorial we learned how to pickle our objects.  Pickling is a way of storing the object (on the computer’s file system) so that it can be used later.   This means that if we want to re use an object we can simply save it and load it when we need it, rather than re-creating it each time we want to use it.  This is very useful when our object is a list of questions for our trivia game.  We really only want to type the questions in once and then reload them later.

Now we need to settle on a way to structure our data.  We saw in our earlier tutorial that each question was a list, and that the list itself had a certain structure.  We also need to think about how a number of questions will be stored.  We will use a list to do that as well!  In this case we will have a list of questions.  Each of the elements in the list will itself be a list.  Let’s build one.  First we make an empty list to store all the questions:

triviaQuestions=[]

It is empty:

len(triviaQuestions)

Next, let’s make a sample question to add to that list.  Feel free to use your own question/ answers if you want to use your own topic:

sampleQuestion = []

Now, we populate the sample question:

sampleQuestion.append("Who expects the Spanish Inquisition?")
# first entry must be the question
sampleQuestion.append("Nobody")
# second entry must be the correct answer
sampleQuestion.append("Eric the Hallibut")
sampleQuestion.append("An unladen swallow")
sampleQuestion.append("Brian")
sampleQuestion.append("Me!")
# any number of incorrect answers can follow
# but they must all be incorrect

There are 6 elements in the sampleQuestion list:

len(sampleQuestion)

Now, we add the sample question (as the first entry) to the list of trivia questions:

triviaQuestions.append(sampleQuestion)

It now has one question in it:

len(triviaQuestions)

To add more questions we “rinse and repeat”:

sampleQuestion = []
# this clears the earlier entries
# if we append without doing this
# we'll have multiple questions in the wrong list
sampleQuestion.append("What is the air-speed velocity of an unladen swallow?")
sampleQuestion.append("What do you mean?  African or European swallow?")
sampleQuestion.append("10 m/s")
sampleQuestion.append("14.4 m/s")
sampleQuestion.append("23.6 m/s")

triviaQuestions.append(sampleQuestion)

Now, the sampleQuestion has five entries and there are two questions in total:

len(sampleQuestion)
len(triviaQuestions)

Now we need to save the question list so we can use it again later.  We will save it to a file called “p4kTriviaQuestions.txt”.  Ideally we would test to see whether this file already exists before first creating it (so that we don’t inadvertently wipe some valuable file).  Today however, we’re just crossing our fingers and hoping that you don’t already have a file of this name in your directory:

import pickle
fileName = "p4kTriviaQuestions.txt"
fileObject = open(fileName,'w')
pickle.dump(triviaQuestions,fileObject)
# oops! earlier draft had these in the wrong order!
fileObject.close()

So far we have spent a lot of time on how to store the data used by the game.  However, in order to hang the various parts of the trivia game together we need to learn about storing a different part of the game – the program itself.  We will be looking at that in the coming tutorials.

An Awful Pickle

Specialist     Come in.
The door opens and Raymond Luxury Yacht enters. He cannot walk straight to the desk as his passage is barred by the strip of wood carrying the degrees, but he discovers the special hinged part of it that opens like a door. Mr Luxury Yacht has his enormous polystyrene nose. It is a foot long.
Specialist     Ah! Mr Luxury Yacht. Do sit down, please.
Mr Luxury Yacht     Ah, no, no. My name is spelled ‘Luxury Yacht’ but it’s pronounced ‘Throatwobbler Mangrove’.
Specialist     Well, do sit down then Mr Throatwobbler Mangrove.
Mr Luxury Yacht     Thank you.

So, we know how to save trivia questions to a file, and how to read them back from a file in the future.  Moreover, we have decided on a particular way of structuring the data which makes a question.  That is, the question is followed by the correct answer and then a number of incorrect answers.   Now we have to translate between a list (which has a concept of elements), and a file (which doesn’t).  Files are “flat” – which is to say that they have no sense of structure, they are simply a stream of data.  A file may record all of the characters which are the questions and answers, but it wouldn’t record the fact that they are a list or, indeed, that they are any kind of Python object.  I was originally just going to run with this to let you find out about files, but I have instead decided to introduce a further concept – the Python pickle!

pickle is a module which allows you to store Python objects including their structure.  That means after you have pickled an object to a file, you can later load that object back up from the file and all the structure associated with that object will be preserved.  While, at the moment, we are only dealing with a list, any object can be pickled – even if it has methods and attributes (ie functions and data which are packaged with the object) – they are saved with the object in the file.  What pickle does is “serialises” the object first before “persisting” it.
To use pickle you must first import it:

import pickle

pickle has two main methods – dump, which dumps an object to a file object and load, which loads an object from a file object.  Note here that the file object referred to here is what is returned by the open() function.  It is not the name of the file.  So to use pickle you must first open() the file (either as ‘w’ if you are dumping an object or as ‘r’ if you are loading one) and store the object that the open() function returns.  I will demonstrate by making a demo list object and pickling it to a file called ‘testfile’:

a = ['A dummy question','The correct answer','A wrong answer']
a
['A dummy question', 'The correct answer', 'A wrong answer']
fileName = "testfile"
fileObject = open(fileName,'w') # open the file for writing
import pickle
pickle.dump(a,fileObject)   # this writes the object a to the file named 'testfile'
fileObject.close()
fileObject = open(fileName,'r')  #open the file for reading
b = pickle.load(fileObject)  #load the object from the file into b
b
['A dummy question', 'The correct answer', 'A wrong answer']
a==b
True

You can see that what is now in b is the same as what is in a (because a==b is True, Python thinks they are the same).  Moreover, this dump/load procedure allows you to preserve the object even when you quit of of python and come back to it later (which is the whole point of this exercise):

fileObject.close()
exit()  # leave python and restart
/home/user> python
Python 2.5 (release25-maint, Dec  9 2006, 14:35:53)
[GCC 4.1.2 20061115 (prerelease) (Debian 4.1.1-20)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> fileName = 'testfile'
>>> fileObject = open(fileName,'r')
>>> c = pickle.load(fileObject)  #load the old object
>>> c
['A dummy question', 'The correct answer', 'A wrong answer']

However, now we try to compare c to the original we see that Python has forgotten a when we exited:

c==a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined

Which is to say that the only place that python got the object c from was the file when it pickle.load()ed.

Homework:
Make some other objects, dump them to a file and load them again.  Make sure that you name the file first then open() it before you pickle and .close() it afterwards.  Use the attribute ‘w’ when you open a file to dump an object and ‘r’ when you are going to load an object.

Pickle vs cPickle:

Python actually has two pickle modules – pickle, which we used above and cPickle. There are some technical differences between them but for most purposes they can be treated as being exactly the same.  The main difference is that cPickle has been written in the C programming language and, as a result, runs much faster.  While I am using pickle here, in future tutorials I will (try to remember to) use cPickle instead.  When you write your own programs you should use the cPickle module by default as it will run faster (ie. wherever you see pickle, use cPickle instead).  Otherwise the usage is exactly the same.

Spelling Note: It is pickle not pickel.