We've now covered all the topics on this course so to finish off, work through this final exercise. It is designed to give you a chance to pratise what you've learned on some new code.
Make a new directory alongside the bestpractices
folder called crypto
. In the Terminal change to that directory with cd ../crypto
and in the Python Console change there with %cd ../crypto
. In that directory make two new files called morse.py
and test_morse.py
:
# A lookup dictionary which, given a letter will return the morse code equivalent
_letter_to_morse = {'a':'.-', 'b':'-...', 'c':'-.-.', 'd':'-..', 'e':'.', 'f':'..-.',
'g':'--.', 'h':'....', 'i':'..', 'j':'.---', 'k':'-.-', 'l':'.-..', 'm':'--',
'n':'-.', 'o':'---', 'p':'.--.', 'q':'--.-', 'r':'.-.', 's':'...', 't':'-',
'u':'..-', 'v':'...-', 'w':'.--', 'x':'-..-', 'y':'-.--', 'z':'--..',
'0':'-----', '1':'.----', '2':'..---', '3':'...--', '4':'....-',
'5':'.....', '6':'-....', '7':'--...', '8':'---..', '9':'----.',
' ':'/'}
# 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 encode(message):
morse = []
for letter in message:
letter = letter.lower()
morse.append(_letter_to_morse[letter])
# We need to join together Morse code letters with spaces
morse_message = " ".join(morse)
return morse_message
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.append(_morse_to_letter[letter])
# Rejoin, but now we don't need to add any spaces
english_message = "".join(english)
return english_message
from morse import encode, decode
def test_encode():
assert encode("SOS") == "... --- ..."
This module is designed to convert message to and from Morse code. It provides one function which takes an English message and converts it to a Morse code string, separated by spaces and another function which takes the Morse code string and converts it to English.
morse
module and to the encode
and decode
functions. Make sure you detail the inputs, outputs and give an example of their usage. Look at the tests to get an idea of how it works or try importing morse
in the Python Console and have a play with the functions to understand them.decode
function to test_morse.py
and check it passes with pytest
--doctest-modules
to run the documentation examples that you added in the last exercise'
or "
). Use single quotes for doctest outputs."Don't forget to save us"
to encode
?'
in the stringmorse.py
to raise a ValueError
in this situation instead.ValueError
is raised when a string with a '
is passed in.&
and £
characters.Let's add another text cypher to our crypto
package. This time we will implement the Caesar Cipher or ROT13. Once more the module will provide encode
and decode
functions:
import string
_lower_cipher = string.ascii_lowercase[13:] + string.ascii_lowercase[:13]
_upper_cipher = string.ascii_uppercase[13:] + string.ascii_uppercase[:13]
def encode(message):
output = []
for letter in message:
if letter in string.ascii_lowercase:
i = string.ascii_lowercase.find(letter)
output.append(_lower_cipher[i])
elif letter in string.ascii_uppercase:
i = string.ascii_uppercase.find(letter)
output.append(_upper_cipher[i])
return "".join(output)
def decode(message):
output = []
for letter in message:
if letter in _lower_cipher:
i = _lower_cipher.find(letter)
output.append(string.ascii_uppercase[i])
elif letter in _upper_cipher:
i = _upper_cipher.find(letter)
output.append(string.ascii_uppercase[i])
return "".join(output)
This time the tests are provided for you. Copy this into a new file called test_rot13.py
:
import pytest
from rot13 import encode, decode
@pytest.mark.parametrize("message, expected", [
("SECRET", "FRPERG"),
("secret", "frperg"),
])
def test_encode(message, expected):
assert encode(message) == expected
@pytest.mark.parametrize("message, expected", [
("FRPERG", "SECRET"),
("frperg", "secret"),
])
def test_decode(message, expected):
assert decode(message) == expected
def test_encode_spaces_error():
with pytest.raises(ValueError):
encode("Secret message for you")
When we run these tests with pytest
we see that there are some passes and some failures:
pytest -v test_rot13.py
There are two failing tests:
test_rot13.py::test_decode[frperg-secret]
is failing due to a bug in the code. Find the bug in rot13.py
and fix it so that the test passes.test_rot13.py::test_encode_spaces_error
is failing due to a missing feature in our code. At the moment any spaces in the string are ignored. Change encode
and decode
in rot13.py
so that they raise an error if any letter in the message is not found in the lookup string.else
to the if
/elif
blockstest_morse.py
and test_rot13.py
which checks for "round-tripping". That is, check that a valid message which is passed to encode
and then the output of that is passed to decode
gets you back the original message.morse
? What could you do to the test to make it pass?