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(0, 4)

      for item in iterable:
          a_list.append(item)

      self.assertEqual(a_list, [])

the terminal shows AssertionError

AssertionError: Lists differ: [0, 1, 2, 3] != []

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

green: make it pass

I change the expectation to match the values in the terminal

self.assertEqual(a_list, [0, 1, 2, 3])

the test passes

refactor: make it better

  • I add another assertion to show that this could have been achieved with the list constructor

    self.assertEqual(a_list, [0, 1, 2, 3])
    self.assertEqual(a_list, list(iterable))
    

    the test is still green. Why use a for loop when you can use the list constructor to get the same thing? I will show this in a little bit

  • but first for some practice with the for loop I add another assertion

    self.assertEqual(a_list, list(iterable))
    self.assertEqual(
        src.list_comprehensions.for_loop(iterable),
        [0, 1, 2, 3]
    )
    

    the terminal shows NameError

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

    # Exceptions Encountered
    # AssertionError
    # NameError
    
  • 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 it to the list of Exceptions encountered

    # Exceptions Encountered
    # AssertionError
    # NameError
    # AttributeError
    
  • I add a function to list_comprehensions.py

    def for_loop(iterable):
        return [0, 1, 2, 3]
    

    the test passes

  • I need a better test, this one breaks if I change the values in the range object

    iterable = range(0, 5)
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1, 2, 3, 4] != [0, 1, 2, 3]
    
  • I undo the change then import the random module

    import random
    import src.list_comprehensions
    import unittest
    
  • I change the values given to the range object

    iterable = range(0, random.randint(2, 1000))
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1[2158 chars] 464] != [0, 1, 2, 3]
    AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1[2283 chars] 489] != [0, 1, 2, 3]
    AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1[2118 chars] 456] != [0, 1, 2, 3]
    AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1[773 chars] 187] != [0, 1, 2, 3]
    

    the values change every time the test runs because I am using random integers

  • I change the expectation in the first assertion

    self.assertEqual(a_list, list(iterable))
    self.assertEqual(a_list, list(iterable))
    

    the test passes. Since it is a duplication I remove the line

  • I change the expectation of the second assertion

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

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1, 2, 3] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1[1393 chars] 311]
    AssertionError: Lists differ: [0, 1, 2, 3] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1[3743 chars] 781]
    AssertionError: Lists differ: [0, 1, 2, 3] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1[2208 chars] 474]
    AssertionError: Lists differ: [0, 1, 2, 3] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1[4778 chars] 988]
    

    I change the return statement in the function

    def for_loop(iterable):
        return list(iterable)
    

    the test passes, but I want to practice writing a for loop, I change the function

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

    the test is green, this is not yet better than using the list constructor, there is another way


test_make_a_list_w_list_comprehensions

red: make it fail

I add a failing test

def test_make_a_list_w_a_for_loop(self):
    ...

def test_make_a_list_w_list_comprehensions(self):
    iterable = range(0, random.randint(2, 1000))
    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, 10, 11, 12,[413 chars] 113] != []
AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,[3178 chars] 666] != []
AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,[568 chars] 144] != []
AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,[3253 chars] 681] != []

green: make it pass

I use the list constructor

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

the test passes

refactor: make it better

  • I can do the same thing with a list comprehension

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

    the terminal still shows green. I remove the comment then add another assertion for practice

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

    the terminal shows AttributeError

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

    I add the function

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

    the test passes

  • I made the same variable twice for the iterable, I will move it to the setUp method to remove duplication

    def setUp(self):
        self.iterable = range(0, random.randint(2, 1000))
    

    then use it in test_make_a_list_w_a_for_loop

    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),
            list(self.iterable)
        )
    

    the test is still green. I do the same with test_make_a_list_w_list_comprehensions

    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),
            [item for item in self.iterable]
        )
    

    the terminal still shows green

  • 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

    • make a list

    • loop through the iterable

    • do the operation I want on the item of the iterable

    When I use list comprehensions, I get the same result with one line that covers all those steps, but so far none of this is better than using the list constructor


test_list_comprehensions_w_conditions

What if I had to build a list from an iterable but based on a condition? This is where a for loop or a list comprehension works better

red: make it fail

I add a failing test

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

    self.assertEqual(even_numbers, list(self.iterable))

the terminal shows AssertionError

AssertionError: Lists differ: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 2[2375 chars] 990] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13[4800 chars] 991]
AssertionError: Lists differ: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 2[680 chars] 312] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13[1410 chars] 313]
AssertionError: Lists differ: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 2[137 chars], 94] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13[320 chars], 94]
AssertionError: Lists differ: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 2[670 chars] 308] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13[1390 chars] 309]
  • 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, it is covered in test_the_modulo_operation

green: make it pass

I change the expectation to use a list comprehension

self.assertEqual(
    even_numbers,
    [item for item in self.iterable]
)

the terminal still shows AssertionError, I add the condition

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

the test passes

