lists: list comprehensions



List Comprehensions are a way to make a list from an iterable. It is a simple way to go over every item in the list and perform an operation usually in one line

test_making_a_list_from_an_iterable

red: make it fail

I add a file called test_list_comprehensions.py to the tests folder with the following code

import unittest


class TestListComprehensions(unittest.TestCase):

    def test_making_a_list_from_an_iterable(self):
        a_list = []
        self.assertEqual(a_list, [])

        container = range(10)
        for item in container:
            a_list.append(item)
        self.assertEqual(a_list, [])
  • a_list = [] makes an empty list called a_list

  • self.assertEqual(a_list, []) confirms that a_list is empty since it is equal to []

  • container = range(10) makes an iterable of numbers from 0 to 9 with the range constructor and calls it container

  • range makes an iterable of numbers from 0 to the given number minus 1

  • for item in container: uses a for statement to make a loop that goes over every item of container

  • a_list.append(item) adds the item from container to a_list on each cycle of the loop, using the append method, see lists for more details

  • the second self.assertEqual(a_list, []) checks to see if a_list is still empty after the operation

the terminal shows AssertionError because a_list is no longer empty, it has 10 items after the loop runs

E    AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] != []

green: make it pass

I make the values in the test match the result

def test_making_a_list_from_an_iterable(self):
    a_list = []
    self.assertEqual(a_list, [])

    container = range(10)
    for item in container:
        a_list.append(item)
    self.assertEqual(
        a_list,
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    )

and the test passes

refactor: make it better

  • I add another test to check what happens when I use the list constructor on container

    self.assertEqual(list(container), [])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] != []
    
  • I make the empty list [] to a_list

    self.assertEqual(list(container), a_list)
    

    and the test passes because calling list on an iterable makes a list

  • I add another test

    self.assertEqual(
        list_comprehensions.make_a_list(container),
        a_list
    )
    

    the terminal shows NameError

    NameError: name 'list_comprehensions' is not defined
    
  • I add it to the list of Exceptions encountered

    # Exceptions Encountered
    # AssertionError
    # NameError
    

    then add an import statement for list_comprehensions at the beginning of test_list_comprehensions.py to define the name in the tests

    import list_comprehensions
    import unittest
    

    the terminal shows ModuleNotFoundError

    ModuleNotFoundError: No module named 'list_comprehensions'
    
  • I add it to the list of Exceptions encountered

    # Exceptions Encountered
    # AssertionError
    # NameError
    # ModuleNotFoundError
    

    then make a file called list_comprehensions.py in the project folder and the terminal shows AttributeError

    AttributeError: module 'list_comprehensions' has no attribute 'make_a_list'
    
  • I add the error to the list of Exceptions encountered

    # Exceptions Encountered
    # AssertionError
    # NameError
    # ModuleNotFoundError
    # AttributeError
    

    then add a function definition to list_comprehensions.py

    def make_a_list():
        return None
    

    and the terminal shows TypeError

    TypeError: make_a_list() takes 0 positional arguments but 1 was given
    
  • I add it to the list of Exceptions encountered

    # Exceptions Encountered
    # AssertionError
    # NameError
    # ModuleNotFoundError
    # AttributeError
    # TypeError
    

    then change the signature of the function to take in an argument

    def make_a_list(argument):
        return None
    

    and the terminal shows AssertionError

    AssertionError: None != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
  • When I make the function return the result of calling the list constructor with argument as input

    def make_a_list(argument):
        return list(argument)
    

    the test passes

  • I make the name of the function’s input from argument to iterable to make it more explicit

    def make_a_list(iterable):
        return list(iterable)
    

From the tests I see that I can make a list from any iterable by using the list constructor


test_making_a_list_w_a_for_loop

red: make it fail

I add a test for making a list with a for loop

def test_making_a_list_w_a_for_loop(self):
    a_list = []
    self.assertEqual(a_list, [])

    container = range(10)
    for item in container:
        a_list.append(item)

    self.assertEqual(a_list, [])
    self.assertEqual(
        list_comprehensions.for_loop(container),
        a_list
    )

the terminal shows AssertionError for the values of a_list because it is no longer empty after I loop through container then add items

AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] != []

