“Arguing that you don’t care about the right to privacy because you have nothing to hide is no different than saying you don’t care about free speech because you have nothing to say.”
—Edward Snowden, 2015
The Caesar cipher isn’t secure; it doesn’t take much for a computer to brute-force through all 66 possible keys. The transposition cipher, on the other hand, is more difficult to brute-force because the number of possible keys depends on the message’s length. There are many different types of transposition ciphers, including the rail fence cipher, route cipher, Myszkowski transposition cipher, and disrupted transposition cipher. This chapter covers a simple transposition cipher called the columnar transposition cipher.
Instead of substituting characters with other characters, the transposition cipher rearranges the message’s symbols into an order that makes the original message unreadable. Because each key creates a different ordering, or permutation, of the characters, a cryptanalyst doesn’t know how to rearrange the ciphertext back into the original message.
The steps for encrypting with the transposition cipher are as follows:
Count the number of characters in the message and the key.
Draw a row of a number of boxes equal to the key (for example, 8 boxes for a key of 8).
Start filling in the boxes from left to right, entering one character per box.
When you run out of boxes but still have more characters, add another row of boxes.
When you reach the last character, shade in the unused boxes in the last row.
Starting from the top left and going down each column, write out the characters. When you get to the bottom of a column, move to the next column to the right. Skip any shaded boxes. This will be the ciphertext.
To see how these steps work in practice, we’ll encrypt a message by hand and then translate the process into a program.
Before we start writing code, let’s encrypt the message “Common sense is not so common.” using pencil and paper. Including the spaces and punctuation, this message has 30 characters. For this example, you’ll use the number 8 as the key. The range for possible keys for this cipher type is from 2 to half the message size, which is 15. But the longer the message, the more keys are possible. Encrypting an entire book using the columnar transposition cipher would allow for thousands of possible keys.
The first step is to draw eight boxes in a row to match the key number, as shown in Figure 7-1.
Figure 7-1: The number of boxes in the first row should match the key number.
The second step is to start writing the message you want to encrypt into the boxes, placing one character into each box, as shown in Figure 7-2. Remember that spaces are also characters (indicated here with ▪).
Figure 7-2: Fill in one character per box, including spaces.
You have only eight boxes, but there are 30 characters in the message. When you run out of boxes, draw another row of eight boxes under the first row. Continue creating new rows until you’ve written the entire message, as shown in Figure 7-3.
Figure 7-3: Add more rows until the entire message is filled in.
Shade in the two boxes in the last row as a reminder to ignore them. The ciphertext consists of the letters read from the top-left box going down the column. C, e, n, and o are from the 1st column, as labeled in the diagram. When you get to the last row of a column, move to the top row of the next column to the right. The next characters are o, n, o, m. Ignore the shaded boxes.
The ciphertext is “Cenoonommstmme oo snnio. s s c”, which is sufficiently scrambled to prevent someone from figuring out the original message by looking at it.
To make a program for encrypting, you need to translate these paper-and-pencil steps into Python code. Let’s look again at how to encrypt the string 'Common sense is not so common.' using the key 8. To Python, a character’s position inside a string is its numbered index, so add the index of each letter in the string to the boxes in your original encrypting diagram, as shown in Figure 7-4. (Remember that indexes begin with 0, not 1.)
Figure 7-4: Add the index number to each box, starting with 0.
These boxes show that the first column has the characters at indexes 0, 8, 16, and 24 (which are 'C', 'e', 'n', and 'o'). The next column has the characters at indexes 1, 9, 17, and 25 (which are 'o', 'n', 'o', and 'm'). Notice the pattern emerging: the nth column has all the characters in the string at indexes 0 + (n – 1), 8 + (n – 1), 16 + (n – 1), and 24 + (n – 1), as shown in Figure 7-5.
Figure 7-5: The index of each box follows a predictable pattern.
There is an exception for the last row in the 7th and 8th columns, because 24 + (7 – 1) and 24 + (8 – 1) would be greater than 29, which is the largest index in the string. In those cases, you only add 0, 8, and 16 to n (and skip 24).
What’s so special about the numbers 0, 8, 16, and 24? These are the numbers you get when, starting from 0, you add the key (which in this example is 8). So, 0 + 8 is 8, 8 + 8 is 16, 16 + 8 is 24. The result of 24 + 8 would be 32, but because 32 is larger than the length of the message, you’ll stop at 24.
For the nth column’s string, start at index (n – 1) and continue adding 8 (the key) to get the next index. Keep adding 8 as long as the index is less than 30 (the message length), at which point move to the next column.
If you imagine each column is a string, the result would be a list of eight strings, like this: 'Ceno', 'onom', 'mstm', 'me o', 'o sn', 'nio.', ' s ', 's c'. If you concatenated the strings together in order, the result would be the ciphertext: 'Cenoonommstmme oo snnio. s s c'. You’ll learn about a concept called lists later in the chapter that will let you do exactly this.
Open a new file editor window by selecting File▸New File. Enter the following code into the file editor and then save it as transpositionEncrypt.py. Remember to place the pyperclip.py module in the same directory as the transpositionEncrypt.py file. Then press F5 to run the program.
transposition
Encrypt.py
1. # Transposition Cipher Encryption
2. # https://www.nostarch.com/crackingcodes/ (BSD Licensed)
3.
4. import pyperclip
5.
6. def main():
7. myMessage = 'Common sense is not so common.'
8. myKey = 8
9.
10. ciphertext = encryptMessage(myKey, myMessage)
11.
12. # Print the encrypted string in ciphertext to the screen, with
13. # a | ("pipe" character) after it in case there are spaces at
14. # the end of the encrypted message:
15. print(ciphertext + '|')
16.
17. # Copy the encrypted string in ciphertext to the clipboard:
18. pyperclip.copy(ciphertext)
19.
20.
21. def encryptMessage(key, message):
22. # Each string in ciphertext represents a column in the grid:
23. ciphertext = [''] * key
24.
25. # Loop through each column in ciphertext:
26. for column in range(key):
27. currentIndex = column
28.
29. # Keep looping until currentIndex goes past the message length:
30. while currentIndex < len(message):
31. # Place the character at currentIndex in message at the
32. # end of the current column in the ciphertext list:
33. ciphertext[column] += message[currentIndex]
34.
35. # Move currentIndex over:
36. currentIndex += key
37.
38. # Convert the ciphertext list into a single string value and return it:
39. return ''.join(ciphertext)
40.
41.
42. # If transpositionEncrypt.py is run (instead of imported as a module) call
43. # the main() function:
44. if __name__ == '__main__':
45. main()
When you run the transpositionEncrypt.py program, it produces this output:
Cenoonommstmme oo snnio. s s c|
The vertical pipe character (|) marks the end of the ciphertext in case there are spaces at the end. This ciphertext (without the pipe character at the end) is also copied to the clipboard, so you can paste it into an email to someone. If you want to encrypt a different message or use a different key, change the value assigned to the myMessage and myKey variables on lines 7 and 8. Then run the program again.
After importing the pyperclip module, you’ll use a def statement to create a custom function, main(), on line 6.
1. # Transposition Cipher Encryption
2. # https://www.nostarch.com/crackingcodes/ (BSD Licensed)
3.
4. import pyperclip
6. def main():
7. myMessage = 'Common sense is not so common.'
8. myKey = 8
The def statement means you’re creating, or defining, a new function that you can call later in the program. The block of code after the def statement is the code that will run when the function is called. When you call this function, the execution moves inside the block of code following the function’s def statement.
As you learned in Chapter 3, in some cases, functions will accept arguments, which are values that the function can use with its code. For example, print() can take a string value as an argument between its parentheses. When you define a function that takes arguments, you put a variable name between its parentheses in its def statement. These variables are called parameters. The main() function defined here has no parameters, so it takes no arguments when it’s called. If you try to call a function with too many or too few arguments for the number of parameters the function has, Python will raise an error message.
Let’s create a function with a parameter and then call it with an argument. Open a new file editor window and enter the following code into it:
hello
Function.py
➊ def hello(name):
➋ print('Hello, ' + name)
➌ print('Start.')
➍ hello('Alice')
➎ print('Call the function again:')
➏ hello('Bob')
➐ print('Done.')
Save this program as helloFunction.py and run it by pressing F5. The output looks like this:
Start.
Hello, Alice
Call the function again:
Hello, Bob
Done.
When the helloFunction.py program runs, the execution starts at the top. The def statement ➊ defines the hello() function with one parameter, which is the variable name. The execution skips the block after the def statement ➋ because the block is only run when the function is called. Next, it executes print('Start.') ➌, which is why 'Start.' is the first string printed when you run the program.
The next line after print('Start.') is the first function call to hello(). The program execution jumps to the first line in the hello() function’s block ➋. The string 'Alice' is passed as the argument and is assigned to the parameter name. This function call prints the string 'Hello, Alice' to the screen.
When the program execution reaches the bottom of the def statement’s block, the execution jumps back to the line with the function call ➍ and continues executing the code from there, so 'Call the function again:' is printed ➎.
Next is a second call to hello() ➏. The program execution jumps back to the hello() function’s definition ➋ and executes the code there again, displaying 'Hello, Bob' on the screen. Then the function returns and the execution goes to the next line, which is the print('Done.') statement ➐, and executes it. This is the last line in the program, so the program exits.
Enter the following code into the interactive shell. This code defines and then calls a function named func(). Note that the interactive shell requires you to enter a blank line after param = 42 to close the def statement’s block:
>>> def func(param):
param = 42
>>> spam = 'Hello'
>>> func(spam)
>>> print(spam)
Hello
The func() function takes a parameter called param and sets its value to 42. The code outside the function creates a spam variable and sets it to a string value, and then the function is called on spam and spam is printed.
When you run this program, the print() call on the last line will print 'Hello', not 42. When func() is called with spam as the argument, only the value inside spam is being copied and assigned to param. Any changes made to param inside the function will not change the value in the spam variable. (There is an exception to this rule when you are passing a list or dictionary value, but this is explained in “List Variables Use References” on page 119.)
Every time a function is called, a local scope is created. Variables created during a function call exist in this local scope and are called local variables. Parameters always exist in a local scope (they are created and assigned a value when the function is called). Think of a scope as a container the variables exist inside. When the function returns, the local scope is destroyed, and the local variables that were contained in the scope are forgotten.
Variables created outside of every function exist in the global scope and are called global variables. When the program exits, the global scope is destroyed, and all the variables in the program are forgotten. (All the variables in the reverse cipher and Caesar cipher programs in Chapters 5 and 6, respectively, were global.)
A variable must be local or global; it cannot be both. Two different variables can have the same name as long as they’re in different scopes. They’re still considered two different variables, similar to how Main Street in San Francisco is a different street from Main Street in Birmingham.
The important idea to understand is that the argument value that is “passed” into a function call is copied to the parameter. So even if the parameter is changed, the variable that provided the argument value is not changed.
In lines 6 through 8 in transpositionEncrypt.py, you can see that we’ve defined a main() function that will set values for the variables myMessage and myKey when called:
6. def main():
7. myMessage = 'Common sense is not so common.'
8. myKey = 8
The rest of the programs in this book will also have a function named main() that is called at the start of each program. The reason we have a main() function is explained at the end of this chapter, but for now just know that main() is always called soon after the programs in this book are run.
Lines 7 and 8 are the first two lines in the block of code defining main(). In these lines, the variables myMessage and myKey store the plaintext message to encrypt and the key used to do the encryption. Line 9 is a blank line but is still part of the block and separates lines 7 and 8 from line 10 to make the code more readable. Line 10 assigns the variable ciphertext as the encrypted message by calling a function that takes two arguments:
10. ciphertext = encryptMessage(myKey, myMessage)
The code that does the actual encrypting is in the encryptMessage() function defined later on line 21. This function takes two arguments: an integer value for the key and a string value for the message to encrypt. In this case, we pass the variables myMessage and myKey, which we just defined in lines 7 and 8. When passing multiple arguments to a function call, separate the arguments with a comma.
The return value of encryptMessage() is a string value of the encrypted ciphertext. This string is stored in ciphertext.
The ciphertext message is printed to the screen on line 15 and copied to the clipboard on line 18:
12. # Print the encrypted string in ciphertext to the screen, with
13. # a | ("pipe" character) after it in case there are spaces at
14. # the end of the encrypted message:
15. print(ciphertext + '|')
16.
17. # Copy the encrypted string in ciphertext to the clipboard:
18. pyperclip.copy(ciphertext)
The program prints a pipe character (|) at the end of the message so the user can see any empty space characters at the end of the ciphertext.
Line 18 is the last line of the main() function. After it executes, the program execution returns to the line after the line that called it.
The key and message variables between the parentheses on line 21 are parameters:
21. def encryptMessage(key, message):
When the encryptMessage() function is called in line 10, two argument values are passed (the values in myKey and myMessage). These values get assigned to the parameters key and message when the execution moves to the top of the function.
You might wonder why you even have the key and message parameters, since you already have the variables myKey and myMessage in the main() function. We need different variables because myKey and myMessage are in the main() function’s local scope and can’t be used outside of main().
Line 23 in the transpositionEncrypt.py program uses a data type called a list:
22. # Each string in ciphertext represents a column in the grid:
23. ciphertext = [''] * key
Before we can move on, you need to understand how lists work and what you can do with them. A list value can contain other values. Similar to how strings begin and end with quotes, a list value begins with an open bracket, [, and ends with a closed bracket, ]. The values stored inside the list are between the brackets. If more than one value is in the list, the values are separated by commas.
To see a list in action, enter the following into the interactive shell:
>>> animals = ['aardvark', 'anteater', 'antelope', 'albert']
>>> animals
['aardvark', 'anteater', 'antelope', 'albert']
The animals variable stores a list value, and in this list value are four string values. The individual values inside a list are also called items or elements. Lists are ideal to use when you have to store multiple values in one variable.
Many of the operations you can do with strings also work with lists. For example, indexing and slicing work on list values the same way they work on string values. Instead of individual characters in a string, the index refers to an item in a list. Enter the following into the interactive shell:
>>> animals = ['aardvark', 'anteater', 'albert']
➊ >>> animals[0]
'aardvark'
>>> animals[1]
'anteater'
>>> animals[2]
'albert'
➋ >>> animals[1:3]
['anteater', 'albert']
Keep in mind that the first index is 0, not 1 ➊. Similar to how using slices with a string gives you a new string that is part of the original string, using slices with a list gives you a list that is part of the original list. And remember that if a slice has a second index, the slice only goes up to but doesn’t include the item at the second index ➋.
A for loop can also iterate over the values in a list, just like it can iterate over the characters in a string. The value stored in the for loop’s variable is a single value from the list. Enter the following into the interactive shell:
>>> for spam in ['aardvark', 'anteater', 'albert']:
... print('For dinner we are cooking ' + spam)
...
For dinner we are cooking aardvark
For dinner we are cooking anteater
For dinner we are cooking albert
Each time the loop iterates, the spam variable is assigned a new value from the list starting with the list’s 0 index until the end of the list.
You can also modify the items inside a list by using the list’s index with a normal assignment statement. Enter the following into the interactive shell:
>>> animals = ['aardvark', 'anteater', 'albert']
➊ >>> animals[2] = 9999
>>> animals
➋ ['aardvark', 'anteater', 9999]
To modify the third member of the animals list, we use the index to get the third value with animals[2] and then use an assignment statement to change its value from 'albert' to the value 9999 ➊. When we check the contents of the list again, 'albert' is no longer contained in the list ➋.
List values can even contain other lists. Enter the following into the interactive shell:
>>> spam = [['dog', 'cat'], [1, 2, 3]]
>>> spam[0]
['dog', 'cat']
>>> spam[0][0]
'dog'
>>> spam[0][1]
'cat'
>>> spam[1][0]
1
>>> spam[1][1]
2
The value of spam[0] evaluates to the list ['dog', 'cat'], which has its own indexes. The double index brackets used for spam[0][0] indicates that we’re taking the first item from the first list: spam[0] evaluates to ['dog', 'cat'] and ['dog', 'cat'][0] evaluates to 'dog'.
You’ve used len() to indicate the number of characters in a string (that is, the length of the string). The len() function also works on list values and returns an integer of the number of items in a list.
Enter the following into the interactive shell:
>>> animals = ['aardvark', 'anteater', 'antelope', 'albert']
>>> len(animals)
4
Similarly, you’ve used the in and not in operators to indicate whether a string exists inside another string value. The in operator also works for checking whether a value exists in a list, and the not in operator checks whether a value does not exist in a list. Enter the following into the interactive shell:
>>> animals = ['aardvark', 'anteater', 'antelope', 'albert']
>>> 'anteater' in animals
True
>>> 'anteater' not in animals
False
➊ >>> 'anteat' in animals
False
➋ >>> 'anteat' in animals[1]
True
>>> 'delicious spam' in animals
False
Why does the expression at ➊ return False while the expression at ➋ returns True? Remember that animals is a list value, while animals[1] evaluates to the string value 'anteater'. The expression at ➊ evaluates to False because the string 'anteat' does not exist in the animals list. However, the expression at ➋ evaluates to True because animals[1] is the string 'anteater' and 'anteat' exists within that string.
Similar to how a set of empty quotes represents a blank string value, a set of empty brackets represents a blank list. Enter the following into the interactive shell:
>>> animals = []
>>> len(animals)
0
The animals list is empty, so its length is 0.
You know that the + and * operators can concatenate and replicate strings; the same operators can also concatenate and replicate lists. Enter the following into the interactive shell.
>>> ['hello'] + ['world']
['hello', 'world']
>>> ['hello'] * 5
['hello', 'hello', 'hello', 'hello', 'hello']
That’s enough about the similarities between strings and lists. Just remember that most operations you can do with string values also work with list values.
We’ll use lists in our encryption algorithm to create our ciphertext. Let’s return to the code in the transpositionEncrypt.py program. In line 23, which we saw earlier, the ciphertext variable is a list of empty string values:
22. # Each string in ciphertext represents a column in the grid:
23. ciphertext = [''] * key
Each string in the ciphertext variable represents a column of the transposition cipher’s grid. Because the number of columns is equal to the key, you can use list replication to multiply a list with one blank string value in it by the value in key. This is how line 23 evaluates to a list with the correct number of blank strings. The string values will be assigned all the characters that go into one column of the grid. The result will be a list of string values that represent each column, as discussed earlier in the chapter. Because list indexes start with 0, you’ll need to also label each column starting at 0. So ciphertext[0] is the leftmost column, ciphertext[1] is the column to the right of that, and so on.
To see how this would work, let’s again look at the grid from the “Common sense is not so common.” example from earlier in this chapter (with column numbers corresponding to the list indexes added to the top), as shown in Figure 7-6.
Figure 7-6: The example message grid with list indexes for each column
If we manually assigned the string values to the ciphertext variable for this grid, it would look like this:
>>> ciphertext = ['Ceno', 'onom', 'mstm', 'me o', 'o sn', 'nio.', ' s ', 's c']
>>> ciphertext[0]
'Ceno'
The next step adds text to each string in ciphertext, as we just did in the manual example, except this time we added some code to make the computer do it programmatically:
25. # Loop through each column in ciphertext:
26. for column in range(key):
27. currentIndex = column
The for loop on line 26 iterates once for each column, and the column variable has the integer value to use for the index to ciphertext. On the first iteration through the for loop, the column variable is set to 0; on the second iteration, it’s set to 1; then 2; and so on. We have the index for the string values in ciphertext that we want to access later using the expression ciphertext[column].
Meanwhile, the currentIndex variable holds the index for the message string the program looks at on each iteration of the for loop. On each iteration through the loop, line 27 sets currentIndex to the same value as column. Next, we’ll create the ciphertext by concatenating the scrambled message together one character at a time.
So far, when we’ve concatenated or added values to each other, we’ve used the + operator to add the new value to the variable. Often, when you’re assigning a new value to a variable, you want it to be based on the variable’s current value, so you use the variable as the part of the expression that is evaluated and assigned to the variable, as in this example in the interactive shell:
>>> spam = 40
>>> spam = spam + 2
>>> print(spam)
42
There are other ways to manipulate values in variables based on the variable’s current value. For example, you can do this by using augmented assignment operators. The statement spam += 2, which uses the += augmented assignment operator, does the same thing as spam = spam + 2. It’s just a little shorter to type. The += operator works with integers to do addition, strings to do string concatenation, and lists to do list concatenation. Table 7-1 shows the augmented assignment operators and their equivalent assignment statements.
Table 7-1: Augmented Assignment Operators
Augmented assignment |
Equivalent normal assignment |
spam += 42 |
spam = spam + 42 |
spam -= 42 |
spam = spam - 42 |
spam *= 42 |
spam = spam * 42 |
spam /= 42 |
spam = spam / 42 |
We’ll use augmented assignment operators to concatenate characters to our ciphertext.
The currentIndex variable holds the index of the next character in the message string that will be concatenated to the ciphertext lists. The key is added to currentIndex on each iteration of line 30’s while loop to point to different characters in message and, at each iteration of line 26’s for loop, currentIndex is set to the value in the column variable.
To scramble the string in the message variable, we need to take the first character of message, which is 'C', and put it into the first string of ciphertext. Then, we would skip eight characters into message (because key is equal to 8) and concatenate that character, which is 'e', to the first string of the ciphertext. We would continue to skip characters according to the key and concatenate each character until we reach the end of the message. Doing so would create the string 'Ceno', which is the first column of the ciphertext. Then we would do this again but start at the second character in message to make the second column.
Inside the for loop that starts on line 26 is a while loop that starts on line 30. This while loop finds and concatenates the right character in message to make each column. It loops while currentIndex is less than the length of message:
29. # Keep looping until currentIndex goes past the message length:
30. while currentIndex < len(message):
31. # Place the character at currentIndex in message at the
32. # end of the current column in the ciphertext list:
33. ciphertext[column] += message[currentIndex]
34.
35. # Move currentIndex over:
36. currentIndex += key
For each column, the while loop iterates through the original message variable and picks out characters in intervals of key by adding key to currentIndex. On line 27 for the first iteration of the for loop, currentIndex was set to the value of column, which starts at 0.
As you can see in Figure 7-7, message[currentIndex] is the first character of message on its first iteration. The character at message[currentIndex] is concatenated to ciphertext[column] to start the first column at line 33. Line 36 adds the value in key (which is 8) to currentIndex each time through the loop. The first time it is message[0], the second time message[8], the third time message[16], and the fourth time message[24].
Figure 7-7: Arrows pointing to what refers to during the first iteration of the for loop when is set to
Although the value in currentIndex is less than the length of the message string, you want to continue concatenating the character at message[currentIndex] to the end of the string at the column index in ciphertext. When currentIndex is greater than the length of message, the execution exits the while loop and goes back to the for loop. Because there isn’t code in the for block after the while loop, the for loop iterates, column is set to 1, and currentIndex starts at the same value as column.
Now when line 36 adds 8 to currentIndex on each iteration of line 30’s while loop, the indexes will be 1, 9, 17, and 25, as shown in Figure 7-8.
Figure 7-8: Arrows pointing to what refers to during the second iteration of the loop when is set to
As message[1], message[9], message[17], and message[25] are concatenated to the end of ciphertext[1], they form the string 'onom'. This is the second column of the grid.
When the for loop has finished looping through the rest of the columns, the value in ciphertext is ['Ceno', 'onom', 'mstm', 'me o', 'o sn', 'nio.', ' s ', 's c']. After we have the list of string columns, we need to join them together to make one string that is the whole ciphertext: 'Cenoonommstmme oo snnio. s s c'.
The join() method is used on line 39 to join the individual column strings of ciphertext into one string. The join() method is called on a string value and takes a list of strings. It returns one string that has all of the members in the list joined by the string that join() is called on. (This is a blank string if you just want to join the strings together.) Enter the following into the interactive shell:
>>> eggs = ['dogs', 'cats', 'moose']
➊ >>> ''.join(eggs)
'dogscatsmoose'
➋ >>> ', '.join(eggs)
'dogs, cats, moose'
➌ >>> 'XYZ'.join(eggs)
'dogsXYZcatsXYZmoose'
When you call join() on an empty string and join the list eggs ➊, you get the list’s strings concatenated with no string between them. In some cases, you might want to separate each member in a list to make it more readable, which we’ve done at ➋ by calling join() on the string ', '. This inserts the string ', ' between each member of the list. You can insert any string you want between list members, as you can see at ➌.
A function (or method) call always evaluates to a value. This is the value returned by the function or method call, also called the return value of the function. When you create your own functions using a def statement, a return statement tells Python what the return value for the function is. Line 39 is a return statement:
38. # Convert the ciphertext list into a single string value and return it:
39. return ''.join(ciphertext)
Line 39 calls join() on the blank string and passes ciphertext as the argument so the strings in the ciphertext list are joined into a single string.
A return statement is the return keyword followed by the value to be returned. You can use an expression instead of a value, as in line 39. When you do so, the return value is whatever that expression evaluates to. Open a new file editor window, enter the following program, save it as addNumbers.py, and then press F5 to run it:
addNumbers.py
1. def addNumbers(a, b):
2. return a + b
3.
4. print(addNumbers(2, 40))
When you run the addNumbers.py program, this is the output:
42
That’s because the function call addNumbers(2, 40) at line 4 evaluates to 42. The return statement in addNumbers() at line 2 evaluates the expression a + b and then returns the evaluated value.
In the transpositionEncrypt.py program, the encryptMessage() function’s return statement returns a string value that is created by joining all of the strings in the ciphertext list. If the list in ciphertext is ['Ceno', 'onom', 'mstm', 'me o', 'o sn', 'nio.', ' s ', 's c'], the join() method call will return 'Cenoonommstmme oo snnio. s s c'. This final string, the result of the encryption code, is returned by our encryptMessage() function.
The great advantage of using functions is that a programmer has to know what the function does but doesn’t need to know how the function’s code works. A programmer can understand that when they call the encryptMessage() function and pass it an integer as well as a string for the key and message parameters, the function call evaluates to an encrypted string. They don’t need to know anything about how the code in encryptMessage() actually does this, which is similar to how you know that when you pass a string to print(), it will print the string even though you’ve never seen the print() function’s code.
You can turn the transposition encryption program into a module using a special trick involving the main() function and a variable named __name__.
When you run a Python program, __name__ (that’s two underscores before name and two underscores after) is assigned the string value '__main__' (again, two underscores before and after main) even before the first line of your program runs. The double underscore is often referred to as dunder in Python, and __main__ is called dunder main dunder.
At the end of the script file (and, more important, after all of the def statements), you want to have some code that checks whether the __name__ variable has the '__main__' string assigned to it. If so, you want to call the main() function.
The if statement on line 44 is actually one of the first lines of code executed when you run the program (after the import statement on line 4 and the def statements on lines 6 and 21).
42. # If transpositionEncrypt.py is run (instead of imported as a module) call
43. # the main() function:
44. if __name__ == '__main__':
45. main()
The reason the code is set up this way is that although Python sets __name__ to '__main__' when the program is run, it sets it to the string 'transpositionEncrypt' if the program is imported by another Python program. Similar to how the program imports the pyperclip module to call the functions in it, other programs might want to import transpositionEncrypt.py to call its encryptMessage() function without the main() function running. When an import statement is executed, Python looks for the module’s file by adding .py to the end of the filename (which is why import pyperclip imports the pyperclip.py file). This is how our program knows whether it’s being run as the main program or imported by a different program as a module. (You’ll import transpositionEncrypt.py as a module in Chapter 9.)
When you import a Python program and before the program is executed, the __name__ variable is set to the filename part before .py. When the transpositionEncrypt.py program is imported, all the def statements are run (to define the encryptMessage() function that the importing program wants to use), but the main() function isn’t called, so the encryption code for 'Common sense is not so common.' with key 8 isn’t executed.
That’s why the code that encrypts the myMessage string with the myKey key is inside a function (which by convention is named main()). This code inside main() won’t run when transpositionEncrypt.py is imported by other programs, but these other programs can still call its encryptMessage() function. This is how the function’s code can be reused by other programs.
NOTE
One useful way of learning how a program works is by following its execution step-by-step as it runs. You can use an online program-tracing tool to view traces of the Hello Function and Transposition Cipher Encryption programs at https://www.nostarch.com/crackingcodes/. The tracing tool will give you a visual representation of what the programs are doing as each line of code is executed.
Whew! You learned several new programming concepts in this chapter. The transposition cipher program is more complicated (but much more secure) than the Caesar cipher program in Chapter 6. The new concepts, functions, data types, and operators you’ve learned in this chapter let you manipulate data in more sophisticated ways. Just remember that much of what goes into understanding a line of code is evaluating it step-by-step in the same way Python will.
You can organize code into groups called functions, which you create with def statements. Argument values can be passed to functions as the function’s parameters. Parameters are local variables. Variables outside of all functions are global variables. Local variables are different from global variables, even if they have the same name as the global variable. Local variables in one function are also separate from local variables in another function, even if they have the same name.
List values can store multiple other values, including other list values. Many of the operations you can use on strings (such as indexing, slicing, and using the len() function) can be used on lists. And augmented assignment operators provide a nice shortcut to regular assignment operators. The join() method can join a list that contains multiple strings to return a single string.
It might be best to review this chapter if you’re not yet comfortable with these programming concepts. In Chapter 8, you’ll learn how to decrypt using the transposition cipher.