refactor: make it better

  • I add another assertion for practice

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

    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):
        ...
    
    
    def get_even_numbers(iterable):
        return [item for item in iterable if item % 2 == 0]
    

    the test passes

  • I wrote the same condition in the test 3 times. I add a function for it

    import unittest
    
    
    def condition(number):
        return number % 2 == 0
    

    I change the condition in the test

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

    the terminal still shows green. I remove the commented line and do the same thing in the first assertion

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

    still green. I do it again with the next one

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

    the terminal still shows green

  • I add a new empty list

    even_numbers = []
    odd_numbers = []
    

    then add an else clause in the for loop

    for item in self.iterable:
        if condition(item):
            even_numbers.append(item)
        else:
            odd_numbers.append(item)
    

    I add an assertion

    self.assertEqual(
        src.list_comprehensions.get_even_numbers(self.iterable),
        [item for item in self.iterable if condition(item)]
    )
    self.assertEqual(
        odd_numbers,
        [item for item in self.iterable]
    )
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23[333 chars] 173] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,[713 chars] 173]
    AssertionError: Lists differ: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23[1723 chars] 729] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,[3493 chars] 729]
    AssertionError: Lists differ: [1, 3] != [0, 1, 2, 3, 4]
    AssertionError: Lists differ: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23[1958 chars] 823] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,[3963 chars] 823]
    

    I add the condition to the assertion

    self.assertEqual(
        odd_numbers,
        [item for item in self.iterable if not condition(item)]
    )
    

    the test passes. I add another assertion

    self.assertEqual(
        odd_numbers,
        [item for item in self.iterable if not condition(item)]
    )
    self.assertEqual(
        src.list_comprehensions.get_odd_numbers(self.iterable),
        [item for item in self.iterable if not condition(item)]
    )
    

    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):
        ...
    
    
    def get_odd_numbers(iterable):
        return [item for item in iterable if item % 2 != 0]
    

    the test passes

  • condition is not a descriptive name in this case, I am only using it to show that you can use any condition you want with the list comprehension. I can add a function for the conditions in list_comprehensions.py and use a descriptive name

    def list_comprehension(iterable):
        ...
    
    
    def is_even(number):
        return number % 2 == 0
    

    then reference it in get_even_numbers

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

    the test is still passing. I remove the second return statement and use the new function in get_odd_numbers

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

    the terminal still shows green, I remove the second `return statement`+


test_list_comprehensions_w_functions

red: make it fail

I add a test to show I can perform operations in a list comprehension

def test_list_comprehensions_w_functions(self):
    squares = []
    for item in self.iterable:
        squares.append(item*item)

    self.assertEqual(
        squares,
        [item for item in self.iterable]
    )

the terminal shows AssertionError

AssertionError: Lists differ: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 1[2036 chars]1124] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1[1432 chars] 318]
AssertionError: Lists differ: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 1[1775 chars]8961] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1[1247 chars] 281]
AssertionError: Lists differ: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 1[6028 chars]7489] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1[3927 chars] 817]
AssertionError: Lists differ: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 1[1852 chars]5264] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1[1302 chars] 292]

green: make it pass

I add the calculation to the list comprehension

self.assertEqual(
    squares,
    [item*item for item in self.iterable]
)

the test passes

refactor: make it better

  • I add another assertion

    self.assertEqual(
        squares,
        [item*item for item in self.iterable]
    )
    self.assertEqual(
        src.list_comprehensions.square(self.iterable),
        [item*item for item in self.iterable]
    )
    

    the terminal shows AttributeError

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

    I add the function

    def get_odd_numbers(iterable):
        ...
    
    
    def square(iterable):
        return [item**2 for item in iterable]
    

    the test passes

  • I add a function for the calculation I did 3 times in this test

    import unittest
    
    
    def process(number):
        return number ** 2
    

    I reference the new function in the test

    def test_list_comprehensions_w_functions(self):
        squares = []
        for item in self.iterable:
            # squares.append(item*item)
            squares.append(process(item))
    
        self.assertEqual(
            squares,
            # [item*item for item in self.iterable]
            [process(item) for item in self.iterable]
        )
        self.assertEqual(
            src.list_comprehensions.square(self.iterable),
            # [item*item for item in self.iterable]
            [process(item) for item in self.iterable]
        )
    

    the terminal still shows green. I remove the commented lines


test_list_comprehensions_w_functions_and_conditions

red: make it fail

I add a failing test

def test_list_comprehensions_w_functions_and_conditions(self):
    even_squares = []
    odd_squares = []
    for item in self.iterable:
        if condition(item):
            even_numbers.append(process(item))
        else:
            odd_numbers.append(process(item))

    self.assertEqual(
        even_squares,
        [item for item in self.iterable]
    )

the terminal shows AssertionError


AssertionError: Lists differ: [0, 4, 16, 36, 64, 100, 144, 196, 256, 324, 4[3142 chars]9316] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13[4120 chars] 855] AssertionError: Lists differ: [0, 4, 16, 36, 64, 100, 144, 196, 256, 324, 4[2750 chars]1536] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13[3630 chars] 757] AssertionError: Lists differ: [0, 4, 16, 36, 64, 100, 144, 196, 256, 324, 4[2574 chars]6944] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13[3405 chars] 712] AssertionError: Lists differ: [0, 4, 16, 36, 64, 100, 144, 196, 256, 324, 4[1934 chars]4704] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13[2605 chars] 552]

green: make it pass

I add a call to process and condition

self.assertEqual(
    even_squares,
    [process(item) for item in self.iterable if condition(item)]
)

the test passes

refactor: make it better

I add an assertion for odd_squares

self.assertEqual(
    odd_squares,
    [item for item in self.iterable]
)

the terminal shows AssertionError

AssertionError: Lists differ: [1, 9, 25, 49, 81, 121, 169, 225, 289, 361[1978 chars]6969] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,[2668 chars] 564]
AssertionError: Lists differ: [1, 9, 25, 49, 81, 121, 169, 225, 289, 361[356 chars]8225] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,[523 chars] 135]
AssertionError: Lists differ: [1, 9, 25, 49, 81, 121, 169, 225, 289, 361[629 chars]5369] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,[918 chars] 214]
AssertionError: Lists differ: [1, 9, 25, 49, 81, 121, 169, 225, 289, 361, 441] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]

I add the calls

self.assertEqual(
    odd_squares,
    [process(item) for item in self.iterable if not condition(item)]
)

the test passes


review

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

Would you like to test dictionaries?


data structures: List Comprehensions: tests and solutions