Debugging

The raise and assert Statements

Raising Your Own Exceptions

You can raise your own exceptions:

In [1]:
raise Exception('This is an error message')
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-1-aa3c3cd016f8> in <module>()
----> 1 raise Exception('This is an error message')

Exception: This is an error message
In [2]:
'''
******
*    *
*    *
******
'''
def boxPrint(symbol, width, height):
  if len(symbol) != 1:
    raise Exception('"symbol" needs to be a string of length 1')
  if (width < 2) or (height < 2):
    raise Exception('"width" and "height" must be greater than or equal to 2')

  print(symbol * width)

  for i in range(height -2):
    print(symbol + (' ' * (width - 2)) + symbol)

  print(symbol * width)

boxPrint('*', 15, 5)
***************
*             *
*             *
*             *
***************

The traceback.format_exc() Function

In [3]:
import traceback
try:
  raise Exception('This is an error message')
except:
  errorFile = open('error_log.txt', 'a')
  errorFile.write(traceback.format_exc())
  errorFile.close()
  print('The traceback info was written in error_log.txt')
The traceback info was written in error_log.txt

You get the file path of the log file from print(os.getcwd())

Assertions and the assert Statement

You can also use assertions: assert condition, 'error message'

In [4]:
assert False, 'This is an error message'
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-4-1087ddf943e7> in <module>()
----> 1 assert False, 'This is an error message'

AssertionError: This is an error message

Assertions are for detecting programmer errors that are NOT meant to be recovered from. User errors should raise exceptions

In [5]:
market_2nd = {'ns': 'green', 'ew': 'red'}

def switchLights(intersection):
  for key in intersection.keys():
    if intersection[key] == 'green':
      intersection[key] = 'yellow'
    elif intersection[key] == 'yellow':
      intersection[key] = 'red'
    elif intersection[key] == 'red':
      intersection[key] = 'green'

  assert 'red' in intersection[key], 'Neither light is red!' + str(intersection)

print(market_2nd)
switchLights(market_2nd)
print(market_2nd)
{'ns': 'green', 'ew': 'red'}
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-5-26806c7b03b6> in <module>()
     13 
     14 print(market_2nd)
---> 15 switchLights(market_2nd)
     16 print(market_2nd)

<ipython-input-5-26806c7b03b6> in switchLights(intersection)
     10       intersection[key] = 'green'
     11 
---> 12   assert 'red' in intersection[key], 'Neither light is red!' + str(intersection)
     13 
     14 print(market_2nd)

AssertionError: Neither light is red!{'ns': 'yellow', 'ew': 'green'}

Logging

Log messages create a breadcrumb trail of what your program is doing.

The logging.basicConfig() Function

Use basicConfig() to set up logging

In [6]:
import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

Example: Bugging Factorial Program

In [7]:
def factorial(n):
  total = 1
  for i in range(n+1):
    total *= i
  return total

print(factorial(5))
0

The logging.debug() Function

The logging module lets you display the logging messages. After calling basicConfig() to set up logging, call logging.debug() to create a log message

In [8]:
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(n+1):
    total *= i
    logging.debug('i is %s, total is %s' % (i, total))
  logging.debug('Return value is %s' % (total))
  return total

print(factorial(5))

logging.debug('End of program')
2018-03-07 22:37:50,992 - DEBUG - Start of program
2018-03-07 22:37:50,994 - DEBUG - Start of factorial (5)
2018-03-07 22:37:50,995 - DEBUG - i is 0, total is 0
2018-03-07 22:37:50,996 - DEBUG - i is 1, total is 0
2018-03-07 22:37:50,997 - DEBUG - i is 2, total is 0
2018-03-07 22:37:50,998 - DEBUG - i is 3, total is 0
2018-03-07 22:37:50,999 - DEBUG - i is 4, total is 0
2018-03-07 22:37:51,001 - DEBUG - i is 5, total is 0
2018-03-07 22:37:51,002 - DEBUG - Return value is 0
2018-03-07 22:37:51,003 - DEBUG - End of program
0
In [9]:
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 %s, total is %s' % (i, total))
  logging.debug('Return value is %s' % (total))
  return total

print(factorial(5))

logging.debug('End of program')
2018-03-07 22:37:55,889 - DEBUG - Start of program
2018-03-07 22:37:55,891 - DEBUG - Start of factorial (5)
2018-03-07 22:37:55,892 - DEBUG - i is 1, total is 1
2018-03-07 22:37:55,893 - DEBUG - i is 2, total is 2
2018-03-07 22:37:55,894 - DEBUG - i is 3, total is 6
2018-03-07 22:37:55,895 - DEBUG - i is 4, total is 24
2018-03-07 22:37:55,896 - DEBUG - i is 5, total is 120
2018-03-07 22:37:55,897 - DEBUG - Return value is 120
2018-03-07 22:37:55,899 - DEBUG - End of program
120

The logging.disable() Function

When done, you can disable the log messages with logging.disable(logging.CRITICAL). Don't use print() for log messages: It's hard to remove them all when you're done debugging.

Log Levels:

The 5 log levels are: DEBUG, INFO, WARNING, ERROR and CRITICAL

  • logging.debug (lowest)
  • logging.info
  • logging.warning
  • logging.error
  • logging.critical (highest)

Logging to a Text File

You can also log a file instead of the screen with the filename keyword argument to the basicConfig() function

In [10]:
logging.basicConfig(filename='myProgramLog.txt', level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

Using the Debugger

The debugger is a tool that lets you execute Python code one line at a time and shows you the values in variables. Open the Debug Control window with Debug > Debugger BEFORE running the program. Be sure to select Stack, Source, Locals and Globals check boxes.

In [11]:
print('Enter the first number to add:')
first=input() # 1
print('Enter the second number to add:')
second=input() # 2
print('Enter the third number to add:')
third=input() # 3
print('The sum is ' + first + second + third)
Enter the first number to add:
1
Enter the second number to add:
2
Enter the third number to add:
3
The sum is 123

Step Over

The Over button will step over the current line of code and pause on the next one. It doesn't go into the function call

Stepping Into and Stepping Out

The Step button will step into a function call. The Out button will step out of the current function you are in. The Go button will continue the program until the next breakpoint or end of the program. The Quit button will immediately terminate the program.

Breakpoints

Breakpoints can be set by right clicking the line and select set breakpoint

In [12]:
import random

heads = 0

for i in range(1, 1001):
  if random.randint(0,1) == 1:
    heads = heads + 1
  if i == 500:
    print('Halfway done!')

print('Heads came up ' + str(heads) + ' times.')
Halfway done!
Heads came up 502 times.