Worksheet

This page contains a number of questions and exercises to give you a chance to practise what you have learned this session. You should create a new .py Python file for each exercise.

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
Overwriting morse.py
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.

Exercise

Add documentation to the 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.


# 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):
    """
    Encode a message from English to Morse Code
    
    Args:
        message (str): the English message to encode
    
    Returns:
        str: The encoded message
    
    Examples:
        >>> encode("Help us")
        ".... . .-.. .--. / ..- ..."
    """
    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):
    """
    Decode a message from Morse Code to English
    
    Args:
        message (str): the Morse Code message to decode
    
    Returns:
        str: The decoded English message
    
    Examples:
        >>> encode(".... . .-.. .--. / ..- ...")
        "help us"
    """
    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
Writing morse.py
Exercise
  • Add a test for the decode function to test_morse.py and check it passes with pytest
  • Parametrise both tests to give several examples. Make sure you include upper and lower case letters as well as checking what happens if you pass in empty strings
  • Make sure to use --doctest-modules to run the documentation examples that you added in the last exercise
  • Hint: When writing doctests, it cares whether your test output uses single or double quotes (' or "). Use single quotes for doctest outputs.

# 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):
    """
    Encode a message from English to Morse Code
    
    Args:
        message (str): the English message to encode
    
    Returns:
        str: The encoded message
    
    Examples:
        >>> encode("Help us")
        '.... . .-.. .--. / ..- ...'
    """
    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):
    """
    Decode a message from Morse Code to English
    
    Args:
        message (str): the Morse Code message to decode
    
    Returns:
        str: The decoded English message
    
    Examples:
        >>> decode(".... . .-.. .--. / ..- ...")
        'help us'
    """
    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
Overwriting morse.py

import pytest

from morse import encode, decode

@pytest.mark.parametrize("message, output", [
    ("SOS", "... --- ..."),
    ("help", ".... . .-.. .--."),
    ("", ""),
    (" ", "/"),
])
def test_encode(message, output):
    assert encode(message) == output

@pytest.mark.parametrize("message, output", [
    ("... --- ...", "sos"),
    (".... . .-.. .--.", "help"),
    ("/", " "),
])
def test_decode(message, output):
    assert decode(message) == output
Overwriting test_morse.py
pytest -v --doctest-modules morse.py test_morse.py
=================== test session starts ====================
platform linux -- Python 3.8.5, pytest-6.0.1, py-1.9.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/matt/projects/courses/software_engineering_best_practices
plugins: requests-mock-1.8.0
collected 9 items                                          

morse.py::morse.decode PASSED                        [ 11%]
morse.py::morse.encode PASSED                        [ 22%]
test_morse.py::test_encode[SOS-... --- ...] PASSED   [ 33%]
test_morse.py::test_encode[help-.... . .-.. .--.] PASSED [ 44%]
test_morse.py::test_encode[-] PASSED                 [ 55%]
test_morse.py::test_encode[ -/] PASSED               [ 66%]
test_morse.py::test_decode[... --- ...-sos] PASSED   [ 77%]
test_morse.py::test_decode[.... . .-.. .--.-help] PASSED [ 88%]
test_morse.py::test_decode[/- ] PASSED               [100%]

==================== 9 passed in 0.04s =====================
Exercise
  • What happens if you pass in the string "Don't forget to save us" to encode?
    • Hint: The problem is caused by the ' in the string
  • Edit morse.py to raise a ValueError in this situation instead.
  • Write a test to make sure that the ValueError is raised when a string with a ' is passed in.
  • Parametrise that test with some other examples including the & and £ characters.

# 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):
    """
    Encode a message from English to Morse Code
    
    Args:
        message (str): the English message to encode
    
    Returns:
        str: The encoded message
    
    Examples:
        >>> encode("Help us")
        '.... . .-.. .--. / ..- ...'
    """
    morse = []

    for letter in message:
        letter = letter.lower()
        
        if letter not in _letter_to_morse:
            raise ValueError(f"Cannot encode \"{message}\". Character \"{letter}\" not in Morse dictionary")
        
        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):
    """
    Decode a message from Morse Code to English
    
    Args:
        message (str): the Morse Code message to decode
    
    Returns:
        str: The decoded English message
    
    Examples:
        >>> decode(".... . .-.. .--. / ..- ...")
        'help us'
    """
    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

import pytest

from morse import encode, decode

@pytest.mark.parametrize("message, output", [
    ("SOS", "... --- ..."),
    ("help", ".... . .-.. .--."),
    ("", ""),
    (" ", "/"),
])
def test_encode(message, output):
    assert encode(message) == output

@pytest.mark.parametrize("message, output", [
    ("... --- ...", "sos"),
    (".... . .-.. .--.", "help"),
    ("/", " "),
])
def test_decode(message, output):
    assert decode(message) == output

def test_error():  # New test
    with pytest.raises(ValueError):
        encode("Don't forget to save us")

@pytest.mark.parametrize("message", [
    "It's sinking",
    "Titanic & Olympic",
    "This boat is expensive £££",
    "Help!",
])
def test_errors(message):  # New test
    with pytest.raises(ValueError):
        encode(message)
Overwriting test_morse.py
pytest -v --doctest-modules morse.py test_morse.py
=================== test session starts ====================
platform linux -- Python 3.8.5, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /home/matt/projects/courses/software_engineering_best_practices/venv/bin/python3
cachedir: .pytest_cache
rootdir: /home/matt/projects/courses/software_engineering_best_practices
plugins: nbval-0.9.6
collected 14 items                                         

morse.py::morse.decode PASSED                        [  7%]
morse.py::morse.encode PASSED                        [ 14%]
test_morse.py::test_encode[SOS-... --- ...] PASSED   [ 21%]
test_morse.py::test_encode[help-.... . .-.. .--.] PASSED [ 28%]
test_morse.py::test_encode[-] PASSED                 [ 35%]
test_morse.py::test_encode[ -/] PASSED               [ 42%]
test_morse.py::test_decode[... --- ...-sos] PASSED   [ 50%]
test_morse.py::test_decode[.... . .-.. .--.-help] PASSED [ 57%]
test_morse.py::test_decode[/- ] PASSED               [ 64%]
test_morse.py::test_error PASSED                     [ 71%]
test_morse.py::test_errors[It's sinking] PASSED      [ 78%]
test_morse.py::test_errors[Titanic & Olympic] PASSED [ 85%]
test_morse.py::test_errors[This boat is expensive \xa3\xa3\xa3] PASSED [ 92%]
test_morse.py::test_errors[Help!] PASSED             [100%]

==================== 14 passed in 0.03s ====================

open(./assets)