Being Exceptional

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.
Assistant     Is that on account, madam?
Lady     Yes.

Apparently, in Python, it is easier to ask for forgiveness rather than seek permission.   That is to say, the normal approach when writing Python code is to assume that what you are trying to do will work properly.  If something exceptional happens and the code doesn’t work the way you were hoping, then the Python interpreter will tell you of the error so that you can handle that exceptional circumstance.  This general approach, of trying to do something, then cleaning up if something goes wrong is acronymically called EAFP (“easier to ask for forgiveness than permission.  Here is a (somewhat silly) example:

>>> a= 1
>>> b="2"
>>> a+b
Traceback (most recent call last):
File "", line 1, in
TypeError: unsupported operand type(s) for +: 'int' and 'str'

What has happened is that we have tried to add a word (ie the string “2″) to a number (the integer 1).  Addition doesn’t make any sense in this circumstance, so Python “throws” an “exception”.  In this case the exception is called a TypeError.   The problem with this is that when an exception is raised, then unless the program deals with the exception, the Python interpreter will step in, stop the program and print an error message like the one above.

Python deals with this by the try/except structure. You try to do the statements in the first block of code, and if there is an exception (that is, you failed when you tried), then you do the statements in the second block of code.

>>> try:
...    print a+b      # this is the first block of code and can be multiple statements
... except TypeError:
...    print "Can't add those things together!" # this is the second block of code
...
Can't add those things together!

Can you see that no exception was raised here?  As far as the Python interpreter is concerned, everything ran smoothly.  The program tried to print a+b, and, in doing so, tried to work out what a +b is.  However, it failed because a is a number and b is a string.  Because it failed nothing got printed in the first block of code.  Also because the specific failure was a TypeError, the second block of code was run.

A short code snippet can show you how this works:

>>> c = [1,"2",2,3,4,5]
>>> sumOfc = 0
>>> # Try Number 1 - this will fail
>>> for number in c:
...     sumOfc += number
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: unsupported operand type(s) for +=: 'int' and 'str'
>>> # Try Number 2 - this will work
>>> sumOfc = 0  # need to reset the sum
>>> for number in c:
...     try:
...        sumOfc += number
...     except TypeError:
...        pass
...
>>> sumOfc
15

In the first try, the interpreter ran into the string “2″ and didn’t know what to do, so gave up.  In the second try, the interpreter knew exactly what to do if there was a problem with the addition (pass – or, in other words “do nothing”[see note 1]) so it went through the whole of the array adding up the things that were numbers without complaining.

Of course, you don’t use try/except blocks around everything.  You will usually have an idea about the parts of the code where something goes wrong.  You can put that code in a try/except block, with code to deal with a failure.  What you shouldn’t ever do is this:

>>> sumOfc = 0
>>> c = [1,"2",2,3,4,5]
>>> for number in c:
...     try:
...        sumOfc += number
...     except:   # <- Don't do this!
...        pass
...
>>> sumOfc
15

It is not entirely obvious what is wrong here – the code works just as well as before.  The only difference is in the except line – there is no exception specified.   The reason that this is bad isn’t that the code doesn’t work well, but, rather, that it works too well.  Every exception will be caught here, not just the one that you were expecting.  This is bad because if some other problem arises in your try block, you’ll never learn about it and your exception code will probably deal with it incorrectly.  You won’t know why your program doesn’t work because the interpreter won’t tell you.

The other approach to dealing with possible errors is called Look Before You Leap (LBYL).  LBYL involves making sure everything is right before you actually do the thing you do the problematic operation.  So, for example, you might check that what you were trying to add was an integer:

>>> c = [1,"2",2,3,4,5]
>>> sumOfc = 0
>>> for number in c:
...    if isinstance(number,int):
...       sumOfc += number
...
>>> sumOfc
15

Here the isinstance() function is another feature of Python introspection.  It tests whether the variable number is an integer (“int”).  So this code doesn’t even bother trying to add the number unless its an integer.  It first checks “is this an integer I see before me?”  If so, it adds it, if not, it ignores it.   Which is fine as far as it goes…

The problem with this is that it’s the wrong way around.  You’re not really interested in whether or not the thing you’re adding is an integer.  Rather, you’re interested in knowing whether addition is meaningful [see note 2].  In fact, this way of approaching things is broken:

>>> c = [1,"2",2,3,4,5.2]
>>> sumOfc = 0
>>> for number in c:
...    if isinstance(number,int):
...       sumOfc += number
...
>>> sumOfc
10

We’ve changed the last number in the array to be 5.2, but this causes it to be ignored because it isn’t an integer – ooops!  Applying the earlier try/except code gives the right result though:

>>> c = [1,"2",2,3,4,5.2]
>>> sumOfc = 0
>>> for number in c:
...     try:
...         sumOfc += number
...     except TypeError:
...         pass
...
>>> sumOfc
15.199999999999999

Well, accurate up to the rounding error…  Our try/except code gave us floating point addition for “free”, where the LBYL failed.  In fact, it’s even more interesting because how it works is dependent on what we define sumOfc to be:

>>> sumOfc = ''
>>> for number in c:
...     try:
...         sumOfc += number
...     except TypeError:
...         pass
...
>>> sumOfc
'2'

Wow! If that didn’t make you giddy with excitement, I don’t know what will.  The same code works if we make sumOfc a string.  Implicitly then we’re asking Python  to join the strings (which is what + means for a string) together and ignore stuff that can’t be joined.   We got this entirely for free from our try/except structure, something that would have needed reworking in the LBYL.  In fact, we could use this structure for any object that has addition defined for it.  It’s this sort of clever which makes Python so good.

Caveat:

Normally the place to use exceptions is where, every once in a while, something out of the ordinary will happen and you have some idea about how it will be out of the ordinary.  In the first case, if it’s not unusual then it’s not exceptional – don’t handle it with exceptions.  In the second case, if you can’t identify how it will be out of the ordinary you can’t deal with it in your except block.

Notes:

1. The point of the except block is to deal with any problems in the try block.  In this case you might try to convert the value to an integer in order that it could be added.

2. Implicitly we’re talking here about breaking interfaces. You, as a programmer, shouldn’t be concerned with what is going on under the hood.  You should be able to rely on the fact that an addition method is defined for an object.  If it is, then  you ought to be able to use it without having to know the internal details of the object.  So in the example here, addition is meaningful between integers and floating point numbers, but addition is also meaningful between two strings.

Weird Binding Stuff

Voice Over     This man is Ernest Scribbler… writer of jokes. In a few moments, he will have written the funniest joke in the world… and, as a consequence, he will die … laughing.
    Ernest stops writing, pauses to look at what he has written… a smile slowly spreads across his face, turning very, very slowly to uncontrolled hysterical laughter… he staggers to his feet and reels across room helpless with mounting mirth and eventually collapses and dies on the floor.

Summary: id(), copy, copy.copy(), copy.deepcopy()

A short tutorial this week on some oddities with the way Python stores and references (“binds to”) data.   You might remember, a long time ago, we talked about how, when we store data in a variable, it’s like putting your stuff in a bucket so that you can access it later.  Variables in Python actually turn out to be references to objects.   A side effect of this is that, in some cases, Python doesn’t work out how you think it will – typically this is where your object is a list or dictionary (actually any object, but you only notice this effect with compound objects) that you think you have copied, but you actually haven’t.

In particular, you can do this with ‘plain’ variables:

 >>> a = 5
 >>> b = a
 >>> a = 6
 >>> b
 5

You can also do this with lists:

 >>> c = [1,2]
 >>> d= c
 >>> c =[5,6]
 >>> d
 [1, 2]

But there’s a gotcha with lists where you change one of the list’s entries:

 >>> c= [1,2]
 >>> d = c
 >>> d
 [1, 2]
 >>> c[0]=3
 >>> d
 [3, 2]

Can you see that, even though we only changed the first entry in the list c (that is, c[0]), the first entry of d has also changed?  That’s because there is an underlying list object that both c and d are pointing to.  That is, they are both pointing to the same thing.  In a sense they are both windows to the same room (the list object).  Looking in either window allows you to “see” the changes made in the room.   You can see that the objects are the same because you can check their location in memory using the id() function which is built in to Python (try help(id)):

 >>> id(c)
 139636641421288
 >>> id(d)
 139636641421288
 >>> id(c) == id(d)
 True

The number (139636641421288) is where in the computer’s memory the object is stored.  It will change, probably each time you run the program.  If we assign a different list to d, it will have a different id, even though the values in the list are the same:

 >>> d = [3,2]       # note this new list has the same values as the old one
 >>> id(c) == id(d)
 False
 >>> id(d)
 139636640593824
 >>>

We can see that this other list is stored in a different location because the id() of the lists is different.  It turns out that this referencing behaviour is actually what you want to happen in most cases.  However, every so often you want your lists to be separate.  For that there is a special module called copy.  The copy module has a method (also called copy) which allows you to copy across the values of an object, rather than simply referencing (called “binding“) to an existing object:

>>> import copy
>>> d = copy.copy(c)
>>> d
[3, 2]
>>> c[0]=1
>>> d
[3, 2]
>>> c
[1, 2]

When you use copy.copy() the two objects will be separate and can be used independently.  Changes to one won’t show up in the other.  Where a compound object like a list or a dictionary has values which themselves are compound objects – for example a list where each entry in the list is itself a list – use the copy.deepcopy() method.   Depending on the complexity of your objects deepcopy() is not guaranteed to work (objects which refer to themselves somehow can cause a problem), but generally you will be fine.

Recap on Progress

Interviewer     Well… lets move on to our guest who not only lives in Essex but also speaks only the ends of words. Mr Ohn Ith. Mr Ith, good evening.
Enter from back of set as per Eamonn Andrews show Mr Ohn Ith. He sits at the desk
Mr Ith     … ood … ing.
Interviewer     Nice to have you on the show.
Mr Ith     … ice … o … e … ere.
Interviewer     Mr Ith, don’t you find it very difficult to make yourself understood?
Mr Ith     Yes, it is extremely difficult.
Interviewer     Just a minute, you’re a fraud
Mr Ith     Oh no. I can speak the third and fourth sentences perfectly normally.
Interviewer     Oh I see. So your next sentence will be only the ends of words again?
Mr Ith     T’s… ight.

Intermezzo

Ooops, sorry.  What was going to be a short respite after Easter became a rather extended vacation.

The good news is that, we’ve finally done enough to pretty much do any application you want (excepting, perhaps, graphical games).  The bad news is that I’m in a quandry about whether to fill out a few holes in the basics (which can be done in bite size chunks), or move onto more advanced stuff (like pygame, which is definitely on the menu).   Not so much a problem for this lesson though – it’s a review of where we’re up to.

I have also started putting together an index, which I am slowly catching up on. If you’re looking for a particular topic that I’ve covered, try the Index (although it’s not yet complete).

Recap

So, what’s the recap on what we’ve done recently?

In our past few tutorials we have created our own GUI application which actually does something useful (more or less).  Some of the things we covered are:

  • configuration files
  • parsing (that is, breaking data down into pieces that our program can make sense of)
  • we had a specific example of using instances of a class to store each configuration item
  • we read a configuration file and parsed it
  • we got to see some new widgets: Tkinter TextEdit, Entry and Frame widgets
  • we saw how to use a Tkinter Frame Widget as a way of collecting a group of related widgets together (don’t be scared of using Frames to help layout things correctly.  In some layouts you can have heaps of Frames)
  • we have tested a layout with a single row of widgets before generalising it for every widget[* see the process point below]
  • subclassing our original widget to add some GUI related functionality
  • why widgets are not like variables and how to get the letters currently in the Entry widget
  • a little bit of data verification – that true/false values remain true or false (extra points would be to use a RadioButton widget)
  • backing up the existing file and
  • writing data that we’ve collected to the file

Some Notes

Of these, I wanted to point out the process item.  that is, testing a layout with a single row of widgets first.  It is generally a good idea to break down any problem that you’re faced with into a number of smaller subtasks before solving the subtasks one after another.  At the time, this will seem tedious because in your mind you want to solve the big issue, not some small issue.  However, it’s the best way to (not only) actually reach a solution, (but also) to reach a solution which you can reuse in the future.  Often, trying to solve the larger task in one go will end up either in failure because it’s too complex (at which time you’re forced to break it up into subtasks anyway), or with a solution which is so specific to your circumstances that it can’t be reused.  Take the extra time to break down the task.

In the meantime I will think about the tutorials to come.

Minecraft config editor – Part, the Ultimate

Boss     (unfolding big map across table; talking carefully) Right … this is the plan then. … At 10:52, I shall approach the counter and purchase a watch costing £5.18.3d. I shall then give the watch to you, Vic. You’ll go straight to Norman’s Garage in East Street. You lads continue back up here at 10:56 and we rendezvous in the back room at the Cow and Sickle, at 11:15. All right, any questions?
Larry     We don’t seem to be doing anything illegal.
Boss     What do you mean?
Larry     Well … we’re paying for the watch.
Boss     (patiently) Yes…
Larry     (hesitating) Well… why are we paying for the watch?
Boss     (heavily) They wouldn’t give it to us if we didn’t pay for it, would they… eh?

This is our final instalment [sic] of our Minecraft config editor.   In the earlier tutorials we have done everything except actually updating the file.  Before we do update the file though, we need to make a backup of it, so it’s these two things that we’re going to do now.

All of the action will be in changing the behaviour of the ‘Ok’ button so that it makes a copy of server.properties into a new file called server.properties.bup and then writes the updated data to the server.properties file.  There is a small amount of work in making a copy of the file so we are going to do it in a separate function.  The function is not very smart.  It reads all of the data from the existing file and then just writes it out to the new file:

def backupFile(fileName):
  ''' Quick and dirty copy of file to fileName+".bup" - might also use os.rename(), but behaviour of os.rename is platform dependent '''
  fileObject = open(fileName,'rb')
  fileData = fileObject.read()
  fileObject.close()
  fileObject= open(fileName+".bup",'wb')  # will overwrite if it exists
  fileObject.write(fileData)
  fileObject.close()

The function could also use the rename() method from the os module.  Unfortunately, this behaves differently depending on the operating system you are using, so to keep it simple I have avoided it.

Exercise: Work out what this function is supposed to do, then confirm that it does it eg: start up a Python console, paste the definition in and then call the function with “server.properties” as a parameter.

Extra Points: If you are on a Unix based system, use the diff command to show the differences between the original and .bup (there shouldn’t be any).

With that done we can hook up the backup to the ‘Ok’ callback, and also write the new data:

def okClicked():
  '''Get the edited values and write them to the file then quit'''
  #TODO: add a confirmation dialog
  global fileName
  backupFile(fileName)
  dataToWrite = []
  for c in configLines:
    c.update()
    dataToWrite.append(c.item2ConfigLine())
  # have updated and printed each line, now exit
  fileObject = open(fileName,'wt')
  fileObject.write('\n'.join(dataToWrite)+'\n')
  # '\n' is technically not the newline character on Windows
  # but by default Python converts \n to the correct character on write
  # not sure if minecraft needs a final '\n', so included one just in case
  fileObject.close()
  exit()

Here we have introduced an array called dataToWrite.  Where, in the last tute we just printed out the line, in this tute we are appending those lines to the dataToWrite array.  Then, once they have been accumulated, we open the server.properties file (clearing it) then write our new data into it.  Only one generation of backup is saved.

We have used the global statement here to use the value of the fileName variable.  This is a little messy, but is a consequence of how the program has evolved.

One of the things that you might include here is a confirmation step.  Before the data gets overwritten we might ask the user to confirm that they are going to write over their data, giving them a second chance if they clicked “Ok” by mistake.

Complete code here:

# -*- coding: utf-8 -*-

'''Minecraft config editor:
This is an editor for the Minecraft server.properties file.  It:
* opens the file server.properties
* reads, then closes the file
* parses each line by
-- stripping leading and trailing whitespace
-- if the line starts with "#", marks it as a comment
-- splits the line into a key, value pair, with the pair separated by a "=" sign
-- if the value of the pair is either "true" or "false", the entry is marked as a boolean (ie its only values are either true or false)
* displays each key, value entry on the screen allowing you to edit it
* renames the server.properties file to server.properties.bup (overwriting any existing file of that name from earlier edits)
* opens a new file called server.properties
* writes each of the entries to that new file
* closes the server.properties file.
'''

from Tkinter import *


class configItem(object):  # name of the class, it is based on an object called 'object'
  def __init__(self, line):# this is called each time an instance of the class is created
    line = line.strip()  # this removes any white space at the start or end of the line
    # if it starts with a # it's a comment so check for it
    if line[:1] == "#":
       self.configKey = "#"
       self.configVal = line[1:]
    else:  # otherwise assume it's of the form x = y
       spam = line.split("=")
       self.configKey = spam[0]
       self.configVal = spam[1]
    # now check to see whether the config item takes only the values "true" and "false" 
    if self.configVal.lower() in ["true","false"]:
       self.isTrueFalse = True
    else:
       self.isTrueFalse = False
       
  def item2ConfigLine(self):
    if self.configKey=="#":
      '''If the key is '#' then this is a comment, so don't include an '=' sign'''
      return "%s%s"%(self.configKey, self.configVal)
    else:
      '''otherwise, it has the form key=value'''
      return "%s=%s"%(self.configKey,self.configVal)
      
   

class guiConfigItem(configItem):
  def __init__(self,line):
    super(guiConfigItem,self).__init__(line)  # run configItem's __init__ method
    self.frame = Frame()
    self.keyLabel = Label(self.frame, text = self.configKey)
    self.valueEntry = Entry(self.frame, width="60")
    self.valueEntry.insert("0",self.configVal)
    self.keyLabel.pack(side=LEFT )
    self.valueEntry.pack(side=RIGHT)
    self.frame.pack(side=TOP, fill="x")
 
  def update(self):
    ''' Get the value which is currently in the Entry widget and save it to configVal'''
    if self.isTrueFalse:
      '''if isTrueFalse is True, then we should only have the values 'true' and 'false' in this
      Entry.  So, only update the configuration value if it is one of these two.  Otherwise, ignore it. '''
      spam = self.valueEntry.get()
      if spam in ['true','false']:
	self.configVal = spam
    else:
      '''this is not a variable which is limited to 'true' and 'false', so store the whole text'''
      self.configVal = self.valueEntry.get()
    
    
# get data from the file

fileName = "ser_ver.properties"
fileObject = open(fileName,'rb')
fileData = fileObject.read()
fileObject.close()



root = Tk()
configLines = []

for line in fileData.split('\n'):  # this splits it into individual lines
    if line.strip()=='':
      continue
    configLines.append(guiConfigItem(line=line))

# 1. create callbacks for each of the buttons, 
def okClicked():
  '''Get the edited values and write them to the file then quit'''
  #TODO: add a confirmation dialog
  global fileName
  backupFile(fileName)
  dataToWrite = []
  for c in configLines:
    c.update()
    dataToWrite.append(c.item2ConfigLine())
  # have updated and printed each line, now exit
  fileObject = open(fileName,'wt')
  fileObject.write('\n'.join(dataToWrite)+'\n')
  # '\n' is technically not the newline character on Windows
  # but by default Python converts \n to the correct character on write
  # not sure if minecraft needs a final '\n', so included one just in case
  fileObject.close()
  exit()

def backupFile(fileName):
  ''' Quick and dirty copy of file to fileName+".bup" - might also use os.rename(), but behaviour of os.rename is platform dependent '''
  fileObject = open(fileName,'rb')
  fileData = fileObject.read()
  fileObject.close()
  fileObject= open(fileName+".bup",'wb')  # will overwrite if it exists
  fileObject.write(fileData)
  fileObject.close()

def cancelClicked():
  '''Cancel edits and quit'''
  exit()

# 2. create a frame for the buttons to go in
bottomFrame = Frame(root)

# 3. create the buttons, hooking up each of the buttons up to the callback
okWidget = Button(bottomFrame, text= "Ok", command = okClicked)
cancelWidget = Button(bottomFrame, text="Cancel", command = cancelClicked)

# 4. pack the buttons, then, finally,
okWidget.pack(side=LEFT)
cancelWidget.pack(side=RIGHT) 
# 5. then pack the frame:
bottomFrame.pack(side=BOTTOM)

root.mainloop()


Exercise: confirm that “Ok” saves your edits (open server.properties in a text editor or (extra points) write some Python to read and print the contents of the file) and that “Cancel” doesn’t.

Comments:

The code is a little messy because of how it has evolved in the course of explaining it.  Having code growing organically and getting messy is not unusual.  Every once in a while you need to stop and clean it up.  Cleaning it up can also allow you to restructure your code in ways you didn’t realise when you were writing it in the first place.

PS

My class names are naughty.  They should start with a capital letter.

Almost There! Adding Methods to Our Classes

Mr Mann     Ee ecky thump! (indicates more power)
Third Booth     Ee ecky thump!
Mr Mann     Excellent.
Third Booth     Thank you, sir. (puts earphones on, listens)
Mr Mann     It’s a really quick method of learning.

There are two more things to do with our Minecraft config file editor before we’ve got the main part of it working (we may do some tweaking later).  We need to:

  • add the Ok and Cancel buttons back; and
  • when someone clicks Ok, we need to update the server.properties file

We’re doing the first of these today. We saw earlier how to do the Ok and Cancel buttons, although at the time we didn’t actually put any meat in the functions they called.  So, let’s fill that out now.  For Cancel, we are just going to quit the editor without making any changes – that’s pretty easy.  For the Ok button though, we’re going to have to:

  1. somehow read all of the values from the screen (since we don’t know which ones have been changed we need to read them all);
  2. make a backup of the server.properties file
  3. write all of the key:value pairs to the new server.properties file.

Unlike variables, widgets are not the same as what is stored in them.  If we have an Entry widget called E and we want to store what has been typed there in a variable called text we can’t just write text = E.   This is because E is not a variable as we understand it.  Actually, E is an instance of a class.  This would just make another reference to the same Entry widget with the name text.  Rather, we want to “get” the current value of the text entered into E.  It turns out that the Entry widget has a method (called get()) which gets that text for you.

>>> from Tkinter import *
>>> E = Entry()  # this should pop up a Tkinter window
>>> E.pack()  # the widget should appear in your Tkinter window now
>>> type(E)
<type 'instance'>
>>> type(Entry)
<type 'classobj'>
>>> text = E
>>> type(text)
<type 'instance'>
>>> print text
.140543131462184
>>> # now type "Hi P4K!" in the entry widget
...
>>> text = E.get()
>>> print text
Hi P4K!
>>> # now add " - Again" to the end of the entry widget (leave the "Hi P4K!" there)
...
>>> text = E.get()
>>> print text
Hi P4K! - Again
>>> # you can also print the value which you get() without storing it first:
...
>>> print E.get()
Hi P4K! - Again

So what we’re going to do in our code is get() all these edited values when someone clicks “Ok”.  We could do that directly, for example by finding the relevant guiConfigItem and calling the get() method on the valueEntry attribute of that item.   That would also mean we’d have to make a copy of the key for that item and then combine them together with “=” before we wrote them to the server.properties file.  This would mean that logic which is relevant to the configItem class would be stored somewhere other than inside the class - which rather defeats the purpose of having a class to keep track of these things.  Instead, we’re going to add a method to the guiConfigItem class which updates the values it has stored.  That turns out to be pretty easy:

  def update(self):
    ''' Get the value which is currently in the Entry widget and save it to configVal'''
    if self.isTrueFalse:
      '''if isTrueFalse is True, then we should only have the values 'true' and 'false' in this
      Entry.  So, only update the configuration value if it is one of these two.  Otherwise, ignore it. '''
      spam = self.valueEntry.get()
      if spam in ['true','false']:
	self.configVal = spam
    else:
      '''this is not a variable which is limited to 'true' and 'false', so store the whole text'''
      self.configVal = self.valueEntry.get()

Note here that we are referencing the attribute configVal which is defined in the parent class.  Also note that we’ve included a bit of logic here to ensure that those configuration values which start as ‘true’ or ‘false’ can only be ‘true’ or ‘false’.  If you type something else into them it will be ignored.  It is sufficient here to just say if self.isTrueFalse rather than if self.isTrueFalse is True (the “is True” is redundant).

We also need a way to prepare the lines of the server.properties file to be printed or written to the file.  We do this by adding a method to the configItem class (since it doesn’t have anything to do with the graphical interface we don’t add it to the subclass):

  def item2ConfigLine(self):
    if self.configKey=="#":
      '''If the key is '#' then this is a comment, so don't include an '=' sign'''
      return "%s%s"%(self.configKey, self.configVal)
    else:
      '''otherwise, it has the form key=value'''
      return "%s=%s"%(self.configKey,self.configVal)

See this tutorial for an explanation of the %s stuff…

We don’t have a way to test these methods out yet. So let’s hook up the Ok and Cancel buttons. The Ok button will run through each of the guiConfigItems and update it, then print out the configuration line. After all items have been processed this way, the program will exit. The cancel button will just exit without doing anything. So, we need to:
1. create callbacks for each of the buttons,
2. create a frame for the buttons to go in
3. create the buttons, hooking up each of the buttons up to the callback
4. pack the buttons, then, finally,
5. pack the frame.
These go before the root.mainloop() line.

# 1. create callbacks for each of the buttons,
def okClicked():
  '''Get the edited values and write them to the file then quit'''
  for c in configLines:
    c.update()
    print c.item2ConfigLine()
  # have updated and printed each line, now exit
  exit()

def cancelClicked():
  '''Cancel edits and quit'''
  exit()

# 2. create a frame for the buttons to go in
bottomFrame = Frame(root)

# 3. create the buttons, hooking up each of the buttons up to the callback
okWidget = Button(bottomFrame, text= "Ok", command = okClicked)
cancelWidget = Button(bottomFrame, text="Cancel", command = cancelClicked)

# 4. pack the buttons, then, finally,
okWidget.pack(side=LEFT)
cancelWidget.pack(side=RIGHT)
# 5. then pack the frame:
bottomFrame.pack(side=BOTTOM)

At the moment, the Ok button just prints out the values of the items. This is so that we can test how it is working before we let it go editing the actual file.

Here is the complete source code:

# -*- coding: utf-8 -*-

'''Minecraft config editor:
This is an editor for the Minecraft server.properties file.  It:
* opens the file server.properties
* reads, then closes the file
* parses each line by
-- stripping leading and trailing whitespace
-- if the line starts with "#", marks it as a comment
-- splits the line into a key, value pair, with the pair separated by a "=" sign
-- if the value of the pair is either "true" or "false", the entry is marked as a boolean (ie its only values are either true or false)
* displays each key, value entry on the screen allowing you to edit it
* renames the server.properties file to server.properties.bup (overwriting any existing file of that name from earlier edits)
* opens a new file called server.properties
* writes each of the entries to that new file
* closes the server.properties file.
'''

from Tkinter import *

class configItem(object):  # name of the class, it is based on an object called 'object'
  def __init__(self, line):# this is called each time an instance of the class is created
    line = line.strip()  # this removes any white space at the start or end of the line
    # if it starts with a # it's a comment so check for it
    if line[:1] == "#":
       self.configKey = "#"
       self.configVal = line[1:]
    else:  # otherwise assume it's of the form x = y
       spam = line.split("=")
       self.configKey = spam[0]
       self.configVal = spam[1]
    # now check to see whether the config item takes only the values "true" and "false"
    if self.configVal.lower() in ["true","false"]:
       self.isTrueFalse = True
    else:
       self.isTrueFalse = False

  def item2ConfigLine(self):
    if self.configKey=="#":
      '''If the key is '#' then this is a comment, so don't include an '=' sign'''
      return "%s%s"%(self.configKey, self.configVal)
    else:
      '''otherwise, it has the form key=value'''
      return "%s=%s"%(self.configKey,self.configVal)

class guiConfigItem(configItem):
  def __init__(self,line):
    super(guiConfigItem,self).__init__(line)  # run configItem's __init__ method
    self.frame = Frame()
    self.keyLabel = Label(self.frame, text = self.configKey)
    self.valueEntry = Entry(self.frame, width="60")
    self.valueEntry.insert("0",self.configVal)
    self.keyLabel.pack(side=LEFT )
    self.valueEntry.pack(side=RIGHT)
    self.frame.pack(side=TOP, fill="x")

  def update(self):
    ''' Get the value which is currently in the Entry widget and save it to configVal'''
    if self.isTrueFalse:
      '''if isTrueFalse is True, then we should only have the values 'true' and 'false' in this
      Entry.  So, only update the configuration value if it is one of these two.  Otherwise, ignore it. '''
      spam = self.valueEntry.get()
      if spam in ['true','false']:
	self.configVal = spam
    else:
      '''this is not a variable which is limited to 'true' and 'false', so store the whole text'''
      self.configVal = self.valueEntry.get()

# get data from the file

fileName = "server.properties"
fileObject = open(fileName,'rb')
fileData = fileObject.read()
fileObject.close()

root = Tk()
configLines = []

for line in fileData.split('\n'):  # this splits it into individual lines
    if line.strip()=='':
      continue
    configLines.append(guiConfigItem(line=line))

# 1. create callbacks for each of the buttons,
def okClicked():
  '''Get the edited values and write them to the file then quit'''
  for c in configLines:
    c.update()
    print c.item2ConfigLine()
  # have updated and printed each line, now exit
  exit()

def cancelClicked():
  '''Cancel edits and quit'''
  exit()

# 2. create a frame for the buttons to go in
bottomFrame = Frame(root)

# 3. create the buttons, hooking up each of the buttons up to the callback
okWidget = Button(bottomFrame, text= "Ok", command = okClicked)
cancelWidget = Button(bottomFrame, text="Cancel", command = cancelClicked)

# 4. pack the buttons, then, finally,
okWidget.pack(side=LEFT)
cancelWidget.pack(side=RIGHT)
# 5. then pack the frame:
bottomFrame.pack(side=BOTTOM)

root.mainloop()

Exercise: run the code and confirm that: (a) your edits are captured and printed out; (b) if you enter anything but “true” or “false” for an item that takes only true and false, then the edit is ignored; and (c) that if you change a true to a false or vice versa, that that edit is captured.

Minecraft Config: Subclassing and Inheritance, Editing all config items

Mr. Simpson:     Good. Well I have this large quantity of string, a hundred and twenty-two thousand miles of it to be exact, which I inherited, and I thought if I advertised it–
Wapcaplet:     Of course! A national campaign. Useful stuff, string, no trouble there.
Mr. Simpson:     Ah, but there’s a snag, you see. Due to bad planning, the hundred and twenty-two thousand miles is in three inch lengths. So it’s not very useful.

In the previous tutorial we learnt that when we pack Tkinter objects, the order in which we pack them affects how they are displayed in the GUI.  We used a Text widget to enable the user to edit text in the GUI.  We also learnt to use a Frame widget to help with the layout of our GUI.  In particular, we put a Label and a Text widget together into one Frame, and put an ok and cancel Button into another.   For homework you needed to get data from the Text widget.

In order to display all of the configuration options we are going to go a bit nutty using Frames.   We will eventually (but not today) use one Frame to hold all of the configuration options, and another Frame to hold the Ok and Cancel buttons.  But that’s not all!  We will also use a Frame to house each configuration option (ie key and value pair).  Before we do that though, we need to remember where we were up to reading and parsing the server.properties file.

Here is the code we finished with two tutorials ago, excluding the last couple of lines (which printed out the results) for your reference if you need it (click to expand):

'''Minecraft config editor:
This is an editor for the Minecraft server.properties file.  It:
* opens the file server.properties
* reads, then closes the file
* parses each line by
-- stripping leading and trailing whitespace
-- if the line starts with "#", marks it as a comment
-- splits the line into a key, value pair, with the pair separated by a "=" sign
-- if the value of the pair is either "true" or "false", the entry is marked as a boolean (ie its only values are either true or false)
* displays each key, value entry on the screen allowing you to edit it
* renames the server.properties file to server.properties.bup (overwriting any existing file of that name from earlier edits)
* opens a new file called server.properties
* writes each of the entries to that new file
* closes the server.properties file.
'''

class configItem(object):  # name of the class, it is based on an object called 'object'
  def __init__(self, line):# this is called each time an instance of the class is created
    line = line.strip()  # this removes any white space at the start or end of the line
    # if it starts with a # it's a comment so check for it
    if line[:1] == "#":
       self.configKey = "#"
       self.configVal = line[1:]
    else:  # otherwise assume it's of the form x = y
       spam = line.split("=")
       self.configKey = spam[0]
       self.configVal = spam[1]
    # now check to see whether the config item takes only the values "true" and "false"
    if self.configVal.lower() in ["true","false"]:
       self.isTrueFalse = True
    else:
       self.isTrueFalse = False

# get data from the file

fileName = "server.properties"
fileObject = open(fileName,'rb')
fileData = fileObject.read()
fileObject.close()

configLines = []

for line in fileData.split('\n'):  # this splits it into individual lines
    if line.strip()=='':
      continue
    configLines.append(configItem(line))

If you remember we defined a class called configItem.  We read the lines from the config file and used each line to create instances of configItem.  We stored those in an array called configLines.  Each instance has two attributesconfigKey and configVal (that is, the things on the left and right hand side of the equals respectively).  In the last tutorial for one key, value pair we:

  • created a label and set it equal to configKey;
  • created a text widget and set its value to configVal; and, finally,
  • created a frame in which to pack each of these.

Now we have to do that for each and every entry in the array.  There are plenty of ways to do this.  However, I am going to do it by “subclassing” the configItem class.  That is, I am going to create a new class which is based on (“inherits from” or “is a subclass of”) the configItem class.  It has the features of the configItem class but will also store some stuff relating to the Tkinter widgets that we will need.  This is the new class which I’ve called guiConfigItem:

class guiConfigItem(configItem):
  def __init__(self,line):
    super(guiConfigItem,self).__init__(line)  # run configItem's __init__ method
    self.frame = Frame()
    self.keyLabel = Label(self.frame, text = self.configKey)
    self.valueEntry = Entry(self.frame, width="60")
    self.valueEntry.insert("0",self.configVal)
    self.keyLabel.pack(side=LEFT )
    self.valueEntry.pack(side=RIGHT)
    self.frame.pack(side=TOP)

Some things to note about this class:

  • instead of the first line ending “(object):” like the other classes we’ve seen, this one ends “(configItem):”.  This means that guiConfigItem’s immediate parent is configItem.  However, since configItem is based on object, in the end, so is guiConfigItem.
  • it takes the same initialisation parameters as configItem (that is, self and line)
  • the first thing it does in initialising stuff is to call super(guiConfigItem,self).__init__(line).  This runs configItem’s __init__ method, so every guiConfigItem starts with the same initialisation that configItem would have
  • it starts by creating a Frame, stores it in self.frame, then, inside the frame, it creates a label and an Entry widget.  An Entry widget is the single line version of the Text widget we used last time, and it should be good enough for our purposes.
  • you can tell that the Label and Entry widgets are created inside the frame which has been created because the first parameter passed to them is self.frame.
  • the Label widget is packed to the LEFT, and the Entry widget to the RIGHT.  the frame is also packed, but it is packed to TOP (ie it will make a list from top to bottom)

This class is added after the definition of the configItem class.  In order to get it working we just have to make a four changes to the program.  We will:

  • import Tkinter – from Tkinter import *;
  • create a root window in which to pack things - root =Tk();
  • change the loop to create guiConfigItems rather than configItems – configLines.append(guiConfigItem(line=line)); and
  • we will start the gui with a mainloop() – root.mainloop()

Here is the updated source code:


'''Minecraft config editor:
This is an editor for the Minecraft server.properties file.  It:
* opens the file server.properties
* reads, then closes the file
* parses each line by
-- stripping leading and trailing whitespace
-- if the line starts with "#", marks it as a comment
-- splits the line into a key, value pair, with the pair separated by a "=" sign
-- if the value of the pair is either "true" or "false", the entry is marked as a boolean (ie its only values are either true or false)
* displays each key, value entry on the screen allowing you to edit it
* renames the server.properties file to server.properties.bup (overwriting any existing file of that name from earlier edits)
* opens a new file called server.properties
* writes each of the entries to that new file
* closes the server.properties file.
'''

from Tkinter import *

class configItem(object):  # name of the class, it is based on an object called 'object'
  def __init__(self, line):# this is called each time an instance of the class is created
    line = line.strip()  # this removes any white space at the start or end of the line
    # if it starts with a # it's a comment so check for it
    if line[:1] == "#":
       self.configKey = "#"
       self.configVal = line[1:]
    else:  # otherwise assume it's of the form x = y
       spam = line.split("=")
       self.configKey = spam[0]
       self.configVal = spam[1]
    # now check to see whether the config item takes only the values "true" and "false"
    if self.configVal.lower() in ["true","false"]:
       self.isTrueFalse = True
    else:
       self.isTrueFalse = False

class guiConfigItem(configItem):
  def __init__(self,line):
    super(guiConfigItem,self).__init__(line)  # run configItem's __init__ method
    self.frame = Frame()
    self.keyLabel = Label(self.frame, text = self.configKey)
    self.valueEntry = Entry(self.frame, width="60")
    self.valueEntry.insert("0",self.configVal)
    self.keyLabel.pack(side=LEFT )
    self.valueEntry.pack(side=RIGHT)
    self.frame.pack(side=TOP)

# get data from the file

fileName = "server.properties"
fileObject = open(fileName,'rb')
fileData = fileObject.read()
fileObject.close()

root = Tk()
configLines = []

for line in fileData.split('\n'):  # this splits it into individual lines
    if line.strip()=='':
      continue
    configLines.append(guiConfigItem(line=line))

root.mainloop()

When I run this I get:


Wow, is that magic? The way we defined the class meant that each of the instances packed itself for us as we created them.  This is an example of why using classes can be so much fun.

Exercise: how might you do the same thing without using classes?

That said, the alignment is a little wonky.   This is because each of the individual frames (there is one on each line) are different sizes.  The overall window is big enough to fit the biggest, but that means that the smaller lines aren’t big enough.  This can be remedied by adding fill=”x” (that is, fill in the x (horizontal) direction if necessary to the pack command for each of the Frames:

self.frame.pack(side=TOP, fill="x")

Now the window looks much better:

Exercise: confirm that you can edit the values on the right.

Exercise 2: check through our docstring to see what we’ve done so far and what we’ve got left to do.

The complete source code with the final edit is below.

'''Minecraft config editor:
This is an editor for the Minecraft server.properties file.  It:
* opens the file server.properties
* reads, then closes the file
* parses each line by
-- stripping leading and trailing whitespace
-- if the line starts with "#", marks it as a comment
-- splits the line into a key, value pair, with the pair separated by a "=" sign
-- if the value of the pair is either "true" or "false", the entry is marked as a boolean (ie its only values are either true or false)
* displays each key, value entry on the screen allowing you to edit it
* renames the server.properties file to server.properties.bup (overwriting any existing file of that name from earlier edits)
* opens a new file called server.properties
* writes each of the entries to that new file
* closes the server.properties file.
'''

from Tkinter import *

class configItem(object):  # name of the class, it is based on an object called 'object'
  def __init__(self, line):# this is called each time an instance of the class is created
    line = line.strip()  # this removes any white space at the start or end of the line
    # if it starts with a # it's a comment so check for it
    if line[:1] == "#":
       self.configKey = "#"
       self.configVal = line[1:]
    else:  # otherwise assume it's of the form x = y
       spam = line.split("=")
       self.configKey = spam[0]
       self.configVal = spam[1]
    # now check to see whether the config item takes only the values "true" and "false"
    if self.configVal.lower() in ["true","false"]:
       self.isTrueFalse = True
    else:
       self.isTrueFalse = False

class guiConfigItem(configItem):
  def __init__(self,line):
    super(guiConfigItem,self).__init__(line)  # run configItem's __init__ method
    self.frame = Frame()
    self.keyLabel = Label(self.frame, text = self.configKey)
    self.valueEntry = Entry(self.frame, width="60")
    self.valueEntry.insert("0",self.configVal)
    self.keyLabel.pack(side=LEFT )
    self.valueEntry.pack(side=RIGHT)
    self.frame.pack(side=TOP, fill="x")

# get data from the file

fileName = "server.properties"
fileObject = open(fileName,'rb')
fileData = fileObject.read()
fileObject.close()

root = Tk()
configLines = []

for line in fileData.split('\n'):  # this splits it into individual lines
    if line.strip()=='':
      continue
    configLines.append(guiConfigItem(line=line))

root.mainloop()

Minecraft Config Editor: Tkinter Text Widget and Frames

Furtively he looks round, then takes from the desk drawer a comic-book entitled ‘Thrills and Adventure’. We see the frames of the comic strip. A Superman-type character and a girl are shrinking from an explosion. She is saying ‘My God, his nose just exploded with enough force to destroy his kleenex’. In the next frame, the Superman character is saying ‘If only I had a kleenex to lend him – or even a linen handkerchief – but these trousers…!! No back pocket!’ In the frame beneath, he flies from side to side attempting to escape; finally he breaks through, bringing the two frames above down on himself. Cut to a picture of a safety curtain.

Last tutorial we covered ‘parsing’.  We broke a standard config file up into separate lines and then we broke each line into pairs, each pair having a ‘key’ and a ‘value’.   If we’re to edit the file, we need a way to edit the values (and after that we’ll write the edited values back to the config file).  We’re going to see how to use Tkinter to do that in this tutorial.

Let’s start thinking about how a single key/value pair will look.  I am thinking of having the key on the left hand side with a space to input the value on the right hand side.  To do this we will use the Label widget that we’ve met before and the Text widgetLabel is used to display static text – ie text that will not be edited, while the Text widget allows the user to edit the text which is displayed in the widget, and for the program to read what is entered in the widget.  The Text widget is the GUI equivalent of raw_input() that we met so long ago.

from Tkinter import *

root = Tk()
labelWidget = Label(root,text="A key:")
textWidget = Text(root)

textWidget.insert('1.0',"A Value")
labelWidget.pack(side=LEFT)
textWidget.pack(side=RIGHT)

root.mainloop()

Here we first create a Tk() object, then create a label widget and a text widget in that object.   We pack the label first and we pack it on the LEFT side (LEFT is actually the name of a constant in Tk which Tk translates to ‘put this on the left’) and pack the text widget on the right side.  At location “1.0″ we add the text “A Value” to the Text widget.  Here the number “1.0″ means “row 1, at character position 0″, which is to say, at the very start of the text.

If I run this code I get something like this:

Which is sort of what I wanted – a label on the left, and an editable text box on the right (can you see the cursor in the screen shot?) – click the close window widget in the top right corner to close the window.

Exercise:  Type something into the text box.  See if you can do it to the label.

However, this isn’t really what I wanted.  I wanted a little text box, not the enormous one I’ve got here.  Since I didn’t specify a height and width for it, the Text widget used its default size (which is way too big).  The user also doesn’t have any way to tell the program to use (or cancel) the edit.  Let’s change the code to add an ok and cancel button, and to change the size of the text widget.

Here is a revised version which is nearer to what I was looking for:

from Tkinter import *

def okClicked():
    '''Get the edited values and write them to the file then quit'''
    #TODO: get values and write them to the file!
    exit()
def cancelClicked():
    '''Cancel edits and quit'''
    exit()

root = Tk()
labelWidget = Label(root,text="A key:")
textWidget = Text(root, width=60, height = 1)
okWidget = Button(root, text= "Ok", command = okClicked)
cancelWidget = Button(root, text="Cancel", command = cancelClicked)

textWidget.insert('1.0',"A Value")
labelWidget.pack(side=LEFT)
cancelWidget.pack(side=RIGHT)
okWidget.pack(side=RIGHT)

textWidget.pack(side=RIGHT)

root.mainloop()

This gives:

I have added a couple of functions to be run when the ok and cancel buttons are clicked.  The ok button’s function is still a little empty at the moment though…  I have specified the width to be 60 characters and the height to be one row.  Note these are not pixel measurements.  If you change the size of the font the text box will also change.

Notice also the way the geometry is working.  The widgets which are pack(side=RIGHT) are added at the right in the order they are packed.  If the buttons were packed last they would be between the label and the text window.

Exercise: change the program so that the widgets are packed in a different order. What happens if you try side=TOP or side=BOTTOM?

The value can be edited by the user typing directly into the text box.  The text in the text box can also be edited programmatically, which is to say its contents can be changed by the program without the user typing.  See the Tkinter documentation for details – or clamour on this site and I’ll add a tute.

One thing that I don’t like about this layout is the fact the buttons are on the same line as the key label and value text.  When you did the exercise above, you should have noticed that side=TOP and side=BOTTOM don’t really help, since you can’t position the ok and cancel buttons on the same line.  What we need to use is the Frame widgetFrames can be thought of as empty spaces in which you can group widgets together.  By treating the widgets within the Frame as a group, additional layouts can be achieved.   Frames can be packed inside other frames. We will use two frames, one on top of the other.  In the first frame we pack the label and text widgets.  In the second frame we pack the two buttons.

Here is the code:

from Tkinter import *

def okClicked():
    '''Get the edited values and write them to the file then quit'''
    #TODO: get values and write them to the file!
    exit()
def cancelClicked():
    '''Cancel edits and quit'''
    exit()

root = Tk()

topFrame = Frame(root)
bottomFrame = Frame(root)
labelWidget = Label(topFrame,text="A key:")
textWidget = Text(topFrame, width=60, height = 1)
okWidget = Button(bottomFrame, text= "Ok", command = okClicked)
cancelWidget = Button(bottomFrame, text="Cancel", command = cancelClicked)

textWidget.insert('1.0',"A Value")
labelWidget.pack(side=LEFT)
cancelWidget.pack(side=RIGHT)
okWidget.pack(side=LEFT)
textWidget.pack(side=RIGHT)

topFrame.pack(side=TOP)
bottomFrame.pack(side=BOTTOM)

root.mainloop()

This is more like what I wanted (notice you can’t see the individual frames).  One might quibble with the location of the ok and cancel buttons.  Maybe they should be offset a little from the centre?  Maybe they should be off to one side.  In any event they are in the general layout that i was looking for: a key label and an editable value text above the ok and cancel buttons. Notice in the code that the widgets we used before have had their parent changed from root to either topFrame or bottomFrame?  However these Frame widgets have root as their parent.   So the original widgets we were using have effectively been pushed down one level in the hierarchy.

We still don’t know how to get what the user has typed into the text box, but maybe I can leave that as an exercise for the reader (try textWidget.get(“1.0″,END)).

Homework:  change the okClicked() function so that it prints the contents of the text box before exiting.  Use the hint in the previous paragraph.

MineCraft config editor part 2

Voice Over     And now for the very first time on the silver screen comes the film from two books which once shocked a generation. From Emily Brontë’s ‘Wuthering Heights’ and from the ‘International Guide to Semaphore Code’. Twentieth Century Vole presents ‘The Semaphore Version of Wuthering Heights’.
CAPTION: ‘THE SEMAPHORE VERSION OF WUTHERING HEIGHTS’

In our last tutorial I left you with some homework to produce a docsting describing the steps we need to do in order to get our MineCraft config editor up and running.

This is what I came up with:

'''Minecraft config editor:
This is an editor for the Minecraft server.properties file.  It:
* opens the file server.properties
* reads, then closes the file
* parses each line by
-- stripping leading and trailing whitespace
-- if the line starts with "#", marks it as a comment
-- splits the line into a key, value pair, with the pair separated by a "=" sign
-- if the value of the pair is either "true" or "false", the entry is marked as a boolean (ie its only values are either true or false)
* displays each key, value entry on the screen allowing you to edit it
* renames the server.properties file to server.properties.bup (overwriting any existing file of that name from earlier edits)
* opens a new file called server.properties
* writes each of the entries to that new file
* closes the server.properties file.
'''

Did you get something like that?  This is our script for writing the program. Most of it we can already do.  In fact, the only thing we can’t do at the moment is ” displays each key, value entry on the screen allowing you to edit it”.  Hey, notice anything different about that listing?  I’ve found WordPress’s special tags for source code.  If you run your mouse over it, some widgets will pop up so you can copy and paste the code.

The other part of your homework was to save the contents of the listing in the previous tute to  a file called “server.properties”.  You need to do that in order to run this tute. Post a comment if you have problems.

Before we dive into reading data from the file we need to think about how we will store the data that we read.  From our docstring we can tell that we might need to store a property key, a property value, a comment and whether or not it’s a property which is only true or false.  We could use a dictionary to store these but, since we’ve just found out about classes, we’re going to use a class instead.  Each instance of the class will hold one line from the configuration file.   We can use the From these we can make a class which describes the properties:

class configItem(object):  # name of the class, it is based on an object called 'object'
  def __init__(self, line):# this is called each time an instance of the class is created
    line = line.strip()  # this removes any white space at the start or end of the line
    # if it starts with a # it's a comment so check for it
    if line[:1] == "#":
       self.configKey = "#"
       self.configVal = line[1:]
    else:  # otherwise assume it's of the form x = y
       spam = line.split("=")
       self.configKey = spam[0]
       self.configVal = spam[1]
    # now check to see whether the config item takes only the values "true" and "false"
    if self.configVal.lower() in ["true","false"]:
       self.isTrueFalse = True
    else:
       self.isTrueFalse = False

So, we have our class. What we need to do is read the data from the file (this, of course, won’t work if you haven’t already saved the file).  See this tute for reading data from files.

# get data from the file

fileName = "server.properties"
fileObject = open(fileName,'rb')
fileData = fileObject.read()
fileObject.close()

Now we will create a configItem instance for each line.  However, we’ll need to keep them in something, so we make an array to do that first.

configLines = []
for line in fileData.split('\n'):  # this splits it into individual lines
    configLines.append(configItem(line))

So, our code at the moment looks like this:

class configItem(object):  # name of the class, it is based on an object called 'object'
  def __init__(self, line):# this is called each time an instance of the class is created
    line = line.strip()  # this removes any white space at the start or end of the line
    # if it starts with a # it's a comment so check for it
    if line[:1] == "#":
       self.configKey = "#"
       self.configVal = line[1:]
    else:  # otherwise assume it's of the form x = y
       spam = line.split("=")
       self.configKey = spam[0]
       self.configVal = spam[1]
    # now check to see whether the config item takes only the values "true" and "false"
    if self.configVal.lower() in ["true","false"]:
       self.isTrueFalse = True
    else:
       self.isTrueFalse = False

# get data from the file

fileName = "server.properties"
fileObject = open(fileName,'rb')
fileData = fileObject.read()
fileObject.close()

configLines = []
for line in fileData.split('\n'):  # this splits it into individual lines
    configLines.append(configItem(line))

for c in configLines:
    print "%s: %s, isTruefalse= %s"%(c.configKey, c.configVal, c.isTrueFalse)

Note: see this tute for  what the %s means.

Debugging

When I run this code on my own copy of the server.properties file I get an error:

Traceback (most recent call last):
File "serverEditor.py", line 33, in <module>
configLines.append(configItem(line))
File "serverEditor.py", line 15, in __init__
self.configVal = spam[1]
IndexError: list index out of range

I added a print statement in the class to print the line it received. It turned out that it had trouble because my file had some extra, blank lines at the end of it.  So I have added some code to skip the line if it is empty:

for line in fileData.split('\n'):  # this splits it into individual lines
    if line.strip()=='':
      continue
    configLines.append(configItem(line))

So now I get this output:

>python serverEditor.py
#: Minecraft server properties, isTruefalse= False
#: Date and time of creation of file, isTruefalse= False
allow-nether: true, isTruefalse= True
level-name: world, isTruefalse= False
enable-query: false, isTruefalse= True
allow-flight: false, isTruefalse= True
server-port: 25565, isTruefalse= False
level-type: DEFAULT, isTruefalse= False
enable-rcon: false, isTruefalse= True
level-seed: , isTruefalse= False
server-ip: , isTruefalse= False
spawn-npcs: true, isTruefalse= True
white-list: false, isTruefalse= True
spawn-animals: true, isTruefalse= True
online-mode: true, isTruefalse= True
pvp: true, isTruefalse= True
difficulty: 1, isTruefalse= False
gamemode: 0, isTruefalse= False
max-players: 20, isTruefalse= False
spawn-monsters: true, isTruefalse= True
generate-structures: true, isTruefalse= True
view-distance: 10, isTruefalse= False
motd: A Minecraft Server, isTruefalse= False

You should check that the data is all correct.  Note that if a line has a value of “true” or “false”, the corresponding item has an attribute called isTrueFalse, which is set to True. So far we have:

* created a class called configItem to describe each line in the file

* opened the file, read each line, and created an instance of the class for each line.

* when a configItem is instantiated, it parses the data which it is initialised with.

Next we will have to work out how to edit them.

Homework: think about what might go wrong with this code.

Here’s the complete code again:

'''Minecraft config editor:
This is an editor for the Minecraft server.properties file.  It:
* opens the file server.properties
* reads, then closes the file
* parses each line by
-- stripping leading and trailing whitespace
-- if the line starts with "#", marks it as a comment
-- splits the line into a key, value pair, with the pair separated by a "=" sign
-- if the value of the pair is either "true" or "false", the entry is marked as a boolean (ie its only values are either true or false)
* displays each key, value entry on the screen allowing you to edit it
* renames the server.properties file to server.properties.bup (overwriting any existing file of that name from earlier edits)
* opens a new file called server.properties
* writes each of the entries to that new file
* closes the server.properties file.
'''

class configItem(object):  # name of the class, it is based on an object called 'object'
  def __init__(self, line):# this is called each time an instance of the class is created
    line = line.strip()  # this removes any white space at the start or end of the line
    # if it starts with a # it's a comment so check for it
    if line[:1] == "#":
       self.configKey = "#"
       self.configVal = line[1:]
    else:  # otherwise assume it's of the form x = y
       spam = line.split("=")
       self.configKey = spam[0]
       self.configVal = spam[1]
    # now check to see whether the config item takes only the values "true" and "false"
    if self.configVal.lower() in ["true","false"]:
       self.isTrueFalse = True
    else:
       self.isTrueFalse = False

# get data from the file

fileName = "server.properties"
fileObject = open(fileName,'rb')
fileData = fileObject.read()
fileObject.close()

configLines = []

for line in fileData.split('\n'):  # this splits it into individual lines
    if line.strip()=='':
      continue
    configLines.append(configItem(line))

for c in configLines:
  print "%s: %s, isTruefalse= %s"%(c.configKey, c.configVal, c.isTrueFalse)

Starting our Config Editor

…And Dinsdale’s there in the conversation pit with Doug and Charles Paisley, the baby crusher, and a couple of film producers and a man they called ‘Kierkegaard’

In the last two tutorials we have been revising some things we had learned earlier about classes and GUIs.  I wanted to give you an example of where you can use Python in practice, so we are going to do a short program which will use classes and Tkinter – a configuration editor for Minecraft servers. We haven’t yet seen all of the widgets that we will need for this short project, so we will do it a little slowly.

When people write computer programs they try to write the program they don’t necessarily know all the relevant details about how and where it will be run.  It is often the case that certain data needs to be provided that program by the end user.  For example,  let’s say you wrote a greeting program which said hello to the user when they ran it.   If the end user’s name was Søren Kierkegaard, how would the program know to say “Hello Søren?” – it wouldn’t.  Søren would need to tell it.  What’s more, it would be a little tedious if Søren had to keep telling the program what his name was every time he ran the program, so, ideally, once it was entered, the program would store the data.  One place it could be stored is in a configuration file.

Minecraft is a building game.  If you run your own Minecraft server the server stores its configuration information in a file called “server.properties”.  Apparently, the default server.properties file looks like this:

#Minecraft server properties
#Date and time of creation of file
allow-nether=true
level-name=world
enable-query=false
allow-flight=false
server-port=25565
level-type=DEFAULT
enable-rcon=false
level-seed=
server-ip=
spawn-npcs=true
white-list=false
spawn-animals=true
online-mode=true
pvp=true
difficulty=1
gamemode=0
max-players=20
spawn-monsters=true
generate-structures=true
view-distance=10
motd=A Minecraft Server

When you run the Minecraft server, it reads each line in this file, comprehends the line and does something based on that comprehension.  For example, the lines beginning with “#” are comment lines, so the server ignores them.  The other lines, you’ll notice all have an “=” sign in them and look like a Python assignment statement.  They are doing something similar.  Each line has a key – value pair.  The stuff on the left of the = sign is the key, and the stuff on the right of the = sign is the value that is assigned to that key.  The last line “motd” stands for “Message of the Day”.  When someone logs onto a server with this server.properties file they will be greeted with the words “A Minecraft Server”. Changing this line will change the message that players see.

The act of comprehending data in this way is called “parsing” (more like “par-zing”, than “pass sing”).  To parse this config file would involve: identifying lines which start with “#” (they are comments) and breaking other lines into their key,value pairs.  A more advanced parser might also identify invalid configuration lines.  For example, many lines you see here have values of either “true” or “false”.  These, are, in fact, the only two possibilities for those keys.  A line like “spawn-monsters=Potato chips” would be invalid.  Equally, view-distance needs to be a whole number and so on.

While you can open up a configuration file in a text editor, what we’re going to do is write a program which allows a user to change the server.properties file by using a Tkinter GUI.  We’ll use a lot of what we have already learned.

Homework: save the extract above to a file called “server.properties” in your Python for kids directory.

Homework: write a docstring describing what we need to in order to edit a configuration file called “server.properties”.  This will be our set of instructions for writing the program.
Hint: step 1 will be to open the file, and the last step will be to close the file…

Recap part 2 – Tkinter

Well it certainly looks as though we’re in for a splendid afternoon’s sport in this the 127th Upperclass Twit of the Year Show. Well the competitors will be off in a moment so let me just identify for you. (close-up of the competitors) Vivian Smith-Smythe-Smith has an O-level in chemo-hygiene. Simon-Zinc-Trumpet-Harris, married to a very attractive table lamp.

In the last lesson, I gave you a quick refresher on classes.  In this tute, we’re going to remember what Tkinter is.  Technically, Tkinter is something that allows Python programs to use something called Tk.   According to its website, Tk is “a graphical user interface toolkit that takes developing desktop applications to a higher level than conventional approaches. Tk is the standard GUI not only for Tcl, but for many other dynamic languages, and can produce rich, native applications that run unchanged across Windows, Mac OS X, Linux and more.

In other words, Tkinter is what allows your Python programs to interact with a user through a windowing system, rather than the command line I had been forcing you to use earlier.  Tkinter is not unique, there are other toolkits that Python can use to do exactly the same thing. We are working with Tkinter here because it’s the de facto standard for Python – also because you should have Tkinter (they come together).  Tkinter’s look is pretty ugly, but we are here for practicality, not beauty.  You can research other toolkits on line if you want to have a different look for your application, but you will need to learn how Python calls those other toolkits, as it will be different.  Other toolkits may also provide additional widgets.   That said, there are a number of common elements to any GUI system that you use:

  • GUIs interact with users through areas on the user’s screen called windows;
  • windows typically have an area (such as a top and bottom bar) which are reserved for operating system related functions – such as the window’s title,  minimize, maximize, close etc.  with the balance of the space being available for use by the program;

  • the GUI uses a geometry manager to arrange widgets within the window;
  • different geometry managers can be used depending on how you want your widgets arranged.  In our examples to date we have not specified a geometry manager, so Tkinter has used a default geometry manager;
  • widgets are graphical components which are displayed in the window.  So far we have met buttons and labels, but there are many more.
  • each time a user does something in the window, an event is generated.
  • there are a whole host of things that can generate an event.  These include: moving the mouse pointer, pressing a button on the mouse, releasing a button on the mouse, pressing a key on the keyboard, releasing a key on the keyboard etc.  So, for example, if you move the mouse over the window of your browser and click on this text, your operating system will generate at least two events – an event for when you press down on the mouse and an event for when you take your finger off the mouse.  In reality many more events will be generated;
  • an event is a special type of object which Tkinter uses.  The event object has a number of attributes.  In particular, the event object records the type of event (eg mouse click vs keyboard press) and the location in the window where that event occurred;
  • when Tkinter receives an event it checks to see whether you have told it to use a handler for that event.  If so, it will pass the event to the handler, if not it will ignore the event;
  • an event handler is a function (or a method) which does something when the event occurs, and may receive the event as a parameter.  An example of a handler is the nextImage function in this tutorial;
  • the act of telling Tkinter to use a given handler for a given event is called binding the event to the handler.  In our examples we have bound the handler by setting the ‘command’ key of the relevant widget but there is a separate function called bind();
  • in order to become visible in a Window every widget it needs to be pack() ed.   If you ever seem to be missing a widget, make sure that widget has been pack()ed (and, if the widget is inside another widget that all widgets up the chain are pack()ed)
  • Tkinter is imported with the code: 

from Tkinter import *

This code imports everything from the Tkinter namespace into the program’s namespace.  This is normally bad practice, but Tkinter is an exception.

In the tutorials to follow we will put both Tkinter and our knowledge of classes to some use – what’s more, in a vaguely practical way (if you think minecraft is practical that is…)

 

Welcome back, Class Recap

Happy New Year

Welcome back to Python4Kids! I am sorry for being a little slack since the end of last year, but hopefully I’m over that now and we can start powering on again!

Towards the end of last year we started working on classes and a GUI toolkit called Tkinter.   In this tutorial we will recap classes.  Hopefully, we’ll recap Tkinter next.  If you have data and functions which are related to each other in some way, classes allow you to group them together.  This makes managing them easier, particularly as your programs get bigger.

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

This class has one method (called __init__()) and one attribute (called age).  It inherits from object.

Recap bit

The main things we have learned about classes are:

  • they are based on (“inherit from”) some Python object (usually object <- the italics here refers to the Python thing called object as opposed to object in its ordinary sense)
  • classes are defined with the class statement.  The class statement creates an archetype* – that is, a kind of template – from which specific instances are created.   Thus the class allDads was representing all dads in the world, while myDad, was a specific instance of a dad – that is, my dad in particular.
  • data which is stored in a class or instance is called an attribute. 
  • functions which are part of a class or instance are called methods
  • if you have an instance of a class (eg myDad), then the attributes and methods of myDad are identified by the dot operator “.”.  So, the appearance attribute of the instance myDad is identified by myDad.appearance (see the dot?).  If it had a method called makesARobot() (the parenthesis indicates it is a function or method) that would be identified by myDad.makesARobot().
  • the attributes of an instance are (typically) specific to that instance.  If two instances have an attribute of the same name (which will always be the case for attributes defined by the class), changing the attribute for an instance does not affect the attribute of another instance.
  • classes usually define a special method called __init__() (“dunder init“, among other pronunciations).  This method is run whenever an instance of the class is created.  It INITialises the instance.
  • classes make use of special variable called selfself is a bit tricky to explain, but it will become clear when you start using it a bit.  Self is used when defining the class as a way for each instance to refer to that instance of the class, rather than to the class as a whole.

Python’s classes are a core component of the language.  You will end up using them all the time.  In fact, they are also a core part of object oriented programming.  I have introduced classes at the same time as Tkinter because Tkinter needs them.  In order to program Tkinter, you basically need to use classes!

Homework:

Part 1:

Create a class with a method called __init__(self, firstName = None).  Make the __init__ method assign the value of the firstName parameter to an attribute which is also called firstName.  Hint: check the example above, and use self.firstName).

Part 2:

Create some instances of your class, passing your first name as a string (ie put it in quotation marks) to the class. 

Part 3

Print the firstName attribute of the instances you have created.

Notes:

* an “archetype” is “a universally understood symbol or term or pattern of behavior, a prototype upon which others are copied, patterned, or emulated.

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 = ["http://python4kids.files.wordpress.com/2011/08/p4kbuttonimage1.gif", "http://python4kids.files.wordpress.com/2011/08/p4kbuttonimage2.gif", "http://python4kids.files.wordpress.com/2011/08/p4kbuttonimage3.gif", "http://python4kids.files.wordpress.com/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
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 = "http://python4kids.files.wordpress.com/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.

Follow

Get every new post delivered to your Inbox.