Module #3

In this module you will learn how to step through your code line-by-line using a debugger. We'll also learn about another way to 'repeat' blocks of code using the for loop. Additionally, we'll learn how we can handle more complex situations like iterating over 2 or 3-dimensional data using nested loop structures. And finally we'll discuss how to make our programs more robust using simple data validation.


Using a Debugger

Sooo... you've finished writing your new program πŸ™Œ! All done, nothing else to do here, amirite? But wait! You have to test your program first to see if it works. When you test your program, assuming that it runs (there are no syntax issues), you'll inevitably encounter errors: runtime and logical errors. These program defects are called bugs πŸ•·πŸ›πŸœπŸž.

Debugging is the process of finding and resolving problems or errors in a program; this can be done by adding code to the program to aid in debugging (such as printing out values of variables) or by using a tool that facilitates the debugging process.

Debugging can be as simple as using print statements to trace the execution of your code. This is a legitimate and very useful debugging technique (though it may result in many extraneous prints... and it may be trickier working through larger programs with just print statements).

If you choose to use a tool, you'll be using a debugger. A debugger is an application (either standalone or part of a text editor or integrated development environment) that aids in the finding and fixing of runtime errors and logical errors. Debuggers can be graphical, or they can be run through a commandline interface.

A debugger allows you to:

  • execute a program's code line by line
  • and examine the values that are in a program's variables

This is done by:

  1. Marking one or more lines as break points
    • that is, the point in a program during debugging where regular execution is stopped and control of program execution is ceded to the debugger
    • this essentially stops or pauses the program πŸ›‘!
  2. Once paused, continuing execution of a program by using the debugger's features to step over or step into the next line of the function:
    • Stepping over executes the program until the next line (without going into function calls)
    • Stepping into continues line-by-line execution into the function definition of the function called in the line

We haven't defined our own functions yet, so for now, using step over is adequate to advance to the next line of the program.

PyCharm has a built-in debugger. You can set break points by clicking a line number... and you can execute your program through the debugger by going to Run → Debug. This will bring up the debug panel where you can choose to stop execution of the program. Note that ⚠️ you must set breakpoints prior to debugging, otherwise you will not have the opportunity to pause the program.

Check out the video πŸ‘€πŸ“Ή for a quick demo of debugging with print statements and with PyCharm's built-in debugger.


For Loops

Count Controlled Loops

The while loop structure that we learned about in Module 4 is especially useful when iterating an unknown number of times. However, when you want to write code that iterates a fixed number of times it's usually more convenient to use a "count controlled loop." In Python this kind of loop is called a for loop.

A for loop can iterate over any fixed set of items. Those items can be as simple as a series of numbers, or more complex like a series of strings or other data types. A for loop has the following form:

for VARIABLE in ITEMS:
    STATEMENTS

In plain English this could be read as "for each VARIABLE that exists in ITEMS execute STATEMENTS." In this example the words in UPPERCASE will be replaced by the data you're working with. You can create a list of items by placing values separated by commas in between an opening and closing square brace like so: [ VALUE_1, VALUE_2, ..., VALUE_N ]. The code in STATEMENTS is the body of the for loop. The code in the body is going to be run once for each item in the list. In addition, each item in the list is temporarily stored in the specified VARIABLE for one iteration (execution) of the body.

Let's take a look at a concrete example to make it more clear:

Sample Program: The following program will iterate over the list of numbers provided and print each number one time. Try changing the values to experiment with it.


Note: Previously we said that you should use descriptive variable names (i.e. names that actually describe the data they hold). You will notice though that we used the variable 'i' in our code above. The 'i' in this case is short for "iterator" or "integer" and is considered a standard coding convention to use, so it is acceptable in this case.

You must have at least one statement in your for loop, but you can have as many additional statements as you want. You can also use accumulators (like you did with while loops) or other variables from outside of your for loop as well.

Sample Program: The following program illustrates multiple statements with an accumulator.


