lists: list comprehensions



List Comprehensions are a simple to make a list from an iterable, by going over every item and performing operations with one line

requirements

  • I open a terminal to run makePythonTdd.sh with list_comprehensions as the name of the project

    ./makePythonTdd.sh list_comprehensions
    

    on Windows without Windows Subsystem Linux use makePythonTdd.ps1

    ./makePythonTdd.ps1 list_comprehensions
    

    it makes the folders and files that are needed, installs packages, runs the first test, and the terminal shows AssertionError

    E       AssertionError: True is not false
    
    tests/test_list_comprehensions.py:7: AssertionError
    
  • I hold ctrl (windows/linux) or option (mac) on the keyboard and use the mouse to click on tests/test_list_comprehensions.py:7 to open it in the editor

  • then change True to False to make the test pass


test_make_a_list_w_a_for_loop

red: make it fail

I change test_failure to test_make_a_list_w_a_for_loop

import unittest


class TestListComprehensions(unittest.TestCase):

  def test_make_a_list_w_a_for_loop(self):
      a_list = []
      iterable = range(10)

      for item in iterable:
          a_list.append(item)

      self.assertEqual(a_list, [])

the terminal shows AssertionError

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

green: make it pass

I copy the value from the terminal and use it as the expectation

self.assertEqual(a_list, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

the test passes, the list is no longer empty after calling the append method in the for loop which goes over every item in the iterable

refactor: make it better

  • I add another assert statement

    self.assertEqual(a_list, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    self.assertEqual(
        src.list_comprehensions.for_loop(iterable),
        a_list
    )
    

    the terminal shows NameError

    NameError: name 'src' is not defined
    

    I add it to the list of Exceptions encountered

    # Exceptions Encountered
    # AssertionError
    # NameError
    

    then I add an import statement

    import src.list_comprehensions
    import unittest
    

    the terminal shows AttributeError

    AttributeError: module 'src.list_comprehensions' has no attribute 'for_loop'
    

    I add the error to the list of Exceptions encountered

    # Exceptions Encountered
    # AssertionError
    # NameError
    # AttributeError
    

    I add a function definition to list_comprehensions.py

    def for_loop():
        return None
    

    and the terminal shows TypeError

    TypeError: for_loop() takes 0 positional arguments but 1 was given
    

    I add the error to the list of Exceptions encountered

    # Exceptions Encountered
    # AssertionError
    # NameError
    # AttributeError
    # TypeError
    

    then I change the signature of the function to take input

    def for_loop(argument):
        return None
    

    the terminal shows AssertionError

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

    I add a for loop to the function

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

    the test passes

    • result = [] makes an empty list

    • for item in argument: loops over the items of argument

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

    • return result returns result after the loop completes

  • I change the input name from argument to iterable to make it clearer

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

    all tests are still passing

I can make a list from an iterable by using a for loop or the list constructor


test_make_a_list_w_list_comprehensions

red: make it fail

I add a failing test

def test_make_a_list_w_list_comprehensions(self):
    iterable = range(10)

    self.assertEqual(
        src.list_comprehensions.for_loop(iterable),
        []
    )

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

self.assertEqual(
    src.list_comprehensions.for_loop(iterable),
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
)

the test passes

refactor: make it better

I change the expectation to use a list comprehension

self.assertEqual(
    src.list_comprehensions.for_loop(iterable),
    # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    [item for item in iterable]
)

the terminal still shows green. I remove the comment then add another assert statement

self.assertEqual(
    src.list_comprehensions.for_loop(iterable),
    [item for item in iterable]
)
self.assertEqual(
    src.list_comprehensions.list_comprehension(iterable),
    src.list_comprehensions.for_loop(iterable)
)

the terminal shows AttributeError

AttributeError: module 'src.list_comprehensions' has no attribute 'list_comprehension'

I add a function that uses a list comprehension to list_comprehensions.py

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


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

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

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

and

[item for item in iterable]

the difference between them is that in the first case I have to

When I use list comprehensions, I get the same result with one line that covers all the steps


test_list_comprehensions_w_conditions_i

There is more I can do with list comprehensions, I can add conditions when I perform an operation

red: make it fail

I add a failing test

def test_list_comprehensions_w_conditions_i(self):
    iterable = range(10)

    even_numbers = []
    for item in iterable:
        if item % 2 == 0:
            even_numbers.append(item)

    self.assertEqual(even_numbers, [])

the terminal shows AssertionError

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

  • % is the modulo operator, it divides the number on the left by the number on the right and returns a remainder

green: make it pass

I copy the values from the terminal and paste in the test

self.assertEqual(even_numbers, [0, 2, 4, 6, 8])

the test passes

refactor: make it better

  • I add another assert statement

    self.assertEqual(even_numbers, [0, 2, 4, 6, 8])
    self.assertEqual(
        [item for item in iterable],
        even_numbers
    )
    

    the terminal shows AssertionError

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

    the list comprehension is missing the condition, I add it

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

    the test passes, I add another assert statement

    self.assertEqual(
        [item for item in iterable if item % 2 == 0],
        even_numbers
    )
    self.assertEqual(
        src.list_comprehensions.get_even_numbers(iterable),
        even_numbers
    )
    

    the terminal shows AttributeError

    AttributeError: module 'src.list_comprehensions' has no attribute 'get_even_numbers'
    

    I add a function to list_comprehensions.py

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

    the test passes


test_list_comprehensions_w_conditions_ii

I add another test

def test_list_comprehensions_w_conditions_ii(self):
    iterable = range(10)

    odd_numbers = []
    for item in iterable:
        if item % 2 != 0:
            odd_numbers.append(item)

    self.assertEqual(odd_numbers, [])

the terminal shows AssertionError

AssertionError: Lists differ: [1, 3, 5, 7, 9] != []

I change the expectation to match

self.assertEqual(odd_numbers, [1, 3, 5, 7, 9])

the test passes. I add another assert statement

self.assertEqual(odd_numbers, [1, 3, 5, 7, 9])
self.assertEqual(
    [item for item in iterable],
    odd_numbers
)

the terminal shows AssertionError

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

I add the condition to the list comprehension

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

the test passes and I add another assert statement

self.assertEqual(
    [item for item in iterable if item % 2 != 0],
    odd_numbers
)
self.assertEqual(
    src.list_comprehensions.get_odd_numbers(iterable),
    odd_numbers
)

the terminal shows AttributeError

AttributeError: module 'src.list_comprehensions' has no attribute 'get_odd_numbers'. Did you mean: 'get_even_numbers'?

I add the function

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


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

I used the same iterable for every test, I can remove the repetition by using the setUp method

class TestListComprehensions(unittest.TestCase):

    def setUp(self):
        self.iterable = range(10)

then change all the references to the variable

def test_make_a_list_w_a_for_loop(self):
    a_list = []
    for item in self.iterable:
        a_list.append(item)

    self.assertEqual(a_list, list(self.iterable))
    self.assertEqual(
        src.list_comprehensions.for_loop(self.iterable),
        a_list
    )

def test_make_a_list_w_list_comprehensions(self):
    self.assertEqual(
        src.list_comprehensions.for_loop(self.iterable),
        [item for item in self.iterable]
    )
    self.assertEqual(
        src.list_comprehensions.list_comprehension(self.iterable),
        src.list_comprehensions.for_loop(self.iterable)
    )

def test_list_comprehensions_w_conditions_i(self):
    even_numbers = []
    for item in self.iterable:
        if item % 2 == 0:
            even_numbers.append(item)

    self.assertEqual(
        [item for item in self.iterable if item % 2 == 0],
        even_numbers
    )
    self.assertEqual(
        src.list_comprehensions.get_even_numbers(self.iterable),
        even_numbers
    )

def test_list_comprehensions_w_conditions_ii(self):
    odd_numbers = []
    for item in self.iterable:
        if item % 2 != 0:
            odd_numbers.append(item)

    self.assertEqual(
        [item for item in self.iterable if item % 2 != 0],
        odd_numbers
    )
    self.assertEqual(
        src.list_comprehensions.get_odd_numbers(self.iterable),
        odd_numbers
    )

the terminal shows all tests are still passing


review

From the tests I can make a list from an iterable by using

I also ran into the following Exceptions

Would you like to test dictionaries?


data structures: List Comprehensions: tests and solutions