Now that you have mastered if
statements, there is only one major topic left to learn. And so in this chapter we’re going to start to explore loops.
Loops are super important. Different types of loops, and how to properly use them, is the focus of the next few chapters.
But, before we look at loops, let’s take a few moments to revisit a special type of variable, the list. We briefly showed you lists already. Back in Chapter 3 you used this code:
choices=["Heads","Tails"]
As we explained then, variables usually store a single value. Lists are a special type of variable that can store lots of values (as well as 0 values).
The code above is from Chapter 3, and it creates a list named choices
that has two items in it: the string Heads
and the string Tails
.
So, how do you create a list in Python? Well, as you have already seen, you can simply create a variable to create a list. What makes it a list? If the values you store in the variable are enclosed with [
and ]
(square brackets), then you’ve created a list.
So this line of code will create an empty list:
animals=[]
animals
is a variable that is a list, but it is empty.
This line of code creates a list with five items in it:
animals=["ant","bat","cat","dog","eel"]
List items must all be enclosed within the square brackets, and they must be separated by commas.
Let’s give this a try. Create a file named List1.py
and type this code:
# Create a list animals=[] # How many items in it? print(len(animals), "animals in list")
Save and run the code. You’ll see the text 0 animals in list
displayed in the Terminal window.
What does the code do? animals=[]
creates an empty list. The print()
statement displays how many items are in the list. To do this, it uses the len()
function. len()
returns the length of whatever item is passed to it, like the number of items in a list. So len(animals)
returns the number of items in the list animals
. Because the list is empty, it returns 0
.
Update your code to look like this (you can use your own list of animals, it doesn’t have to be ours):
# Create a list animals=["ant","bear","cat","dog","elephant"] # How many items in it? print(len(animals), "animals in list")
Save and run this updated code. The output will tell you that the list contains five animals.
Lists are designed to store, well, lists, like our list of animals. For a list to be useful, you need to be able to access the items stored in it. To do this, you once again use square brackets, specifying the item number you want.
Let’s try that. Create file List2.py
. Here’s the code:
# Create a list animals = ["ant","bat","cat","dog","eel"] # Display a list item print(animals[1])
Save and run the code. What animal gets displayed? The position of an item in a list is called an index. In the print()
function, we specified the index 1
, which is bat
. Why? Because, as you know, Python always starts counting at 0
, so ant
is in position 0
, and bat
is in position 1
.
New Term
Index The position of an item (within a list, for example) is its index.
Try updating and running the code with different index values. Then try using an index that is too high (like 5
if you are using the same example). Python won’t like that, it will tell you that list index out of range
, which means the index you provided isn’t valid.
You’ve seen that you can refer to specific items in a list by their index. We used this to get an item from the list, but the same syntax can be used to update an item in the list.
Create a file named List3.py
. Here is the code:
# Create a list animals = ["ant","bat","cat","dog","eel"] # Display the list print(animals) # Update item 2 animals[2] = "cow" # Display the list print(animals)
Save and run the code. It will display a list of animals and then display the list again but with cat
replaced by cow
. This line of code:
animals[2] = "cow"
saves cow
into the animals
list in the third spot (once again, starting from 0
, so that 2
is the third item). It does not add an item but overwrites the existing value. Bye bye, cat
; hello, cow
!
All of the lists we’ve used so far were created and initialized with a set of values. But what if you need to add or remove values on-the-fly?
Let’s look at adding an item first. This is the code for List4.py
:
# Create a list animals = ["ant","bat","cat","dog","eel"] # How big is the list? print(len(animals), "animals in list") # Add an item animals.append("fox") # Now how big is the list? print(len(animals), "animals in list")
Save and run the code. It will report five animals in the list and then six animals in the list.
Why? The animals
list does indeed start with five animals, and so the len()
in the print()
statements returns 5
.
But then comes this line:
animals.append("fox")
The append()
function adds an item to the end of the list. So the next print()
statement says there are six animals as that is what len()
will return.
How do you remove an item from a list? There are two functions that can do that. If you know the exact index you want to remove, you can use the pop()
function, like this:
animals.pop(5)
If you want to remove an item by its value, you can do this:
animals.remove("fox")
What if you want to check if a value is in a list? There are a couple of ways to do this.
If you just wanted to know if the list contains a value and don’t care about exactly where in the list it is, then you can use a simple if
statement.
Create the file List5.py
with this code:
# Create a list animals = ["ant","bat","cat","dog","eel","fox"] # Is "goat" in the list? if "goat" in animals: # Yes it is print("Yes, goat is in the list.") else: # No it isn't print("Nope, no goat, sorry.")
Save and run the file. The text Nope, no goat, sorry.
will be displayed. Add "goat"
to the animals list (on line 2) and then run the code again, this time it’ll display Yes, goat is in the list.
You saw lots of if
statements in the previous chapters. Here the if
uses an in
clause, which, exactly as its name suggests, returns True
if the value on the left can be found anywhere in the list and False
otherwise.
If you want to know exactly where in the list an item is, you can use the index()
function. Update the code to look like this (the only change is on line 6, in the first print()
statement):
# Create a list animals = ["ant","bat","cat","dog","eel","fox"] # Is "goat" in the list? if "goat" in animals: # Yes it is print("Yes, goat is item", animals.index("goat")) else: # No it isn't print("Nope, no goat, sorry.")
Save and run the code. If there is no goat
in animals
, it’ll behave exactly as it did before. But if there is a goat
in animals
, the display will tell you where in the list it is because animals.index("goat")
returns the index of goat
.
Lists don’t have any specific order. They store and display items however they were added.
All of the lists we used in this chapter were alphabetically sorted. They didn’t have to be. We did that because it makes it easier for you to read and write the code.
But what if you actually do want the list in order? Imagine you have code like this:
# Create a list animals = ["iguana","dog","bat","eel","goat","ant","cat"]
This list is definitely not in alphabetical order. What if it needed to be?
Now, granted, this isn’t a great example because you could have just typed the animals in alphabetical order, right? True. But what if the list was not hardcoded and the user was typing animals, and then you needed to sort the list when they were done?
New Term
Hard Coding When values (numbers, text, dates, all sorts of stuff) are typed into the actual code, we say that they are hard coded. And, as a rule, hard coding anything is bad.
Create file List6.py
; here’s the code (feel free to add more animals than we’ve done here; the more, the better):
# Create a list animals = ["iguana","dog","bat","eel","goat","ant","cat"] # Display the list print(animals) # Sort the list animals.sort() # Display the list print(animals)
Save and run the code. You’ll see the complete list of animals displayed twice: first in the order in which they were put into the list and then alphabetically.
The magic in the code is this line:
animals.sort()
sort()
is a function that does just that, it sorts the list. By default, it sorts alphabetically, but you can also make it sort in reverse order and more if needed.
Fun stuff, huh? Ok, so what does this all have to do with loops?
Thus far, you have learned that Python executes code line by line. It starts at the top of a file, ignores comments, and processes each line in order. In Chapter 4 we introduced if
statements, which effectively allow lines of code to be included or excluded in the processing.
But what if you want to repeat a block of code over and over? Perhaps you are writing a game, and you need to allow movements over and over until an obstacle is reached. Or maybe your users can take selfies and send messages over and over until a chat session is closed. Or perhaps it’s something as simple as a calculator app that lets users enter numbers over and over until they hit the Calculate button.
All of these examples have one thing in common: They allow the same functionality to be used over and over until the process has completed.
Coding these actions requires the use of loops, and Python supports two types of loops:
You can loop through a defined set of options. This might be looping from 1 to 10, or looping through a set of uploaded images, or looping through the lines of a file you are reading. In this type of loop, the number of iterations (that means how many times the loop loops) is finite. You are looping through a set of options and the loop ends once the last option has been reached. We’ll focus on this type of loop in this chapter.
You can also loop until a condition changes. For example, allowing a user to move in a game until their character dies, the loop repeats so long as a condition (character is still alive) is True
, and ends when the condition is not (bye bye, character). Or allowing a user to take selfies until they hit Send, the loop allows the camera to be used and pictures to be taken so long as the condition (user has not clicked Send yet) is True
; as soon as they click Send, the condition becomes False
, and the loop ends. In this type of loop, the number of iterations is not known; the loop just keeps going and going and going until the condition changes. We’ll look at this type of loop in the next chapter.
Let’s start with the simplest of loops, looping through a list of items:
animals=["ant","bat","cat","dog","eel"]
You know what this is: It’s a list called animals
containing five items in it.
Earlier in this chapter, we saw how to access individual list items. But what if you want to loop through the list so as to print each one individually? Tada! Loops to the rescue!
Create a new file named Loop1.py
and type the following:
# List of animals animals=["ant","bat","cat","dog","eel"] # Loop through the list for animal in animals: # Display one per loop iteration print(animal)
Save and run this code. You should see output like this:
You know what the first line of code does, so let’s look at the loop code. This line:
for animal in animals:
tells Python to loop through animals
and, in each iteration, to put the next item in a variable named animal
. Note that there are now two variables used here: animal
and animals
. animals
is the list we created, but what is animal
? We didn’t explicitly create the animal
variable; the for
loop code did that for us, and the loop changes the value of animal
automatically on each iteration. We told the for
loop what to name the variable by specifying for animal
. (We could have named the variable anything, but animal
seemed like a good choice for a variable that holds one animal from a list of animals.)
New Term
Iteration Each cycle of a loop is called an iteration. You may also hear coders say the code iterates, which means it loops.
Just like if
statements, loops end with a colon (the :
character), and then whatever is indented beneath the loop is what gets called once per iteration.
So, in our example, how many times does the print()
statements get called? Five—because there are five items in the list animals
. Try adding an item or a few, and run the code again. The indented code will always be called once per item in the list.
And just like with if
statements, you need to be careful with indentation. Mess it up, and the loop won’t work as intended. For example, if the loop code looked like this:
# Loop through the list for animal in animals: # Display one per loop iteration print("Here is the next animal:") print(animal)
the output would look like this:
Why? The indented print()
statement will be called once per iteration, so five times. But the last print()
statement is not indented (coders will say that it is outside of the loop), so it isn’t processed until loop processing has completed. Therefore, that last print()
gets called only once, and the variable animal
then will contain the last value that was put in it (the final item in the list).
Next let’s look at looping through numbers. This is similar to the list loop we just saw, but instead of looping through a list we specify a set of numbers and loop through them.
Create the file Loop2.py
. Here is the code:
# Loop from 1 to 10 for i in range(1, 11): # Display i in each loop iteration print(i)
Save and run the code, and you’ll see numbers 1
through 10
displayed, one per line, in the Terminal window.
range()
specifies the range of numbers, and just like randrange()
in Chapter 3, the end number is not included, so range(1, 11)
means start at 1
and stop before you reach 11
.
for i
creates a variable named i
, and inside the loop, i
contains the next item in the range. On the first iteration i
will be 1
, then 2
, then 3
, and so on.
Try changing the range values and then run your code. Try that a few times.
Now let’s make things more interesting. At the end of Chapter 4 we showed you a nested if
statement, which is an if
statement inside of another if
statement. You can do the same with loops.
Let’s try an example that will bring back fond memories of your earliest years in school. Remember studying your multiplication tables? Fun, right? It might have taken you a while to memorize all the way to 12 x 12, but Python can do that for you in just three lines of code!
Create a file named Loop3.py
and type this code:
# Loop from 1 to 12 for i in range(1, 13): # Loop from 1 to 12 within each iteration of the outer loop for j in range(1, 13): # Print both loop values and multiply them print(i, "x", j, "=", i*j)
Save and run the code. You’ll see 144 lines of output fly by, starting with 1 x 1 = 1
all the way to 12 x 12 = 144
.
Ok, so how does this code work? We have two loops here, so to keep things clear, let’s call them outer loop and inner loop.
The outer loop uses range(1, 13)
, so everything indented beneath it gets called 12
times, and each time, variable i
will contain the current outer loop iteration number (first 1
, then 2
, and so on).
The inner loop also uses range(1, 13)
, and so everything indented beneath it also gets called 12
times, and each time, variable j
will contain the current inner loop iteration number.
So how many times does that print()
statement get called? The outer loop makes the inner loop execute 12 times. And each time the inner loop executes, it executes the print()
statement 12 times. So print()
gets executed 144 (12 times 12) times in total.
The print()
statement itself is pretty simple:
print(i, "x", j, "=", i*j)
The first time this runs, i
will be 1
, and j
will be 1
, so this is effectively:
print(1, "x", 1, "=", 1*1)
print()
does the math on-the-fly and will display 1 x 1 = 1
.
The next time around, i
will be 1
, and j
will be 2
, so the output will be 1 x 2 = 2
. This will continue until j
is 12
and the displayed text is 1 x 12 = 12
. Then the inner loop will have finished, and the outer loop will restart the inner loop a second time; this time, i
will be 2
. So on the next iteration, it will display 2 x 1 = 1
and so on, all the way until i
is 12
and j
is 12
and the output is 12 x 12 = 144
.
And all in just three lines of code. (Yes, we know there are six lines above, but three of them are comments. Python ignores them, so we can, too!)
Now that you know how to use loops, let’s create programs to encrypt and decrypt text. Yes, we’ll create two programs. The first will ask the user for some text and will display an encrypted version of that text. The other will ask the user for encrypted text and will decrypt it and display the original text. And so long as the same secret key (more on that in a moment) is used for encryption and decryption, all will work well.
So, if you receive this text:
Fphjsp#jw!hxrm%
you could decrypt it and it’ll say…um, nope, can’t tell you, sorry! You’ll find out soon….
Ok, a warning. The following code may look complicated. But don’t panic: It is using stuff you’ve already learned. Ready?
Replacing characters with their encrypted version requires doing a little math. Math, you say? Letters aren’t numbers, so how can you do math on them?
Well, as it so happens, inside of your computer letters are indeed numbers. Every single letter and character has an internal number. You don’t usually care about these; to us, letter A
is just A
, b
is just b
, and 3
is just 3
. But inside of your computer, the letter displayed as A
is character number 65
, b
is 98
, and the character for number 3
is 51
. Every single character has its own number; so a
and A
have different numbers because they are different characters.
Yep, this sounds odd, but just accept it for now. Every character has a number (called an ASCII Character Code) that can be used to refer to it.
Tip
Use Test Files Every coder has dozens of test files (usually named test42.py
, or something like that). Sometimes coders have whole test folders (yep, often named test
). Test files are great places to tinker and try stuff out.
In Python you can get the ASCII code for any character by using the ord()
function. Run this code. (You have test
Python files, right? They are perfect for this):
print(ord('A'))
and it will display 65, the ASCII code for A
. Change that A
to a B
, and it’ll display 66
. And so on.
As ord()
returns a number, you can do math with it. Not that this next example is particularly useful, but this code:
print(ord('A') + 1)
will display 66: ord('A')
returns 65
, and the program adds 1
and you get 66
.
So how do you turn this number back into a character? The opposite of the ord()
function is the chr()
function. This code:
print(chr(65))
will display the letter A
.
So, if you wanted to add 1
to A
to get B
, you could do this:
print(chr(ord('A')+1))
What does this do? It is easiest to read it from the inside working outward. ord('A')
returns 65
, as you saw previously. Add 1
and the total is 66
. That sum, 66
, gets passed to chr()
, which returns B
.
We can use this technique to encrypt your text. To encrypt the text HELLO
, we just need to know how much to add to (or subtract from) the ASCII value of each character. If we add 10
, then HELLO
becomes encrypted as ROVVY
(H
becomes R
, E
becomes O
, and so on). Subtract 10
from each letter in ROVVY
, and you get the decrypted HELLO
.
In the example we just saw, 10
is the magic number—the encryption key. It is what we use to change each letter.
But it’s not very safe to use a simple number like this for encryption. Users could try 1
and then 2
and then 3
and eventually guess your code. To make this safer, you’d want to use different keys.
For example, what if the key was 314159
? To encrypt HELLO
, we’d use 3
for the H
, 1
for the E
, 4
for the first L
, 1
for the second L
, and 5
for the O
. So HELLO
becomes KFPMT
. Guessing this is much harder because a different key is used for each letter.
But what if your text is longer than the key? Say you were encrypting the text Hello World
. The key has 6
digits in it, we need 11
digits (because the space value has an ASCII value, too). What to do?
The answer is reuse the key. If the key is 6 digits long, we use these 6 digits for the first 6 letters to be encrypted. And then we start over: For letter 7, we use the first digit in the key, for letter 8 the second, and keep reusing the key over and over as needed.
How do we figure out which digit to use? We use division and look at the remainder. For the 8th character (we need the second number from the key), we just divide the character index (8, as this is the 8th character) by the key length (6), and the remainder is 2. In Chapter 3 we introduced the modulus operator (%
), which finds a remainder. It is used like this:
print(8%6)
Here 8
is divided by 6
, and the remainder is 2
.
Using modulus, we can always divide the character position (8
for the 8th character, 42
for the 42nd, and so on) by the key length, and the remainder will point to a valid key digit to use.
Ok, here’s the code for file Encrypt.py
:
# ASCII range of usable characters - anything out of this range could throw errors asciiMin = 32 # Represents the space character - " " asciiMax = 126 # Represents the tilde character - "~" # Secret key key = 314159 # Top secret! This is the encryption key! key = str(key) # Convert to string so can access individual digits # Get input message message = input("Enter message to be encrypted: ") # Initialize variable for encrypted message messEncr = "" # Loop through message for index in range(0, len(message)): # Get the ASCII value for this character char = ord(message[index]) # Is this character out of range? if char < asciiMin or char > asciiMax: # Yes, not safe to encrypt, leave as is messEncr += message[index] else: # Safe to encrypt this character # Encrypt and shift the value as per the key ascNum = ord(message[index]) + int(key[index % len(key)]) # If shifted past range, cycle back to the beginning of the range if ascNum > asciiMax: ascNum -= (asciiMax - asciiMin) # Convert to a character and add to output messEncr = messEncr + chr(ascNum) # Display result print("Encrypted message:", messEncr)
Save and run this code. It will ask you for a message to encrypt and will then display the encrypted message. It doesn’t decrypt; we’ll get to that next.
So how does this work?
Not all ASCII characters print well, so to be safe, you define the range of characters you want to use, like this:
asciiMin = 32 # Represents the space character - " " asciiMax = 126 # Represents the tilde character - "~"
Next comes the key:
# Secret key key = 314159 # Top secret! This is the encryption key! key = str(key) # Convert to string so can access individual digits
key
is the numeric encryption key. This one has six digits, though yours can be longer or shorter. The key here (hee hee, bad pun, sorry) is that you must have the same key to encrypt and decrypt the text.
We need to use each digit in the key individually. Remember? That’s how we can encrypt each character with a different key digit. To do this, we convert the key to a string, so 314159
becomes "314159"
because getting characters from a string is super easy. It’s much like getting them from a list. Remember those?
Next, the code asks for the text to be encrypted using an input()
function. You’re very familiar with this one.
Then this code is used:
messEncr = ""
This creates an empty string variable named messEncr
(for message encrypted). The code is going to encrypt the text one character at a time, and as it does so, the encrypted character will be added to this variable.
Now we loop through message:
for index in range(0, len(message)):
Here we use a for loop that loops from 0
to the length of the text. How does the loop know how long the text is? Once again, we can use the len()
function for that. If the text is 10 characters long, len()
returns 10
, so the range for the loop will be range(0, 10)
, meaning loop from 0 to 9, exactly what we need. Within each iteration, the variable index
will contain the iteration number: 0
the first time, 1
the second, and so on.
The code indented under the for
statement will be executed once per character to be encrypted. At the start of each loop, we need to get the ASCII value for the letter being processed, like this:
char = ord(message[index])
message[index]
lets us access a single character. index
is 0
on the first loop, so on the first iteration, message[index]
will return the first character. On the next iteration, it will return the second. ord()
gets the ASCII code for the number, and that code is saved to the variable char
.
The next if
statement checks to see if the ASCII code for this character is within the safe range. If not, we don’t encode it. If yes, this code gets executed:
ascNum = char + int(key[index % len(key)])
This code does the actual encryption. index
is the current character number (set by the for
loop). index % len(key)
divides the index
by the length of the key, giving us a remainder, which is the key digit to use. That gets added to char
(the current ASCII code), and the result is saved in ascNum
. So if, for example, the loop is currently at index 9
, and the key has six digits, index % len(key)
will become 9 % 6
, which returns 3
(the remainder), and index 3
of the key will be used.
The encoded character then gets appended to messEncr
, like this:
messEncr = messEncr + chr(ascNum)
chr(ascNum)
converts the newly calculated encoded character to a string, and that gets appended to messEncr
. (Remember that adding strings concatenates them.)
As we mentioned previously, some ASCII characters don’t print, and so we need to make sure to exclude these. This code checks to ensure that the encoded character is within the safe range, and if it isn’t, it shifts to a safer value:
if ascNum > asciiMax: ascNum -= (asciiMax - asciiMin)
And finally, a print()
is used to display the encrypted text.
That does it. Enter text, and the program will encrypt it using the digits in the secret key. If you send someone a message, they’d need the matching key to read it. You can use different keys for different people (that way they won’t be able to read each other’s messages).
Great. But how do you decrypt the encrypted messages? The process is actually exactly the same. It’s so similar, in fact, that you can use the same Encrypt.py
file and just make a few changes.
Tip
Use Save As If you use the VS Code Save As option (in the File menu) to save Encrypt.py
as Decrypt.py
, you have two files that are identical. Try it!
Click on Decrypt.py
and we’ll make a few changes. First, change the input()
so the prompt is correct:
message = input("Enter message to be decrypted: ")
Next, find the line that does the actual encryption, which looks like this:
ascNum = char + int(key[index % len(key)])
Recall that when we encrypt, we add a key digit. To decrypt, all we need to do is subtract the same digit. So, change the code to this:
ascNum = char - int(key[index % len(key)])
The +
is changed to a -
.
Next, look at the if
statement right below the line you just edited. It checks to see that we haven’t gone above the allowed range and subtracts the change, if needed. We need to reverse that so it looks like this:
if ascNum < asciiMin: ascNum += (asciiMax - asciiMin)
In the if
statement, the >
gets changed to a <
, and in the assignment, the -=
becomes +=
. Now if the decoding process creates a number below the range, we can fix that.
Some of the comments will be wrong, best to fix those, too.
That’s it! Now you can encrypt and decrypt messages, so long as both parties have the same key. And all made possible by some simple for
loops.
Oh, using the key 314159
, were you able to decrypt the encrypted text we showed you earlier in this chapter?
In this chapter, you learned how to use for
loops to loop over a known set of items. In the next chapter, we’ll look at conditional loops (and how to use both loop types together).