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.

Follow

Get every new post delivered to your Inbox.

Join 72 other followers