The for loops in Python can iterate over any type of data making it possible to use with strings, floats, characters, etc. In addition we can nest other programming structures inside of a for loop, so we can include statements like if, elif, else statements inside of a for loop. With each layer of nesting, we have to make sure our indention amount is equivalent to the level of nesting that we want in our program.

Sample Program: The following program illustrates iterating over a list of strings with an if statement nested inside the body of the for loop.

The range() function

In this module, the first program we showed you looped through the set of integers from 10 to 50 skipping 10 each time. But what if we want to iterate over a much larger set of numbers (say, the numbers 1 through 100)? We could spend our time writing out every single number we want to iterate over inside of a list, but that would be a very inefficient use of our time. Instead we can use a new function called range to generate a series of numbers for us.

The simplest form of the range() function is to pass it one parameter. For example:

range(10)

will return the series of numbers:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9

So we see that range(N) returns the set of numbers from 0 inclusively (including that number) to N exclusively (excluding that number).

We now have a quick and easy way to get a large list of numbers:

Sample program: The program below iterates over a large set of numbers and computes a running total. Try these extensions to the program once you understand the basics:

  • Update the code so that it calculates 10,000 inclusively (solution)
  • Update the code so that it only sums the multiples of 3 (solution)


Using range() with a starting point

You may not always want to start counting at 0. Let's take the following situation for example. Suppose you want to ask the user what number to start and end? You could write code like this:

total = 0
start = int(input("Enter starting number"))
end = int(input("Enter ending number"))
for i in range(end + 1):      # +1 to make it inclusive
    if i < start:
        # ignore values less than the starting point
        continue
    total += i
print("Sum from", start, "to", end, "inclusively:", total)

The code works, but it's not very pleasant to read and there is a better way. The range() function is pretty flexible and has some extra functionality built-in to it that we can take advantage of. It has what we call "optional" parameters. We can pass the range() function both a starting and a stopping value in the form range(START_VALUE, END_VALUE).

Important:The order of these parameters is critical - the starting number must be supplied first and the ending number must be supplied second.

We can simplify our code from above to incorporate these values and get rid of the if statement:

total = 0
start = int(input("Enter starting number"))
end = int(input("Enter ending number"))
for i in range(start, end + 1):      # +1 to make it inclusive
    total += i
print("Sum from", start, "to", end, "inclusively:", total)

Using range() with a step size

If we want to find all the multiples of 5 from 1 to 50, we could have the following code:

for i in range(1,50+1):
    if i % 5 == 0:
        print(i, "is a multiple of 5")

As we said before, the range() is very flexible, it actually has a way for us to use a step size in how much i will be incremented with each iteration. For that, we include a 3rd parameter so the syntax becomes: range(START_VALUE, STOP_VALUE, STEP_SIZE). In that case, our above code can be simplified to:

for i in range(5, 50+1, 5):
    print(i, "is a multiple of 5")

Using range() in reverse

So far we have show you how to count from a starting point and approaching an ending point and we have seen that by default the step value for each iteration is 1, but that value can be modified. Let's say that we want to write a countdown timer for a rocket launch system. We want that count to start at 10 and count down to 0. To do that, we can just set the step value to a negative value, so that it is subtracting instead of adding with each iteration!

Note: When using a negative step size, you still need to keep your inclusivity and exclusivity rules in mind as they don't change!

Sample Program: counts backwards using range()


For Loop Practice Problems

Programming Challenge: Write a program that asks the user to enter the a starting number (integer), ending number (integer) and the word "even" or "odd". Then generate a customized printout based on their input. Here's a sample running of the program:
Starting number: 5
Ending number: 15
Even or Odd?: even

6
8
10
12
14
Click the "Run" button to check your work, and click here to download the solution.

Programming Challenge: Write a program that asks the user to enter in a number of products (as an integer). Then prompt the user for that number of prices and compute the total cost of all products. Here's a sample running of your program

Enter a number of products: 3
Enter price for product # 1: 100
Enter price for product # 2: 200
Enter price for product # 3: 300

Total cost: 600

Click the "Run" button to check your work, and click here to download the solution.



Nested Loops

