Week 6 – Functions Error Handling and Debugging
[ad_1]Objectives
To define functions (§6.2).
To invoke value-returning functions (§6.3).
To invoke functions that does not return a value (§6.4).
To pass arguments by values (§6.5).
To pass arguments by values (§6.6).
To develop reusable code that is modular, easy to read, easy to
debug, and easy to maintain (§6.7).
To create modules for reusing functions (§§6.7-6.8).
To determine the scope of variables (§6.9).
To define functions with default arguments (§6.10).
To return multiple values from a function (§6.11).
To apply the concept of function abstraction in software
development (§6.12).
To design and implement functions using stepwise refinement
(§6.13).
To simplify drawing programs using functions (§6.14).
8
Defining Functions
A function is a collection of statements that are
grouped together to perform an operation.
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
function name formal parameters
return value
function
body
function
header
Define a function Invoke a function
z = max(x, y)
actual parameters
(arguments)
9
Function Header
A function contains a header and body. The header begins with the
def keyword, followed by function’s name and parameters,
followed by a colon.
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
function name formal parameters
return value
function
body
function
header
Define a function Invoke a function
z = max(x, y)
actual parameters
(arguments)
10
Formal Parameters
The variables defined in the function header are known as
formal parameters.
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
function name formal parameters
return value
function
body
function
header
Define a function Invoke a function
z = max(x, y)
actual parameters
(arguments)
11
Actual Parameters
When a function is invoked, you pass a value to the parameter. This
value is referred to as actual parameter or argument.
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
function name formal parameters
return value
function
body
function
header
Define a function Invoke a function
z = max(x, y)
actual parameters
(arguments)
12
Return Value
A function may return a value using the return keyword.
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
function name formal parameters
return value
function
body
function
header
Define a function Invoke a function
z = max(x, y)
actual parameters
(arguments)
13
Calling Functions
Testing the max function
This program demonstrates calling a function
max to return the largest of the int values
Return the max between two numbers
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
def main():
i = 5
j = 2
k = max(i, j) # Call the max function
print(“The maximum between”, i, “and”, j, “is”, k)
main() # Call the main function
14
Calling Functions, cont.
def main():
i = 5
j = 2
k = max(i, j)
print(“The maximum between”,
i, “and”, j, “is”, k)
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
pass int 5
pass int 2
main()
15
Trace Function Invocation
Invoke the main function
def main():
i = 5
j = 2
k = max(i, j)
print(“The maximum between”,
i, “and”, j, “is”, k)
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
pass int 5
pass int 2
main()
16
Trace Function Invocation
i is now 5
def main():
i = 5
j = 2
k = max(i, j)
print(“The maximum between”,
i, “and”, j, “is”, k)
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
pass int 5
pass int 2
main()
17
Trace Function Invocation
j is now 2
def main():
i = 5
j = 2
k = max(i, j)
print(“The maximum between”,
i, “and”, j, “is”, k)
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
pass int 5
pass int 2
main()
18
Trace Function Invocation
invoke max(i, j)
def main():
i = 5
j = 2
k = max(i, j)
print(“The maximum between”,
i, “and”, j, “is”, k)
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
pass int 5
pass int 2
main()
19
Trace Function Invocation
invoke max(i, j)
Pass the value of i to num1
Pass the value of j to num2
def main():
i = 5
j = 2
k = max(i, j)
print(“The maximum between”,
i, “and”, j, “is”, k)
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
pass int 5
pass int 2
main()
20
Trace Function Invocation
(num1 > num2) is true
since num1 is 5 and num2
is 2
def main():
i = 5
j = 2
k = max(i, j)
print(“The maximum between”,
i, “and”, j, “is”, k)
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
pass int 5
pass int 2
main()
21
Trace Function Invocation
result is now 5
def main():
i = 5
j = 2
k = max(i, j)
print(“The maximum between”,
i, “and”, j, “is”, k)
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
pass int 5
pass int 2
main()
22
Trace Function Invocation
return result, which is 5
def main():
i = 5
j = 2
k = max(i, j)
print(“The maximum between”,
i, “and”, j, “is”, k)
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
pass int 5
pass int 2
main()
23
Trace Function Invocation
return max(i, j) and assign
the return value to k
def main():
i = 5
j = 2
k = max(i, j)
print(“The maximum between”,
i, “and”, j, “is”, k)
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
pass int 5
pass int 2
main()
24
Trace Function Invocation
Execute the print
statement
def main():
i = 5
j = 2
k = max(i, j)
print(“The maximum between”,
i, “and”, j, “is”, k)
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
pass int 5
pass int 2
main()
25
Trace Function Invocation
Return to the caller
def main():
i = 5
j = 2
k = max(i, j)
print(“The maximum between”,
i, “and”, j, “is”, k)
def max(num1, num2):
if num1 > num2:
result = num1
else:
result = num2
return result
pass int 5
pass int 2
main()
26
Functions With/Without Return Values
This type of function does not return a value. The
function performs some actions.
ReturnGradeFunction Run
PritGradeFunction Run
Functions Without Return Values
Print grade for the score
def printGrade(score):
if score >= 90.0:
print(‘A’)
elif score >= 80.0:
print(‘B’)
elif score >= 70.0:
print(‘C’)
elif score >= 60.0:
print(‘D’)
else:
print(‘F’)
def main():
score = eval(input(“Enter a score: “))
print(“The grade is “, end = “”)
printGrade(score)
main() # Call the main function
27
This type of function does not
return a value. The function
performs some actions.
Functions With Return Values
Return the grade for the score
def getGrade(score):
if score >= 90.0:
return ‘A‘
elif score >= 80.0:
return ‘B’
elif score >= 70.0:
return ‘C‘
elif score >= 60.0:
return ‘D‘
else:
return ‘F’
def main():
score = eval(input(“Enter a score: “))
print(“The grade is”, getGrade(score))
main() # Call the main function
28
This type of function returns a
value.
29
The None Value
A function that does not return a value is known
as a void function in other programming
languages such as Python, C++, and C#. In
Python, such function returns a special None.
def sum(number1, number2):
total = number1 + number2
print(sum(1, 2))
30
Passing Arguments by Positions
def nPrintln(message, n):
for i in range(0, n):
print(message)
Suppose you invoke the function using
nPrintln(“Welcome to Python”, 5)
What is the output?
Suppose you invoke the function using
nPrintln(“Computer Science”, 15)
What is the output?
What is wrong
nPrintln(4, “Computer Science”)
31
Keyword Arguments
def nPrintln(message, n):
for i in range(0, n):
print(message)
What is wrong
nPrintln(4, “Computer Science”)
Is this OK?
nPrintln(n = 4, message = “Computer Science”)
32
Scope of Variables
Scope: the part of the program where the
variable can be referenced.
A variable created inside a function is referred to as
a local variable. Local variables can only be
accessed inside a function. The scope of a local
variable starts from its creation and continues to
the end of the function that contains the variable.
In Python, you can also use global variables. They
are created outside all functions and are accessible
to all functions in their scope.
33
Example 1
globalVar = 1
def f1():
localVar = 2
print(globalVar)
print(localVar)
f1()
print(globalVar)
print(localVar) # Out of scope. This gives an error
34
Example 2
x = 1
def f1():
x = 2
print(x) # Displays 2
f1()
print(x) # Displays 1
35
Example 3
x = eval(input(“Enter a number: “))
if (x > 0):
y = 4
print(y) # This gives an error if y is not created
36
Example 4
sum = 0
for i in range(0, 5):
sum += i
print(i)
37
Example 5
x = 1
def increase():
global x
x = x + 1
print(x) # Displays 2
increase()
print(x) # Displays 2
38
Default Arguments
Python allows you to define functions with
default argument values. The default values are
passed to the parameters when a function is
invoked without the arguments.
def printArea(width = 1, height = 2):
area = width * height
print(“width:”, width, “theight:”, height, “tarea:”, area)
printArea() # Default arguments width = 1 and height = 2
printArea(4, 2.5) # Positional arguments width = 4 and
height = 2.5
printArea(height = 5, width = 3) # Keyword arguments
width
printArea(width = 1.2) # Default height = 2
printArea(height = 6.2) # Default width = 1
39
Returning Multiple Values
Python allows a function to return multiple
values. The code defines a function that takes
two numbers and returns them in ascending
order.
def sort(number1, number2):
if number1 < number2: return number1, number2 else: return number2, number1 n1, n2 = sort(3, 2) print(“n1 is”, n1) print(“n2 is”, n2) 40 Function Abstraction You can think of the function body as a black box that contains the detailed implementation for the function. Function Header Function Body Black Box Optional arguments for input Optional return value 41 Benefits of Functions • Write a function once and reuse it anywhere. • Information hiding. Hide the implementation from the user. • Reduce complexity. 42 Stepwise Refinement The concept of function abstraction can be applied to the process of developing programs. When writing a large program, you can use the “divide and conquer” strategy, also known as stepwise refinement, to decompose it into subproblems. The subproblems can be further decomposed into smaller, more manageable problems. 43 PrintCalender Case Study Let us use a Print Calendar example to demonstrate the stepwise refinement approach. 44 Design Diagram printCalendar (main) readInput printMonth getStartDay printMonthTitle printMonthBody getTotalNumOfDays getNumOfDaysInMonth getMonthName isLeapYear 45 Design Diagram printCalendar (main) readInput printMonth getStartDay printMonthTitle printMonthBody getTotalNumOfDays getNumOfDaysInMonth getMonthName isLeapYear 46 Design Diagram printCalendar (main) readInput printMonth getStartDay printMonthTitle printMonthBody getTotalNumOfDays getNumOfDaysInMonth getMonthName isLeapYear 47 Design Diagram printCalendar (main) readInput printMonth getStartDay printMonthTitle printMonthBody getTotalNumOfDays getNumOfDaysInMonth getMonthName isLeapYear 48 Design Diagram printCalendar (main) readInput printMonth getStartDay printMonthTitle printMonthBody getTotalNumOfDays getNumOfDaysInMonth getMonthName isLeapYear 49 Design Diagram printCalendar (main) readInput printMonth getStartDay printMonthTitle printMonthBody getTotalNumOfDays getNumOfDaysInMonth getMonthName isLeapYear 50 Error / Exception Handling • Right now, getting an error, or exception, in your Python program means the entire program will crash. We don’t want this to happen in real-world programs! • Instead, we want the program to detect errors, handle them, and then continue to run. For example, consider the following program, which has a “divide-byzero” error: def divide(value): return 42 / value print(divide(2)) # 21 print(divide(1)) # 42 print(divide(0)) # ?!?!?!?! • We’re happy that 42 / 2 gives us 21, and that 42 / 1 is 42 – but it’s mathematically illegal to divide by zero – and the program cannot process this instruction. Instead it’ll raise an exception to tell us that something has gone wrong. 51 Error / Exception Handling (Cont’d) • The output of running the following program is as follows: Traceback (most recent call last): File “zeroDivide.py”, line 6, in
print(divide(0))
File “zeroDivide.py”, line 2, in divide
return 42 / value
ZeroDivisionError: division by zero
• This is telling us the error is a ZeroDivisionError…
• …that it happened on line 2 of the divide function (and it also tells us exactly
what the line was – return 42 / value)…
• …and finally that the specific call to the divide function occurred on line 6 (and
again, it gives us the exact line of code that was called – print(divide(0)).
52
Error / Exception Handling (Cont’d)
• Errors can be handled with try and except statements.
• The code that could potentially have an error is put in a try clause, and the program
execution moves to the start of a following except clause if an error occurs.
• As such, we can re-write our divide function as follows for it to be able to gracefully
handle a divide-by-zero error without crashing:
def divide(value):
try:
return 42 / value
except ZeroDivisionError:
print(‘Error: Invalid argument.’)
• The output when calling the function and passing in 2, 1 and then 0 now becomes:
21 # 42 / 2 is 21
42 # 42 / 1 is 42
Error: Invalid argument. # 42 / 0 is invalid, but no crash!
53
Error / Exception Handling (Cont’d)
• Another way that we could have achieved the same result would be to move the
try / except code out of the function, and instead wrap our calls to the function:
def divide(value):
return 42 / value
try:
print(divide(2))
print(divide(0)) # This line causes an error…
print(divide(1)) # …so this line gets skipped!
except ZeroDivisionError:
print(‘Error: Invalid argument.’)
• However, when doing it this way – as soon as we hit a line of code that causes
an exception, then all following lines of code in the try block are immediately
skipped! The only way around this with the try / except code outside the function
is to wrap each and every call to the function in its own try / except block, which
gets messy and cumbersome, so is best avoided.
JJuumpp!!
54
Error / Exception Handling (Cont’d)
• There are lots of different types of exceptions built into Python, but they all
subclass BaseException.
• You can take a look at them here if you’re interested:
https://docs.python.org/3/library/exceptions.html
• The base classes for exceptions are:
• BaseException (we should leave this alone as an ‘internal’ Python thing),
• Exception (a generic exception we can subclass from),
• ArithmeticError (i.e. OverflowError, ZeroDivisionError etc.),
• BufferError (raised when some buffer related operation fails), and
• LookupError (raised when a key or index on some mapping sequence is
invalid).
• Each of these base classes has a number of specific subtypes – but if a
function can fail, you can always safely wrap it in a standard Exception and be
confident that it’ll be caught successfully.
Quiz: So if we can catch everything as an Exception, why should we have different subtypes?
55
Raising Exceptions
• As we’ve seen, Python raises an exception whenever it tries to execute invalid
code, and raising an exception is a way of saying: “Stop running the code in this
function and move the program execution to the except statement.”
• Exceptions are raised with a raise statement, which consists of the following:
• The raise keyword,
• A call to the Exception() function, and
• A string with a helpful error message passed to the Exception() function.
• For example, we could enter the following into the interactive shell:
raise Exception(‘This is the error message.’)
Traceback (most recent call last):
File “”, line 1, in
raise Exception(‘This is the error message.’)
Exception: This is the error message.
56
Raising Exceptions (Cont’d)
• If there are no try and except statements covering the raise statement
that raised the exception, the program simply crashes and displays the
exception’s error message.
• Often 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.
• For example, let’s write some code prints a box shape out of characters and that
raises exceptions if the arguments passed to the function don’t meet some criteria:
def boxPrint(symbol, width, height):
if len(symbol) != 1:
raise Exception(‘Symbol must be a single character.’)
if width <= 2:
raise Exception(‘Width must be greater than 2.’)
if height <= 2:
raise Exception(‘Height must be greater than 2.’)
57
Raising Exceptions (Cont’d)
print(symbol * width)
for i in range(height – 2):
print(symbol + (‘ ‘ * (width – 2)) + symbol)
print(symbol * width)
for sym, w, h in ((‘*’, 10, 3), (‘x’, 1, 3), (‘ZZ’, 3, 3)):
try:
boxPrint(sym, w, h)
except Exception as err:
print(‘An exception happened: ‘ + str(err))
• When running the code above will call the boxPrint function three times,
resulting in the following output:
- *
An exception happened: Width must be greater than 2.
An exception happened: Symbol must be a single character.
Still in
boxPrint
function
58
Getting the Traceback as a String
• When Python encounters an error, it produces a treasure trove of error
information called the traceback.
• The traceback includes the error message, the line number of the line that
caused the error, and the sequence of function calls that led to the error. This
sequence of calls is called the call stack.
• Let’s write some code to raise an exception and view the call stack:
def test():
epic_fail()
def epic_fail():
raise Exception(‘Failing hard!’)
test()
59
Getting the Traceback as a String (Cont’d)
• When we run the previous code we’ll get the following traceback:
Traceback (most recent call last):
File “errorExample.py”, line 7, in
epic_fail()
File “errorExample.py”, line 2, in test
epic_fail()
File “errorExample.py”, line 5, in epic_fail
raise Exception(‘Failing hard!’)
Exception: Failing hard!
• We can see that the error happened on line 5 in the epic_fail() function…
• …and this particular call to epic_fail() came from line 2 in the test() function…
• …which itself was called from line 7.
60
Getting the Traceback as a String (Cont’d)
• In programs where functions can be called from multiple places, the call stack
can help you determine which call led to the error – which is massively helpful.
• The traceback is displayed by Python whenever a raised exception goes
unhandled – but you 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.
• We need need to import Python’s traceback module before calling this
function. For example, instead of crashing our program right when an exception
occurs, we can write the traceback information to a log fie and keep
the program running.
• Later on, when we’re ready to debug the program, we can look at the log fie
later and look up what happened. Let’s give it a go…
61
Getting the Traceback as a String (Cont’d)
import traceback
try:
raise Exception(‘This is the error message.’)
print(‘Hello!’)
except:
errorFile = open(‘errorInfo.txt’, ‘w’)
errorFile.write(traceback.format_exc())
errorFile.close()
print(‘The traceback info was written to errorInfo.txt.’)
• Running this code will result in the following output being in the errorInfo.txt file:
Traceback (most recent call last):
File “traceback.py”, line 4, in
raise Exception(‘This is the error message.’)
Exception: This is the error message.
• Quiz: Will the ‘Hello’ message be printed out when the code above is executed?
62
Unit Testing a Function That Raises an Exception
• Sometimes the correct ‘answer’ to a unit test is that the function being tested
raises an exception. For example, if we try to divide a value by zero, then we want
the program to raise a DivideByZero exception rather than just crash!
def divide_two_values(first, second):
if (second == 0.0):
raise ArithmeticException(‘Cannot divide by zero!’)
else:
result = first / second
return result
• Now to check that this divide by zero exception is working properly we might write
a unit test like this:
def check_divide_by_zero(self):
self.assertRaises(Exception, divide_two_values, (3.0, 0.0))
Note: We can get away with catching an Exception in our assertion because ArithmeticException is a type-of (i.e.
subclass of) the generic Exception class!
FFuunnccttiioonn ttoo rruunn AArrgguumeennttss
63
Logging
• If you’ve ever put a print() statement in your code to output some variable’s
value while your program is running, then you’ve used a form of logging to
debug your code.
• Logging is a great way to understand what’s happening in your program and in
what order its happening.
• Python’s logging module makes it easy to create a record of custom messages
that you write. These log messages will describe when the program execution
has reached the logging function call and list any variables you have specified
at that point in time…
• …but on the other hand, a missing log message indicates a part of the code
was skipped and never executed!
• Both of these can be very useful in figuring out exactly what’s happening when
your code runs – so let’s take a look at how logging can be used in Python.
64
Using the Logging Module
• To enable the logging module to display log messages on our screen as a
program runs, we can insert the following to the top of our program:
import logging
logging.basicConfig(level=logging.DEBUG, format=’ %(asctime)s
- %(levelname)s – %(message)s’)
• That second line might look a little complex on first glance, but it’s really not that
bad – all we’re saying is:
• When we’re debugging our program and we log an event…
• …write the current time then a dash…
• …followed by the debug level (more on which shortly) followed by a dash…
• …and then finally write the actual message to log.
• Essentially, whenever Python logs an event it creates a LogRecord object that
holds information about the event, and the logging.basicConfig settings
determine what gets displayed and how it’s formatted.
65
Using the Logging Module (Cont’d)
• Let’s write a function to calculate the factorial of a number (i.e. 2! is 2 x 1, 3! is 3 x 2 x 1, 4! is
4 x 3 x 2 x 1 etc.) – and we’ll log what’s going on:
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 ‘ + str(n))
total = 1
for i in range(1, n + 1):
total *= i
logging.debug(‘i is ‘ + str(i) + ‘, total is ‘ + str(total))
logging.debug(‘Final value: ‘ + str(total))
return total
print( factorial(5) )
logging.debug(‘End of program’)
66
Using the Logging Module (Cont’d)
• The output when we run the program to calculate 5! is now:
2017-12-15 14:54:20,791 – DEBUG – Start of program
2017-12-15 14:54:20,807 – DEBUG – Start of factorial 5
2017-12-15 14:54:20,807 – DEBUG – i is 1, total is 1
2017-12-15 14:54:20,822 – DEBUG – i is 2, total is 2
2017-12-15 14:54:20,822 – DEBUG – i is 3, total is 6
2017-12-15 14:54:20,838 – DEBUG – i is 4, total is 24
2017-12-15 14:54:20,838 – DEBUG – i is 5, total is 120
2017-12-15 14:54:20,838 – DEBUG – Final value: 120
120
2017-12-15 14:54:20,854 – DEBUG – End of program
• That worked out okay! We have the timestamp, then the
debug level, then whatever message we added when we
called the logging.debug() function – winning! =D
This is the
actual ‘print’
output of the
function!
67
Don’t Debug with print()
• Typing import logging and logging.basicConfig(level=logging.DEBUG,
format=’%(asctime)s – %(levelname)s – %(message)s’) is somewhat unwieldy –
so you may want to use print() calls instead, but don’t give in to this temptation!
• Once you’re done debugging, you’ll end up spending a lot of time removing
print() calls from your code – and you might even accidentally remove some
print() calls that were being used for non-logging messages by mistake!
• The nice thing about log messages is that you’re free to fill your program
with as many as you like, and you can always disable them later by adding
a single logging.disable(logging.CRITICAL) call.
• Unlike print(), the logging module makes it easy to switch between showing and
hiding messages, because log messages are intended for the programmer, not
the user.
• For messages that the user will want to see, like File not found or Please enter a
number, you should use a print() call, but for everything else you should use log!
68
Logging Levels
• Logging levels provide a way to categorise your log messages by importance.
There are five logging levels, described below, which go from least to most
important – and messages can be logged at each level using a different logging
function.
Level Function Description
DEBUG logging.debug() Least important – used for small details &
debugging.
INFO logging.info() Used to record information on general events to
confirm things are working correctly.
WARNING logging.warning() Used to indicate a potential problem that might
not crash the program, but might in the future.
ERROR logging.error() Used to record an error that caused the program
to fail. May be recoverable, but possibly not.
CRITICAL logging.critical() Most important – used to indicate a fatal error
that will cause the program to stop running.
69
Logging Levels (Cont’d)
• The benefit of logging levels is that you can change what priority of
logging message you want to see when you run your program.
• Passing logging.DEBUG to the logging.basicConfig() function’s level
parameter will show messages from all the logging levels upwards (because
DEBUG is the lowest level).
• After developing your program some more, you may only be interested in
errors. In that case, you can set basicConfig()’s level argument to
logging.ERROR so that it will only show ERROR and CRITICAL messages
and skip any messages logged at DEBUG, INFO, or WARNING levels.
• Finally, so that you don’t have to go all the way through your code disabling
each logged message, you can simply call logging.disable() and specify a
logging level to supress all log messages at that level or lower.
• So calling logging.disable(logging.INFO) will disable log messages at INFO
and DEBUG levels, while calling logging.disable(logging.CRITICAL) will
disable all logged messages at all levels (because CRITICAL is the highest).
70
Introduction to Debugging
• Now that we know enough to write complicated programs, we may start finding
not-so-simple bugs in them – so in this last section of the class we’ll look at
how we can use a debugger to help us find the cause of bugs in our code so we
can fix them faster and with less effort.
• To paraphrase an old joke among programmers: “Writing code accounts for 90
percent of programming. Debugging code accounts for the other 90 percent!”
• This is because a computer will only ever do what you tell it to do; it won’t read
your mind and do what you intended it to do! Even professional programmers
create bugs all the time, so don’t feel discouraged if your code has a
problem.
• Fortunately, debuggers allow us to identify exactly what our code is doing at
any given moment so we can find out where it’s going wrong.
• Let’s take a look at using a debugger to step through our Python code, and then
we’ll be done with the class for today.
This section of the course is based on: Automate the Boring Stuff with Python (2015, Apress)
71
Introduction to Debugging (Cont’d)
• ‘Stepping through’ our program means that we can run it one instruction at a
time, which gives us a chance to inspect the values of variables while our code
runs – and hence track how the variables change over the course of the
program’s execution.
• This is much slower than running the program at full-speed, but is helpful to see
how the actual values change. And this in turn, gives us an opportunity to see
exactly what’s going on at any given time – rather than us just trying to deduce
what the values might (or should!) be from looking at the source code.
72
IDLE’s Debugger
• We’ll look at using the debugger in Python’s default IDE (IDLE), and use it to
step through our program.
• The debugger will run a single line of code and then wait for you to tell it to
continue. By running your program “under the debugger”, you can take as much
time as you want to examine the values in the variables at any given point
during the program’s lifetime – which is a valuable tool for tracking down bugs.
• To enable IDLE’s debugger,
click Debug | Debugger in
the interactive shell window.
This will bring up the Debug
Control window as shown to
the right.
73
IDLE’s Debugger (Cont’d)
• When the Debug Control window appears, select all four of the Stack,
Locals, Source, and Globals checkboxes so that the window shows the full
set of debug information.
• While the Debug Control window is displayed, any time you run a program from
the file editor, the debugger will pause execution before the first instruction and
display the following:
• The line of code that is about to be executed,
• A list of all local variables and their values, and
• A list of all global variables and their values.
74
IDLE’s Debugger (Cont’d)
• When debugging a program, you’ll notice that in the list of global variables there
are several variables you haven’t defied, such as builtins, doc,
file, and so on.
• These are variables that Python automatically sets whenever it runs a program.
The meaning of these variables is beyond the scope of this class, and you can
comfortably ignore them.
• When debugging a program, it will stay paused until you press one of the five
buttons in the Debug Control window: Go, Step, Over, Out, or Quit.
• Go
• Clicking the Go button will cause the program to execute normally until it
either terminates or reaches a breakpoint (more on which later). If you are
done debugging and want the program to continue normally, click the Go
button.
75
IDLE’s Debugger (Cont’d)
• Step
• Clicking the Step button will cause the debugger to execute the next line
of code and then pause again, where the Debug Control window’s list of global
and local variables will be updated if their values change.
• If the next line of code is a function call, the debugger will “step into” that
function and jump to the first line of code of that function.
• Over
• Clicking the Over button will execute the next line of code, similar to the
Step button – however, if the next line of code is a function call, the Over
button will “step over” the code in the function. This means that the function’s
code will be executed at full speed, and the debugger will pause as soon as
the function call returns.
• For example, if the next line of code is a print() call, you don’t really care
about code inside the built-in print() function; you just want the string you
passed it printed to the screen. For this reason, using the Over button is more
common than using the Step button.
76
IDLE’s Debugger (Cont’d)
• Out
• Clicking the Out button will cause the debugger to execute lines of code
from the current location at full speed until it returns from the current
function.
• If you have stepped into a function call with the Step button and now simply
want to keep executing instructions until you get back out, click the Out
button to “step out” of the current function call.
• Quit
• If you want to stop debugging entirely and not bother to continue executing
the rest of the program, click the Quit button.
• The Quit button will immediately terminate the program. If you want to run
your program normally again, select Debug | Debugger again to disable
the debugger.
77
Debugging a Number Adding Program
• Let’s go through the process of debugging a simple program that adds three
numbers and returns the sum. We’ll start off by making the code a little buggy so we
have something to do when we go to fix it!
print(‘Enter the first number to add:’)
first = input()
print(‘Enter the second number to add:’)
second = input()
print(‘Enter the third number to add:’)
third = input()
print(‘The sum is ‘ + first + second + third)
• If we run this program, we might get output like this:
Enter the first number to add:
5
Enter the second number to add:
3
Enter the third number to add:
42
The sum is 5342
78
Debugging a Number Adding Program (Cont’d)
• Okay – so the program hasn’t crashed,
but the sum is obviously wrong. Let’s
enable the Debug Control window and
run it again, this time under the debugger.
• When you press F5 or select Run | Run
Module (with Debug | Debugger enabled
and all four checkboxes on the Debug
Control window checked), the program
starts in a paused state on line 1 (the
debugger will always pause on the line
of code it is about to execute).
In this case, the Debug Control window
will look as shown on the right:
79
Debugging a Number Adding Program (Cont’d)
• If we click the Over button we’ll step ‘over’ (i.e. just execute) the first print line:
80
Debugging a Number Adding Program (Cont’d)
• We can then click Over again to run the first input statement, at which point
Python will wait for us to enter a value and we can’t proceed any further until
we’ve done so.
• Doing this for the second and third print / input pairs will result in the debugger
showing the value of the global variables as follows (if we enter 5, 3 and 42 as
we did previously):
• At this point we can see that the first, second and third values are the string
values ‘5’, ‘3’ and ’42’ instead of the expected Integer values 5, 3 and 42.
81
Debugging a Number Adding Program (Cont’d)
• So when we use the addition operator with strings the values get concatenated
instead of being mathematically added – which is why we get our incorrect
result of 5342 instead of the expected value of 50.
• Stepping through the program with the debugger is helpful but can also be slow.
• Often you’ll want the program to run normally until it reaches a certain line of
code. You can configure the debugger to do this with breakpoints.
• A breakpoint can be set on a specific line of code and forces the debugger to
pause whenever the program execution reaches that line.
• Let’s open a new file editor window and enter the following program, which
simulates flipping a coin 1,000 times, and then save it as coinFlip.py…
82
Using Breakpoints
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.’)
• The random.randint(0, 1) call will return 0 half of the time and 1 the other
half of the time, which can be used to simulate a 50/50 coin flip.
• When we run this program without the debugger, it quickly outputs something
like the following:
Halfway done!
Heads came up 490 times.
83
Using Breakpoints (Cont’d)
• If you ran this program under the debugger, you would have to click the Over
button thousands of times before the program terminated!
• If you were interested in the value of heads at the halfway point of the
program’s execution, when 500 of 1000 coin flips have been completed, you
could instead just set a breakpoint on the line print(‘Halfway done!’)
• To set a breakpoint, right-click the line in the file editor and select Set
Breakpoint, as shown below:
84
Using Breakpoints (Cont’d)
• You don’t want to set a breakpoint on the if statement line, since the if
statement is executed on every single iteration through the loop.
• By setting the breakpoint on the code inside the if statement block, the
debugger breaks only when the execution enters the if clause.
• The line with the breakpoint will be highlighted in yellow in the file editor. When
you run the program under the debugger, it will start in a paused state at the
first line, as usual. But if you click Go, the program will run at full speed until it
reaches the line with the breakpoint set on it. You can then click Go, Over,
Step, or Out to continue as normal.
85
Wrap Up
• In this weeks lab you’ll get a chance to experiment further using the debugger
to step through code and step into and out from functions.
• We’ll also write a test case with a number of unit tests that ensure functions and
classes work correctly (each test case consists of multiple unit tests).
• There’ll be some logging at different levels (which we can leave in our code and
set to ignore by specifying to disable logging of up to the CRITICAL level –
which is all of it!).
• Finally, we’ll look at some exception handling and how we can perform different
corrective actions based on the type of exception that occurred.
[Button id=”1″]
[ad_2]
Source link
"96% of our customers have reported a 90% and above score. You might want to place an order with us."
