Python Debugging

Finding and resolving bugs

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 or False)
  • A comma
  • A string to display when the condition is False
>>> 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.

LevelLogging FunctionDescription
DEBUGlogging.debug()The lowest level. Used for small details. Usually you care about these messages only when diagnosing problems.
INFOlogging.info()Used to record information on general events in your program or confirm that things are working at their point in the program.
WARNINGlogging.warning()Used to indicate a potential problem that doesn’t prevent the program from working but might do so in the future.
ERRORlogging.error()Used to record an error that caused the program to fail to do something.
CRITICALlogging.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')

Subscribe to pythoncheatsheet.org

Join 14,100+ Python developers in a two times a month and bullshit free publication , full of interesting, relevant links.