As you may have noticed throughout the work we've been doing so far, a lot of enciphering and deciphering requires repetition of the same operations. We take a letter (an input), perform a series of operations with the input, and calculate the output, another letter. This input/output relationship is often called a function in mathematics. In Python, we can write our own functions that we can call by name to help us from having to rewrite the same blocks of code over and over in our programs.

You've been using functions all throughout this course that are default functions in Python. The print() function is a good example. It works the same way every time and you don't need to know how it was programmed, just how to use it. We'll be writing our own functions for the same reason in this course.

Why Use Functions?

  1. Functions are a convenient way to divide your code into useful chunks that do one thing very well. This becomes especially useful when you need to repeat whatever task your function accomplishes many times.

  2. The variables that are created inside a function exist and can only be used from inside that function. They are called local variables, as opposed to global variables which can be accessed by any part of your program. Once the function is done performing its task, it returns specified information back to your main program and deletes all the local variables created while running. Not having to know about these variables is convenient, much like how we don't need to know what's going on inside the print() function.

  3. Because of points 1 and 2, functions are portable. You can copy/paste functions from one notebook to another, and they'll work exactly the same way for a given set of inputs. It ensures you don't need to worry about having variables named the same way as someone else, or the same way you named them in a different project.

Defining a Function

A Python function, much like a mathematical function, requires a name for the function, a name for the input (or inputs), a definition about what to do with the input, and an output.

For example, the mathematical function:

$$ f(x,y) = x + y $$

has the name $f$, inputs $x, y$, a definition that explains how to use $x$ and $y$, and the output $x + y$.

We can use Python to accomplish the same result:

def f(x, y):
    sum = x + y
    return sum

print( f(2,3) )

Notice some key features of how we defined the function:

  • use the def command to indicate you're defining a function
  • name the function. You can use a word, you're not limited to a single character
  • open parentheses ( and then list the name or names of the variables you'll provide for the function
  • close parentheses ) and then type a colon (:)
  • indent any code that is specific to the function
  • when the function's task is complete, indicate what you want the function to return as the output using the return command
    • As soon as a function reaches a return command, it will return whatever is specified to the main program and exit the function, even if the function was in the middle of a loop or other operation
    • A function can have more than one return statement. This could be useful when combined with an if/elif/else logic statement if you want the function to return different values depending on certain conditions.

This function does not print the output by itself, instead it returns an value that we then pass into the print() function. This is a nice feature because we may want to use the function sometimes without printing the output. For example:

print( f(1,2) + f(3,4) )

If the function f printed the sum as part of it's task, the output of the previous command would have been:


Because each time function f was used it would have printed to the screen. It's usually best for functions to return the needed information to the main program, and let the main program decide what to do with it. This retains flexibility in how functions can be used.

textClean() Function

Here's an example of a function textClean() that takes in a string and outputs the same string without punctuation or spaces.

def textClean( text ):
    # input: string: `text`
    # output: string
    text = text.upper()
    cleaned = ''
    for char in text:
        if char in LETTERS:
            cleaned += char
    return cleaned
print( textClean('T3st M3ssage!') )
print( textClean('No*Numbers'))

Once the textClean() function was defined, we were able to clean the text of any string we passed into the function without having rewrite the code. We also didn't have to know what the variable names were inside the function. It was entirely self-contained, all we had to do was give it the string. It's generally good practice to use comments in your code to indicate what the input and output data types will be so someone reading through your code knows how to use the function. When using a Jupyter Notebook, you could also document the usage in a MarkDown cell.

caesarEncipher() Function

Now that we have a function that cleans text, we can use that as a part of other functions. For example, if we were to write the Caesar Encipher as a function, we can use the textClean() function to clean whatever string we pass into the caesarEncipher() function we write.

def caesarEncipher(text, key):
    # inputs: string: text, int: key
    # output: string: ciphertext
    plaintext = textClean(text)
    ciphertext = ''
    for char in plaintext:
        ciphertext += LETTERS[ (LETTERS.find(char) + key) % 26 ]
    return ciphertext
print( caesarEncipher('test message.', 5) )
print( caesarEncipher('This is WAY easier than doing this by hand!', 22) )

Remember, the function caesarEncipher() does not have access to any of the variables that textClean() does. That's why plaintext was defined using the output of textClean(text) instead of directly trying to access the cleaned variable found inside the textClean() function.

Exercise for the Reader

Turn your previous code for enciphering and deciphering the Caesar, Multiplicative, and Affine ciphers into functions: caesarDecipher(), multiplicativeEncipher(), multiplicativeDecipher(), affineEncipher(), and affineDecipher(). Think about what the input and outputs of those functions should be.