A "nested" loop is a loop that has been placed inside of the body of another loop. The most common form of nested loop structures is probably a for loop nested inside of another for loop, but you can also have any set of nested combinations of structures such as: a for loop inside of a while loop, a while loop inside of a for loop, or a while loop inside of a while loop.

Nested for loops can be extremely useful when dealing with multi-dimensional data. A good example is any time you need to work with data that has two or more pieces of information per entry. Let's take an image for example. Every computer image we look at on a screen is made up a of a series of colored squares called "pixels" that are arranged as a grid. A pixel is the smallest representable part of an image. If we zoom in on a picture we can see the individual pixels as block like structures. Here is an example:

A two dimensional matrix of pixel values

In this example, we will use some pseudocode to demonstrate how we could iterate through each pixel in an image. Pseudocode is a design tool that we use as programmers to lay out the structural design of a program, without worrying about details of a particular language. Pseudocode tends to look a lot like Python code because Python code was significantly influenced by the simplicity of it.

# To process the entire image one pixel at a time, we first subdivide
# the problem into processing each row of pixels, one row at a time.
# process each row individually
for every row in image_pixels:
    # Process an entire row, by iterating
    # over the row one pixel at a time
    for every pixel in row:
        # analyze and process a single pixel in the row.
        # after analyzing, the for loop will move to the next pixel
        # if no more pixels are in the row, it jumps back to the outer loop 

    # When a row has been completed,
    # the program will move to the next row
    # if all the rows have been completed, the outer for loop will finish

# At this point all the pixels will have been processed

Another example of using nested loops might be to ask the user for a number of years - we can then iterate over these years and then iteratve over the months in those years. Here's an example:

for year in range(2015, 2017):
    print ("Year: ", year)
	
    # now iterate over months in this year
    for month in range(1, 13):
        print ("Month #: ", month)

Programming Challenge: Write a program that asks a teacher for the number of students in his or her class. Next, ask the teacher how many assignments are given in this class. With this information prompt the user to enter in scores for each student and compute their average grade in the class. Here's a sample running of your program:

How many students in the class? 2
How many assignments in the class? 2

Student #1
Assignment #1: 100
Assignment #2: 90
Student #1 earned a 95

Student #2
Assignment #1: 90
Assignment #2: 80
Student #2 earned a 85
.

Click the "Run" button below to test your program. You can download the solution to this problem by clicking here.



Simple Data Validation

All of us have had programs crash on us or do things that we may not have expected. Often these are results of bugs in the program. When we design programs we want to write programs that are robust so that they crash as rarely as possible. Finagle's Law states: "Anything that can go wrong, willβ€”at the worst possible moment." Our programs should be ready to handle these situations in a graceful way. That means that we should employ some amount of defensive programming when we write code. One way of writing code defensively is to check that the data we're receiving is within the range of valid values.

Take a look at this sample code:

numerator = input("Enter a numerator: ")
denominator = input("Enter a denominator: ")
result = numerator/denominator
print(numerator, "divided by", denominator, "is:", result)

If the user enters a denominator of 0, the program will crash:

Enter a numerator: 10
Enter a denominator: 0
Traceback (most recent call last):
  File "code01.py", line 3, in 
    result = numerator/denominator
ZeroDivisionError: division by zero

Not only do these kinds of errors crash the program, but the error messages for the user can be very confusing and are full of extraneous information that they don't need to know about (but are helpful in debugging).

A better way to handle the situation is to check the data before you use it using the if statement to make sure it's within the proper range of values.

Sample Program: This program shows how you can perform simple data validation to check that invalid data is not being used in a calculation.


Or better yet, we can wrap the code that asks the user for a value inside of a while loop so that it keeps asking the user until we receive a valid input. Below are a couple ways this can be done. Compare the different ways and think about how each functions differently and what advantages each may have:

Sample Program: Click the "Run" button to see the program in action or download a copy.

Sample Program: Click the "Run" button to see the program in action or download a copy.

Sample Program: Click the "Run" button to see the program in action or download a copy.


