In the last chapter we saw how we can take sections of code and make them reusable in different contexts without having to copy and paste the code. By using functions we give the code block a name and define what it needs in order to do its job.
Functions are a step in the right direction but they still have the problem that to use functions from one script in another you'll have to copy and paste them over. The answer to this is to move the functions to a common location which both scripts can access. The Python solution for this is modules. Just like we used modules from the Python standard library earlier, we can create our own modules too.
Continuing with the example of the ounces_to_grams
function from the last chapter, let's see how we can use the function in other code, outside of that one script.
To demonstrate this, let's look at the code in our convert.py
file and think about the different role that parts of it are playing.
def ounces_to_grams(weight):
new_weight = weight * 28.3495
return new_weight
weight_in_grams = ounces_to_grams(12)
print(f"{weight_in_grams} g")
There are two main categories of code in this file:
ounces_to_grams
is the sort of code which is useful in many different situations. It would be strange if it were only ever used to convert $12$ ounces into grams, but rather it could be used in a calculator, a recipe website, an engineering tool etc.ounces_to_grams
function.Let's continue this though and make it explicit by moving the code for today's problem into its own file, recipe.py
and delete that same code from convert.py
:
def ounces_to_grams(weight):
new_weight = weight * 28.3495
return new_weight
weight_in_grams = ounces_to_grams(12)
print(f"{weight_in_grams} g")
If we run this new script now, you'll get an error that it doesn't know what ounces_to_grams
is:
python recipe.py
This is because that function is indeed not defined in this script. We need to tell it to load the file which contains the function we want to use, using the import
statement:
import convert
weight_in_grams = convert.ounces_to_grams(12)
print(f"{weight_in_grams} g")
python recipe.py
Here we have done a few things. First, on the first line we have imported our module. This is Python's way of getting access to code which is inside other modules. You write the keyword import
followed by a space and then the name of the module you want to import. You'll notice that the way we do this is identical to when we were importing math
etc. earlier.
The name of a module is the same as the name of the file but without the .py
extension. So, since we saved our file above as convert.py
, the name of the module is convert
.
A module in Python is just a text file with Python code in it. All scripts can be imported as modules but it makes sense to logically separate in your mind between "library" modules and "script" modules. Library modules are those which you import to get access to the code inside them. Script modules are those which you run at the terminal using python script.py
.
Secondly, when calling the fucntion, we need to explicily state that we're using the ounces_to_grams
function from the convert
module using convert.ounces_to_grams
.
morse
(so the file name must be morse.py
), and move the encode
function from encode.py
from the previous chapter into it. Delete it from the original file.letter_to_morse
into morse.py
.encode.py
so that it imports the new module and calls the function from the new module.Add into morse.py
a function to convert Morse code to English. The code to do so is written below:
# We need to invert the dictionary. This will create a dictionary
# that can go from the morse back to the letter
morse_to_letter = {}
for letter in letter_to_morse:
morse = letter_to_morse[letter]
morse_to_letter[morse] = letter
def decode(message):
english = []
# Now we cannot read by letter. We know that morse letters are
# separated by a space, so we split the morse string by spaces
morse_letters = message.split(" ")
for letter in morse_letters:
english_letter = morse_to_letter[letter]
english.append(english_letter)
# Rejoin, but now we don't need to add any spaces
english_message = "".join(english)
return english_message
Then write a new script decode.py
which imports morse
, calls the decode
function and decodes the message "... . -.-. .-. . - / -- . ... ... .- --. ."
.
So far we've been writing code and running it, but have you actually checked that the code is doing the right thing? Testing code is a very important part of the software development process because if you want other people to trust your work, you need to show that you've checked that your code does what you claim.
There are more formal methods for software testing but we'll start with a common technique used with encoders/decoders and that is the round-trip. If we take a message, convert it to morse code and then convert it back, we should end up with the message we started with.
Make a new file called test_morse.py
:
import morse
message = "sos we have hit an iceberg"
code = morse.encode(message)
decode = morse.decode(code)
print(message == decode)
When you run it, you should see True
printed to the console terminal tells us that the round-trip was successful.
python test_morse.py
We'll cover move on how to write tests for your Python modules in the Best Practices in Sofware Engineering course but for now, this round-trip will suffice.