green: make it pass

  • I make the values of the test match the result

    def test_making_a_list_w_a_for_loop(self):
        a_list = []
        self.assertEqual(a_list, [])
    
        container = range(10)
        for item in container:
            a_list.append(item)
    
        self.assertEqual(
            a_list,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        )
        self.assertEqual(
            list_comprehensions.for_loop(container),
            a_list
        )
    

    the terminal shows AttributeError since list_comprehensions.py does not have a definition for for_loop

    AttributeError: module 'list_comprehensions' has no attribute 'for_loop'
    
  • I add a function definition called for_loop to list_comprehensions.py

    def for_loop():
        return None
    

    and the terminal shows TypeError because the function signature does not match the call in the test

    TypeError: for_loop() takes 0 positional arguments but 1 was given
    
  • I make the signature of the function to take input

    def for_loop(argument):
        return None
    

    and the terminal shows AssertionError

    AssertionError: None != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
  • I make the behavior of the function to use a for loop

    def for_loop(argument):
        result = []
        for item in argument:
            result.append(item)
        return result
    
    • result = [] makes an empty list called result

    • for item in argument: makes a loop over the items of argument which is an iterable passed into the function

    • result.append(item) adds each item from argument to the list called result

    • return result returns result after the loop completes

    the terminal shows all tests are passing

  • I change the input name from argument to iterable to be more explicit

    def for_loop(iterable):
        result = []
        for item in iterable:
            result.append(item)
        return result
    

    all tests are still passing

From the tests I see that I can make a list from any iterable by using


test_making_lists_w_list_comprehensions

red: make it fail

I add a failing test to TestListComprehensions

def test_making_lists_w_list_comprehensions(self):
    a_list = []
    self.assertEqual(a_list, [])

    container = range(10)
    for item in container:
        a_list.append(item)

    self.assertEqual(a_list, [])
    self.assertEqual([], a_list)
    self.assertEqual(
        list_comprehensions.list_comprehension(container),
        a_list
    )

the terminal shows AssertionError

AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] != []

green: make it pass

  • I make the values in the test match the terminal

    def test_making_lists_w_list_comprehensions(self):
        a_list = []
        self.assertEqual(a_list, [])
    
        container = range(10)
        for item in container:
            a_list.append(item)
    
        self.assertEqual(
            a_list,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        )
        self.assertEqual([], a_list)
        self.assertEqual(
            list_comprehensions.list_comprehension(container),
            a_list
        )
    

    and the terminal shows another AssertionError for the next line

    AssertionError: Lists differ: [] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
  • this time I add a list comprehension to show how it is written

    def test_making_lists_w_list_comprehensions(self):
        a_list = []
        self.assertEqual(a_list, [])
    
        container = range(10)
        for item in container:
            a_list.append(item)
    
        self.assertEqual(
            a_list,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        )
        self.assertEqual(
            [item for item in container],
            a_list
        )
        self.assertEqual(
            list_comprehensions.list_comprehension(container),
            a_list
        )
    

    the terminal now shows AttributeError for the last line

    AttributeError: module 'list_comprehensions' has no attribute 'list_comprehension'
    
  • I add a function that uses a list comprehension to list_comprehensions.py

    def list_comprehension(argument):
        return [item for item in argument]
    

    and all tests pass

  • I change argument to iterable to be more explicit

    def list_comprehension(iterable):
        return [item for item in iterable]
    

    I made 2 functions, one that uses a for loop and another that uses a list comprehension to do the same thing. The difference between

    a_list = []
    for item in container:
        a_list.append()
    

    and

    [item for item in container]
    

    Is that in the first case I have to

    • make a list

    • loop through the iterable

    • add the items I want from the iterable to the list

    With the list comprehension I can get the same result with less words, lines and steps

refactor: make it better

