There’s only one way for a program to run correctly, but so many different ways errors can appear. Some errors are easy to diagnose and fix and others can ruin whole days trying to pin down. Errors in programming fall broadly into two classes: exceptions and bugs.
SubsectionA.6.1Exceptions
Exceptions are those errors which (left untreated) stop the execution of code abruptly. On the scale of all possible errors, this is clearly worse than executes flawlessly, but not much. In Python an Exception is a special data type which is used for all sorts of regularly-occurring programming errors. The following are some of the more common exceptions which arise when working with Python, in no particular order.
TechnologyA.6.1.Syntax Error.
A Python SyntaxError is the programming equivalent of a grammatically incorrect sentence, where you’ve written something which violates the basic rules of the Python language. Python will show you in the error message exactly where your Syntax error occurs. Here’s an example:
>>> foo = "This is a test"
>>> bar =
SyntaxError: invalid syntax
ListingA.6.2.An example which will produce a SyntaxError
TechnologyA.6.3.Index errors.
An IndexError is raised when you try to access an index of a container with “regular” indexing (like lists, tuples, and strings, but not dictionaries) but outside the bounds of the valid indices.
>>> foo = "this is a string"
>>> bar = len(foo) + 5
>>> foo[bar]
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
foo[bar]
IndexError: string index out of range
ListingA.6.4.An example which will produce a IndexError
TechnologyA.6.5.Key errors.
The KeyError is closely related to the IndexError, but for dictionaries! If a key is attempted to be referenced which has not been declared to have a value, a KeyError will result.
ListingA.6.6.An example which will produce a KeyError
TechnologyA.6.7.Type errors.
Another very common error is the TypeError, which is raised whenever the data type of a variable used in a computation is unexpected by the function being called. For instance, many mathematical functions expect numerical input, but the Python interpreter won’t know the error exists until it attempts to execute the expression.
>>> "This is a string"/2
Traceback (most recent call last)
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'str' and 'int'
ListingA.6.8.An example which will produce a TypeError
There are many other types of errors which will be encountered as you try to write codes, too many to describe. Other common exceptions you might see are ValueError, AttributeError, DivideByZeroError, and one which I use frequently as a control mechanism, RuntimeError. In most cases, the well written code underlying whatever you are attempting involves a raise command, the method by which an exception is generally triggered. A notable exception to this is the KeyboardInterrupt, which us useful to remember: it is triggered by pressign Ctrl-C on your keyboard while a program is running in Python.
SubsectionA.6.2Handling exceptions
There is a school of engineering design called by many names which expresses the belief that “a system should only fail into a known state” or that “a system that fails should do so securely.” Applied to programming this idea is expressed in exception handling where violations of the logic of a program are anticipated and treated before they occur. As we should perhaps expect, Python implements this with a block data structure, the try:... except:... block.
TechnologyA.6.9.Python exception handling.
Any exception generated in the indented block following a try: command will attempt to be caught by one of a sequence of except ... : blocks which follow. As only one exception is generated at a time, this behaves as an if:... elif:... sequence; for instance, if one of the blocks begins with except RuntimeError as error_message:, then that block will only be executed when the body of the try: block raises a RuntimeError(...); the optional argument to the error is named error_message within this except block.
These exceptions are usually triggered by a raise command. Here is a meaningless example demonstrating the use of the structure.
n = 1
try:
if n==1:
raise RuntimeError('First error')
elif n==2:
raise ValueError('Second error')
else:
raise TypeError('Third error')
except RuntimeError as error_message:
print "We have encountered a RuntimeError, specifically {0}".format(error_message)
pass
except:
print "All errors but RuntimeErrors are caught here!"
pass
print "The program now exits cleanly."
ListingA.6.10.An example of a try:... except:... block
The code listing in Technology A.6.9 is intentionally stupid. The code doesn’t actually do anything except demonstrate the try:... except:... block structure.
CheckpointA.6.11.Memo-ization.
A cool use of dictionaries and the try:... except:... block is called memo-ization, although it is usually written without the hyphen. Consider the following implementation of the Collatz sequence.
coll_mem = {1:[1], 2:[2,1], 4:[4,2,1]}
def memoized_coll(x):
try:
return coll_mem[x]
except KeyError:
if not type(x)==int or x<1:
return None
elif x%2==0:
next_val = x//2
else:
next_val = 3 * x + 1
coll_mem[x] = [x] + memoized_coll(next_val)
return coll_mem[x]
ListingA.6.12.An implementation of the Collatz sequence problem using a dictionary to store previously-computed resluts.
Picking apart the code, we see that first it sets up a dictionary of known values. Then when asked to produce the Collatz sequence from a given input, the function tries to return the value stored in that key of the dictionary. If no such key exists, a KeyError is generated; when that happens, the program falls back on the \(3x+1\) form of generating the sequence.
Suppose this task is iterated over all integers up to some given value; that is, \(x=1,2,3,\dotsc\) are checked. For a given input value \(x\text{,}\) when will the memoized version of the function always have the key stored in the dictionary? What has to happen to the next_val in the sequence for this to be the case?
RemarkA.6.13.Recursively-defined functions.
A function like memoized_coll in Checkpoint A.6.11 which refers to itself is a recursive function. Typically the first recursively-defined function seen by students is the function which defines the Fibonacci sequence, \(F_{n+2} = F_{n+1} + F_n\) for \(n\in\Nats\) when \(F_0 = F_1 = 1\text{.}\)
SubsectionA.6.3Controlling the flow of a program
Generating errors via the raise command is an extremely powerful way to disrupt the flow of a program. A raise statement outside of a try:... except:... block will halt the program, so they are to be used sparingly. Two other statements which modify the flow of a program in an abrupt way are continue and break.
TechnologyA.6.14.Python continue statements.
When continue is encountered during the execution of a loop, the current step of the loop immediately completes and the loop condition is reevaluated, moving to the next object in a for loop or retesting the conditional of a while loop.
This is particularly useful in cases where the code must handle cases for the loop which do nothing, or are exempt from the behavior encoded in the block structure of the loop, but that do not halt the execution of the loop.
TechnologyA.6.15.Python break statements.
When break is encountered in the execution of a loop, the loop immediately terminates and the program continues past the looping block structure. Unlike with a continue statement, any further elements over which a for loop was programmed to iterate are skipped.
SubsectionA.6.4Semantic errors, or bugs
When left unhandled, exceptions halt the flow of the program, which may be undesirable but at least allows the programmer to detect that an error has occurred with great ease. On the other hand, semantic errors are discreet.
DefinitionA.6.16.Semantic errors are bugs.
A semantic error or bug occurs when a program appears to work, but produces a result other than what was expected. This can occur either due to an error in the algorithm or an error in the implementation of the algorithm.
Bugs are called semantic errors because they do not violate the syntactic rules of the language, but rather are syntactically valid but semantically meaningless. The following English sentence is likewise grammatically well-formed but semantically meaningless:
Colorless green ideas sleep furiously.
―Noam Chomsky, Syntactic Structures, 1957
Semantic errors are sometiems difficult to remedy; one strategy is to simulate the execution of a code by hand, writing down all of the state changes of variables as they occur to see when the error actually arises. Many programming IDEs include automatic debuggers which assist with this, allowing for more complicated problems to be remedied. For a discussion on why software errors are called bugs, see Wikipedia. 13