Controlling Speed in Turtle Graphics

Note that the graphics techniques presented in this section will not run correctly using our web-based coding environment. To try any of the code samples included below simply launch IDLE on your own computer and run the code locally.

Until now all of our turtle graphics programs have been drawing in "real-time" (i.e. we have had to wait and watch as Python methodically followed our directions and rendered our shapes to the screen). This sub-module will introduce you to a series of additional functions which can be used to speed up this process and make it easier to draw more complicated shapes.

Hiding the Turtle

Sometimes you don't want to see the actual turtle cursor on the screen. You can remove the cursor easily by calling the hideturtle function at the beginning of the program. Here's a quick program that shows this in action:

# make the turtle graphics module available
import turtle

# also make the random module available
import random

# set up our graphical canvas
# width = 500, height = 500
turtle.setup(500, 500)

# hide the turtle
turtle.hideturtle()

# draw a square
for side in range(4):
    turtle.forward(100)
    turtle.right(90)

Note this will not speed up the turtle at all - this technique simply removes the small "triangle" associated with the turtle cursor.

Turtle Drawing Speed

You can adjust the drawing speed of the turtle by calling the speed function. This function takes one integer as an argument - this integer describes how fast the turtle should draw shapes to the screen. 1 is the slowest speed for the turtle, 5 is moderate and 10 is fast. The fastest speed the turtle can draw at is speed 0. Here's an example of this in action:

# make the turtle graphics module available
import turtle

# also make the random module available
import random

# set up our graphical canvas
# width = 500, height = 500
turtle.setup(500, 500)

# hide the turtle
turtle.hideturtle()

# set the speed to the absolute fastest speed possible
turtle.speed(0)

# loop 100 times
for j in range(100):

    # pick up the pen and move to a random position on the screen
    turtle.penup()
    turtle.goto(random.randint(-250, 250), random.randint(-250, 250))
    turtle.pendown()

    # draw a square here
    for side in range(4):
        turtle.forward(100)
        turtle.right(90)

Instant Drawing

Sometimes turtle.speed(0) isn't fast enough! In order to speed up the turtle even further you can use the following technique to more or less render your images instantly:

  • Step 1: Turn off drawing to the screen completely.
  • Step 2: Draw your sphapes. All calculations regarding colors and shapes will be performed and the results will be stored in memory. This is much, much faster than drawing directly to the screen.
  • Step 3: Force the screen to render itself - this will draw everything in memory to the screen at once.

We can do this in turtle graphics by using the tracer and update functions. You can call tracer to turn off drawing to the screen - the function takes one argument (an integer) which represents how often to draw to the screen. Sending this function the number 0 will essentially turn off drawing to the screen completely.

Next, you can draw all of your shapes as usual. When finished you can call update to force the screen to draw itself. Here's a program that uses this technique:

# make the turtle graphics module available
import turtle

# also make the random module available
import random

# set up our graphical canvas
# width = 500, height = 500
turtle.setup(500, 500)

# hide the turtle
turtle.hideturtle()

# turn off drawing!  nothing will show up when we draw from this point forward
# the drawing will be stored in memory though
turtle.tracer(0)

# loop 100 times
for j in range(100):

    # pick up the pen and move to a random position on the screen
    turtle.penup()
    turtle.goto(random.randint(-250, 250), random.randint(-250, 250))
    turtle.pendown()

    # draw a square here
    for side in range(4):
        turtle.forward(100)
        turtle.right(90)

# let turtle graphics draw what is in memory to the screen
turtle.update()

Quiz

Now that you've completed this module, please visit our NYU Classes site and take the corresponding quiz for this module. These quizzes are worth 5% of your total grade and are a great way to test your Python skills! You may also use the following scratch space to test out any code you want.

Feedback

Tell us what you thought about this module (it's anonymous).
How helpful did you find this module on a scale of 1-5:
Very unhappy face
Unhappy face
Neutral face
Happy face
Very happy face
Which resource(s) in this module did you find the most helpful (check all that apply):

Copyright 2014-2018