There is more I can do with list comprehensions, I can add conditions to the operations performed

  • I add a failing test to TestListComprehensions

    def test_list_comprehensions_w_conditions_i(self):
        even_numbers = []
        self.assertEqual(even_numbers, [])
    
        container = range(10)
        for item in container:
            if item % 2 == 0:
                even_numbers.append(item)
    
        self.assertEqual(even_numbers, [])
        self.assertEqual(
            [],
            even_numbers
        )
        self.assertEqual(
            list_comprehensions.get_even_numbers(container),
            even_numbers
        )
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 2, 4, 6, 8] != []
    
    • if item % 2 == 0: checks if the item in container leaves a remainder of 0 when divided by 2

    • % is a modulo operator which divides the number on the left by the number on the right and gives a remainder

    • even_numbers.append(item) adds item to even_numbers if item divided by 2 leaves a remainder of 0

  • I add the values of the result to the test to make it pass

    def test_list_comprehensions_w_conditions_i(self):
        even_numbers = []
        self.assertEqual(even_numbers, [])
    
        container = range(10)
        for item in container:
            if item % 2 == 0:
                even_numbers.append(item)
    
        self.assertEqual(even_numbers, [0, 2, 4, 6, 8])
        self.assertEqual(
            [],
            even_numbers
        )
        self.assertEqual(
            list_comprehensions.get_even_numbers(container),
            even_numbers
        )
    

    and the terminal shows AssertionError for the next line

    AssertionError: Lists differ: [] != [0, 2, 4, 6, 8]
    
  • I try using a list comprehension like I did in the last example

    def test_list_comprehensions_w_conditions_i(self):
        even_numbers = []
        self.assertEqual(even_numbers, [])
    
        container = range(10)
        for item in container:
            if item % 2 == 0:
                even_numbers.append(item)
    
        self.assertEqual(even_numbers, [0, 2, 4, 6, 8])
        self.assertEqual(
            [item for item in container],
            even_numbers
        )
        self.assertEqual(
            list_comprehensions.get_even_numbers(container),
            even_numbers
        )
    

    and get AssertionError because the lists are not the same, I have too many values

    AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] != [0, 2, 4, 6, 8]
    
  • When I add the if condition to the list comprehension

    self.assertEqual(
        [item for item in container if item % 2 == 0],
        even_numbers
    )
    

    the terminal shows AttributeError for the next line. Progress

    AttributeError: module 'list_comprehensions' has no attribute 'get_even_numbers'
    
  • I add a function definition to list_comprehensions.py using the list comprehension I just wrote

    def get_even_numbers(iterable):
        return [item for item in iterable if item % 2 == 0]
    

    and the terminal shows passing tests, Hooray!

  • I want to try another list comprehension with a different condition so I add a test to TestListComprehensions

    def test_list_comprehensions_w_conditions_ii(self):
        odd_numbers = []
        self.assertEqual(odd_numbers, [])
    
        container = range(10)
        for item in container:
            if item % 2 != 0:
                odd_numbers.append(item)
    
        self.assertEqual(odd_numbers, [])
        self.assertEqual([], odd_numbers)
        self.assertEqual(
            list_comprehensions.get_odd_numbers(container),
            odd_numbers
        )
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [1, 3, 5, 7, 9] != []
    
  • when I make the values to match

    def test_list_comprehensions_w_conditions_ii(self):
        odd_numbers = []
        self.assertEqual(odd_numbers, [])
    
        container = range(10)
        for item in container:
            if item % 2 != 0:
                odd_numbers.append(item)
    
        self.assertEqual(odd_numbers, [1, 3, 5, 7, 9])
        self.assertEqual([], odd_numbers)
        self.assertEqual(
            list_comprehensions.get_odd_numbers(container),
            odd_numbers
        )
    

    the terminal shows AssertionError for the next test

    AssertionError: Lists differ: [] != [1, 3, 5, 7, 9]
    
  • I make the value on the left with a list comprehension that uses the same condition I used to make even numbers

    self.assertEqual(
        [item for item in container if item % 2 == 0],
        odd_numbers
    )
    

    and the terminal shows AssertionError

    AssertionError: Lists differ: [0, 2, 4, 6, 8] != [1, 3, 5, 7, 9]
    
  • When I make the logic in the condition so it uses not equal to 0 instead

    self.assertEqual(
        [item for item in container if item % 2 != 0],
        odd_numbers
    )
    

    the terminal shows AttributeError for the next line

    AttributeError: module 'list_comprehensions' has no attribute 'get_odd_numbers'
    
  • I define a function that returns a list comprehension in list_comprehensions.py

    def get_odd_numbers(iterable):
        return [item for item in iterable if item % 2 != 0]
    

    and the terminal shows all tests passed

    I see from the tests that I can make a list from any iterable by using

    If you typed along you now know a couple of ways to loop through iterables and have your program make decisions by using conditions.

    You also know how to do it with less words using list comprehensions. Your magic powers are growing.

  • I have written the same thing multiple times in these tests and since the programming gods told me to not repeat myself, It is time to remove the repetition in the code. In each test I make an empty list, verify it is empty and then perform operations on it. Since that part is the same for every test I can add it to the unittest.TestCase.setUp method which is called before a test method. Anything I place in this method will run before the tests, I place my empty list creation and verification in here

    def setUp(self):
        self.a_list = []
        self.assertEqual(self.a_list, [])
    
  • I make a reference to self.a_list in test_making_a_list_from_an_iterable

    def test_making_a_list_from_an_iterable(self):
        a_list = []
        self.assertEqual(a_list, [])
    
        container = range(10)
        for item in container:
            self.a_list.append(item)
        self.assertEqual(
            self.a_list,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        )
        self.assertEqual(list(container), self.a_list)
        self.assertEqual(
            list_comprehensions.make_a_list(container),
            self.a_list
        )
    

    the terminal still shows passing tests

  • I remove the creation of the empty list and verification from test_making_a_list_from_an_iterable

    def test_making_a_list_from_an_iterable(self):
        container = range(10)
        for item in container:
            self.a_list.append(item)
        self.assertEqual(
            self.a_list,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        )
        self.assertEqual(list(container), self.a_list)
        self.assertEqual(
            list_comprehensions.make_a_list(container),
            self.a_list
        )
    

    the terminal still shows passing tests

  • I make the same change in the other tests

    def test_making_a_list_w_a_for_loop(self):
        container = range(10)
        for item in container:
            self.a_list.append(item)
    
        self.assertEqual(
            self.a_list,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        )
        self.assertEqual(
            list_comprehensions.for_loop(container),
            self.a_list
        )
    
    def test_making_lists_w_list_comprehensions(self):
        container = range(10)
        for item in container:
            self.a_list.append(item)
    
        self.assertEqual(
            self.a_list,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        )
        self.assertEqual(
            [item for item in container],
            self.a_list
        )
        self.assertEqual(
            list_comprehensions.list_comprehension(container),
            self.a_list
        )
    
    def test_list_comprehensions_w_conditions_i(self):
        container = range(10)
        for item in container:
            if item % 2 == 0:
                self.a_list.append(item)
    
        self.assertEqual(self.a_list, [0, 2, 4, 6, 8])
        self.assertEqual(
            [item for item in container if item % 2 == 0],
            self.a_list
        )
        self.assertEqual(
            list_comprehensions.get_even_numbers(container),
            self.a_list
        )
    
    def test_list_comprehensions_w_conditions_ii(self):
        container = range(10)
        for item in container:
            if item % 2 != 0:
                self.a_list.append(item)
    
        self.assertEqual(self.a_list, [1, 3, 5, 7, 9])
        self.assertEqual(
            [item for item in container if item % 2 != 0],
            self.a_list
        )
        self.assertEqual(
            list_comprehensions.get_odd_numbers(container),
            self.a_list
        )
    

    the terminal still shows passing test

  • In each test I make a range object named container, I can add this to the setUp method and reference it in the tests

    def setUp(self):
        self.a_list = []
        self.assertEqual(self.a_list, [])
        self.container = range(10)
    
    def test_making_a_list_from_an_iterable(self):
        for item in self.container:
            self.a_list.append(item)
    
        self.assertEqual(
            self.a_list,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        )
        self.assertEqual(list(self.container), self.a_list)
        self.assertEqual(
            list_comprehensions.make_a_list(self.container),
            self.a_list
        )
    
    def test_making_a_list_w_a_for_loop(self):
        for item in self.container:
            self.a_list.append(item)
    
        self.assertEqual(
            self.a_list,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        )
        self.assertEqual(
            list_comprehensions.for_loop(self.container),
            self.a_list
        )
    
    def test_making_lists_w_list_comprehensions(self):
        for item in self.container:
            self.a_list.append(item)
    
        self.assertEqual(
            self.a_list,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        )
        self.assertEqual(
            [item for item in self.container],
            self.a_list
        )
        self.assertEqual(
            list_comprehensions.list_comprehension(self.container),
            self.a_list
        )
    
    def test_list_comprehensions_w_conditions_i(self):
        for item in self.container:
            if item % 2 == 0:
                self.a_list.append(item)
    
        self.assertEqual(self.a_list, [0, 2, 4, 6, 8])
        self.assertEqual(
            [item for item in self.container if item % 2 == 0],
            self.a_list
        )
        self.assertEqual(
            list_comprehensions.get_even_numbers(self.container),
            self.a_list
        )
    
    def test_list_comprehensions_w_conditions_ii(self):
        for item in self.container:
            if item % 2 != 0:
                self.a_list.append(item)
    
        self.assertEqual(self.a_list, [1, 3, 5, 7, 9])
        self.assertEqual(
            [item for item in self.container if item % 2 != 0],
            self.a_list
        )
        self.assertEqual(
            list_comprehensions.get_odd_numbers(self.container),
            self.a_list
        )
    

    the terminal shows all tests are still passing


review

I also ran into the following Exceptions

Would you like to test dictionaries?


data structures: List Comprehensions: tests and solutions