Skip to main content

Utilizing Control Flow Structures in Python

Hands-On Lab

 

Photo of Keith Thompson

Keith Thompson

DevOps Training Architect II in Content

Length

00:30:00

Difficulty

Beginner

To be effective with Python, one needs to be comfortable using the control flow structures it provides, either to take actions or to perform the same action multiple times. In this hands-on lab, we'll be utilizing control flow structures, like if statements and loops, to fix some failing automated tests, and ensure that the application works correctly. By the time we're finished with this hands-on lab, we should be more comfortable using if statements and for loops to write more robust functions.

What are Hands-On Labs?

Hands-On Labs are scenario-based learning environments where learners can practice without consequences. Don't compromise a system or waste money on expensive downloads. Practice real-world skills without the real-world risk, no assembly required.

Utilizing Control Flow Structures in Python

Introduction

To be effective with Python, one needs to be comfortable using the control flow structures it provides, either to take actions or to perform the same action multiple times. In this hands-on lab, we'll be utilizing control flow structures, like if statements and loops, to fix some failing automated tests, and ensure that the application works correctly.

By the time we're finished with this hands-on lab, we should be more comfortable using if statements and for loops to write more robust functions.

The Scenario

We've been tasked with building a terminal-based todo list application, and it's in its earliest stages. A co-worker has written and documented a few functions that interact with "todo" dictionaries, but there are some edge cases that aren't handled. By the time we've finished with our implementation, we should have modified the implementation for the take_first function to handle an empty list of todos, and modified sum_points to handle any number of todos.

Thankfully, our co-worker has already adjusted the doctests for these functions, to demonstrate how they should work. We have some automated tests that we can run, to ensure that our implementation meets the requirements. To run the tests, we'll use the following command from within the ~/tasker directory:

[cloud_user@host]$ python3.7 -m doctest -v tasker.py
...
3 items had failures:
   1 of   2 in tasker.print_todo
   2 of   2 in tasker.sum_points
   3 of   4 in tasker.take_first
8 tests in 4 items.
2 passed and 6 failed.
***Test Failed*** 6 failures.
[cloud_user@host]$

By the time we've implemented these functions, we'll have proven our knowledge of some of Python's built-in control flow structures.

Get logged in

Use the credentials and server IP in the hands-on lab overview page to log into the server with SSH. Once we're in, we can cd tasker so that we're in the right directory when we get to work.

Correct the take_first function

Currently, the take_first function will raise an error if it receives an empty list of todos, but we'd like to handle that differently. Our doctests show that we should instead be returning (None, todos) if todos is empty. There is more than one way that we could go about doing this:

  1. Use a conditional statement to see if todos is empty right away. If it is, then return (None, todos).
  2. Use a try statement, and if an error is raised return (None, todos).

For simplicity's sake let's use a conditional, instead of error handling, as control flow:

def take_first(todos):
    """
    take_first receives a list of todos and removes the first todo
    and returns that todo and the remaining todos in a tuple

    >>> todos = [{'name': 'Example 1', 'body': 'This is a test task', 'points': '3'},
    ... {'name': 'Task 2', 'body': 'Yet another example task', 'points': '2'}]
    >>> todo, todos = take_first(todos)
    >>> todo
    {'name': 'Example 1', 'body': 'This is a test task', 'points': '3'}
    >>> todos
    [{'name': 'Task 2', 'body': 'Yet another example task', 'points': '2'}]
    >>> todos = []
    >>> take_first(todos)
    (None, [])
    """
    if todos:
        todo = todos.pop(0)
        return (todo, todos)
    else:
        return (None, todos)

Correct the sum_points Function

The original version of sum_points takes two todo dictionaries and returns the sum of their point values. But that's not very useful. For us to make this work for us, we're going to modify the function so that it will take a list of todo dictionaries and sum all of the point values. We can do this easily, using a for loop and an accumulator:

def sum_points(todos):
    """
    sum_points receives two todo dictionaries and returns the sum of their `point` values.

    >>> todos = [{'name': 'Example 1', 'body': 'This is a test task', 'points': '3'},
    ... {'name': 'Task 2', 'body': 'Yet another example task', 'points': '2'},
    ... {'name': 'Task 3', 'body': 'Third task', 'points': '5'}]
    >>> sum_points(todos)
    10
    """
    total = 0
    for todo in todos:
        total += int(todo['points'])
    return total

Conclusion

After two simple fixes, we were able to get tasker.py performing as it should, or at least as far as it's been developed to do so far. Congratulations!