Python Debugging
In computer programming and software development, debugging is the process of finding and resolving bugs (defects or problems that prevent correct operation) within computer programs, software, or systems.
Raising Exceptions
Exceptions are raised with a raise statement. In code, a raise statement consists of the following:
- The
raise
keyword - A call to the
Exception()
function - A string with a helpful error message passed to the
Exception()
function
>>> raise Exception('This is the error message.')
# Traceback (most recent call last):
# File "<pyshell#191>", line 1, in <module>
# raise Exception('This is the error message.')
# Exception: This is the error message.
Typically, it’s the code that calls the function, not the function itself, that knows how to handle an exception. So, you will commonly see a raise statement inside a function and the try
and except
statements in the code calling the function.
>>> def box_print(symbol, width, height):
... if len(symbol) != 1:
... raise Exception('Symbol must be a single character string.')
... if width <= 2:
... raise Exception('Width must be greater than 2.')
... if height <= 2:
... raise Exception('Height must be greater than 2.')
... print(symbol * width)
... for i in range(height - 2):
... print(symbol + (' ' * (width - 2)) + symbol)
... print(symbol * width)
...
>>> for sym, w, h in (('*', 4, 4), ('O', 20, 5), ('x', 1, 3), ('ZZ', 3, 3)):
... try:
... box_print(sym, w, h)
... except Exception as err:
... print('An exception happened: ' + str(err))
...
# ****
# * *
# * *
# ****
# OOOOOOOOOOOOOOOOOOOO
# O O
# O O
# O O
# OOOOOOOOOOOOOOOOOOOO
# An exception happened: Width must be greater than 2.
# An exception happened: Symbol must be a single character string.
Read more about Exception Handling.
Getting the Traceback as a string
The traceback
is displayed by Python whenever a raised exception goes unhandled. But can also obtain it as a string by calling traceback.format_exc(). This function is useful if you want the information from an exception’s traceback but also want an except statement to gracefully handle the exception. You will need to import Python’s traceback module before calling this function.
>>> import traceback
>>> try:
... raise Exception('This is the error message.')
>>> except:
... with open('errorInfo.txt', 'w') as error_file:
... error_file.write(traceback.format_exc())
... print('The traceback info was written to errorInfo.txt.')
...
# 116
# The traceback info was written to errorInfo.txt.
The 116 is the return value from the write()
method, since 116 characters were written to the file. The traceback
text was written to errorInfo.txt.
Traceback (most recent call last):
File "<pyshell#28>", line 2, in <module>
Exception: This is the error message.
Assertions
An assertion is a sanity check to make sure your code isn’t doing something obviously wrong. These sanity checks are performed by assert
statements. If the sanity check fails, then an AssertionError
exception is raised. In code, an assert
statement consists of the following:
- The
assert
keyword - A condition (that is, an expression that evaluates to
True
orFalse
) - A comma
- A
string
to display when the condition isFalse
>>> pod_bay_door_status = 'open'
>>> assert pod_bay_door_status == 'open', 'The pod bay doors need to be "open".'
>>> pod_bay_door_status = 'I\'m sorry, Dave. I\'m afraid I can\'t do that.'
>>> assert pod_bay_door_status == 'open', 'The pod bay doors need to be "open".'
# Traceback (most recent call last):
# File "<pyshell#10>", line 1, in <module>
# assert pod_bay_door_status == 'open', 'The pod bay doors need to be "open".'
# AssertionError: The pod bay doors need to be "open".
In plain English, an assert statement says, “I assert that this condition holds true, and if not, there is a bug somewhere in the program.” Unlike exceptions, your code should not handle assert statements with try and except; if an assert fails, your program should crash. By failing fast like this, you shorten the time between the original cause of the bug and when you first notice the bug. This will reduce the amount of code you will have to check before finding the code that’s causing the bug.
Disabling Assertions
Assertions can be disabled by passing the -O
option when running Python.
Logging
To enable the logging
module to display log messages on your screen as your program runs, copy the following to the top of your program:
>>> import logging
>>> logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s- %(message)s')
Say you wrote a function to calculate the factorial of a number. In mathematics, factorial 4 is 1 × 2 × 3 × 4, or 24. Factorial 7 is 1 × 2 × 3 × 4 × 5 × 6 × 7, or 5,040. Open a new file editor window and enter the following code. It has a bug in it, but you will also enter several log messages to help yourself figure out what is going wrong. Save the program as factorialLog.py.
>>> import logging
>>> logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s- %(message)s')
>>> logging.debug('Start of program')
>>> def factorial(n):
... logging.debug('Start of factorial(%s)' % (n))
... total = 1
... for i in range(1, n + 1):
... total *= i
... logging.debug('i is ' + str(i) + ', total is ' + str(total))
... logging.debug('End of factorial(%s)' % (n))
... return total
...
>>> print(factorial(5))
>>> logging.debug('End of program')
# 2015-05-23 16:20:12,664 - DEBUG - Start of program
# 2015-05-23 16:20:12,664 - DEBUG - Start of factorial(5)
# 2015-05-23 16:20:12,665 - DEBUG - i is 0, total is 0
# 2015-05-23 16:20:12,668 - DEBUG - i is 1, total is 0
# 2015-05-23 16:20:12,670 - DEBUG - i is 2, total is 0
# 2015-05-23 16:20:12,673 - DEBUG - i is 3, total is 0
# 2015-05-23 16:20:12,675 - DEBUG - i is 4, total is 0
# 2015-05-23 16:20:12,678 - DEBUG - i is 5, total is 0
# 2015-05-23 16:20:12,680 - DEBUG - End of factorial(5)
# 0
# 2015-05-23 16:20:12,684 - DEBUG - End of program
Logging Levels
Logging levels provide a way to categorize your log messages by importance. There are five logging levels, described in Table 10-1 from least to most important. Messages can be logged at each level using a different logging function.
Level | Logging Function | Description |
---|---|---|
DEBUG | logging.debug() | The lowest level. Used for small details. Usually you care about these messages only when diagnosing problems. |
INFO | logging.info() | Used to record information on general events in your program or confirm that things are working at their point in the program. |
WARNING | logging.warning() | Used to indicate a potential problem that doesn’t prevent the program from working but might do so in the future. |
ERROR | logging.error() | Used to record an error that caused the program to fail to do something. |
CRITICAL | logging.critical() | The highest level. Used to indicate a fatal error that has caused or is about to cause the program to stop running entirely. |
Disabling Logging
After you’ve debugged your program, you probably don’t want all these log messages cluttering the screen. The logging.disable() function disables these so that you don’t have to go into your program and remove all the logging calls by hand.
>>> import logging
>>> logging.basicConfig(level=logging.INFO, format=' %(asctime)s -%(levelname)s - %(message)s')
>>> logging.critical('Critical error! Critical error!')
# 2015-05-22 11:10:48,054 - CRITICAL - Critical error! Critical error!
>>> logging.disable(logging.CRITICAL)
>>> logging.critical('Critical error! Critical error!')
>>> logging.error('Error! Error!')
Logging to a File
Instead of displaying the log messages to the screen, you can write them to a text file. The logging.basicConfig()
function takes a filename keyword argument, like so:
>>> import logging
>>> logging.basicConfig(filename='myProgramLog.txt', level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')