list comprehensions


what is a list comprehension?

A List Comprehension is a simple way to make a list from an iterable with one line


preview

These are the tests I have by the end of the chapter

  1import random
  2import src.list_comprehensions
  3import unittest
  4
  5
  6def process(number):
  7    return number ** 2
  8
  9
 10def condition(number):
 11    return number % 2 == 0
 12
 13
 14class TestListComprehensions(unittest.TestCase):
 15
 16    def setUp(self):
 17        self.a_list = []
 18        self.iterable = range(0, random.randint(2, 1000))
 19
 20    def test_making_a_list_w_a_for_loop(self):
 21        for number in self.iterable:
 22            self.a_list.append(number)
 23
 24        self.assertEqual(self.a_list, list(self.iterable))
 25        self.assertEqual(
 26            src.list_comprehensions.a_for_loop(self.iterable),
 27            self.a_list
 28        )
 29
 30    def test_making_a_list_w_extend(self):
 31        self.assertIsNone(self.a_list.extend(self.iterable))
 32        self.assertEqual(self.a_list, list(self.iterable))
 33
 34    def test_making_a_list_w_a_list_comprehension(self):
 35        self.assertEqual(
 36            list(self.iterable),
 37            [item for item in self.iterable]
 38        )
 39        self.assertEqual(
 40            src.list_comprehensions.a_list_comprehension(self.iterable),
 41            [item for item in self.iterable]
 42        )
 43
 44    def test_making_a_list_w_conditions(self):
 45        even_numbers, odd_numbers = [], []
 46        for item in self.iterable:
 47            if condition(item):
 48                even_numbers.append(item)
 49            else:
 50                odd_numbers.append(item)
 51
 52        self.assertEqual(
 53            even_numbers,
 54            [item for item in self.iterable if condition(item)]
 55        )
 56        self.assertEqual(
 57            src.list_comprehensions.get_even_numbers(self.iterable),
 58            [item for item in self.iterable if condition(item)]
 59        )
 60        self.assertEqual(
 61            odd_numbers,
 62            [item for item in self.iterable if not condition(item)]
 63        )
 64        self.assertEqual(
 65            src.list_comprehensions.get_odd_numbers(self.iterable),
 66            [item for item in self.iterable if not condition(item)]
 67        )
 68
 69    def test_making_a_list_w_processes(self):
 70        square_club = []
 71        for item in self.iterable:
 72            square_club.append(process(item))
 73
 74        self.assertEqual(
 75            square_club,
 76            [process(item) for item in self.iterable]
 77        )
 78        self.assertEqual(
 79            src.list_comprehensions.square(self.iterable),
 80            [process(item) for item in self.iterable]
 81        )
 82
 83    def test_making_a_list_w_processes_and_conditions(self):
 84        even_squares, odd_squares = [], []
 85        for item in self.iterable:
 86            item = process(item)
 87            if condition(item):
 88                even_squares.append(item)
 89            else:
 90                odd_squares.append(item)
 91
 92        self.assertEqual(
 93            even_squares,
 94            [process(item) for item in self.iterable if condition(item)]
 95        )
 96        self.assertEqual(
 97            odd_squares,
 98            [process(item) for item in self.iterable if not condition(item)]
 99        )
100
101
102# Exceptions seen
103# AssertionError
104# NameError
105# AttributeError
106# TypeError

questions about list comprehensions

Here are questions you can answer after going through this chapter


start the project

  • I name this project list_comprehensions

  • I open makePythonTdd.sh or makePythonTdd.ps1 in the editor

    Tip

    Here is a quick way to open makePythonTdd.sh or makePythonTdd.ps1 if you are using Visual Studio Code

    code makePythonTdd.sh
    

    on Windows without Windows Subsystem for Linux use

    code makePythonTdd.ps1
    
  • I change everywhere I have lists to the name of this project

     1#!/bin/bash
     2mkdir list_comprehensions
     3cd list_comprehensions
     4mkdir src
     5touch src/list_comprehensions.py
     6mkdir tests
     7touch tests/__init__.py
     8
     9echo "import unittest
    10
    11
    12class TestListComprehensions(unittest.TestCase):
    13
    14    def test_failure(self):
    15        self.assertFalse(True)
    16
    17
    18# Exceptions seen
    19# AssertionError
    20" > tests/test_list_comprehensions.py
    

    Attention

    on Windows without Windows Subsystem for Linux use makePythonTdd.ps1 NOT makePythonTdd.sh

     1mkdir list_comprehensions
     2cd list_comprehensions
     3mkdir src
     4New-Item src/list_comprehensions.py
     5mkdir tests
     6New-Item tests/__init__.py
     7
     8"import unittest
     9
    10
    11class TestListComprehensions(unittest.TestCase):
    12
    13    def test_failure(self):
    14        self.assertFalse(True)
    15
    16# Exceptions seen
    17# AssertionError
    18" | Out-File tests/test_list_comprehensions.py
    
  • I run the program in the terminal

    ./makePythonTdd.sh
    

    Attention

    on Windows without Windows Subsystem for Linux use makePythonTdd.ps1 NOT makePythonTdd.sh

    ./makePythonTdd.ps1
    

    the terminal shows AssertionError

    ================================= FAILURES =================================
    _________________________ TestListComprehensions.test_failure __________________________
    
    self = <tests.test_list_comprehensions.TestListComprehensions testMethod=test_failure>
    
        def test_failure(self):
    >       self.assertFalse(True)
    E       AssertionError: True is not false
    
    tests/test_list_comprehensions.py:7: AssertionError
    ================================ short test summary info =================================
    FAILED tests/test_list_comprehensions.py::TestListComprehensions::test_failure - AssertionError: True is not false
    ============================ 1 failed in X.YZs =============================
    
  • I hold ctrl (Windows/Linux) or option/command (MacOS) on the keyboard and use the mouse to click on tests/test_list_comprehensions.py:7 to open it in the editor

  • then I change True to False

    7        self.assertFalse(False)
    

    the test passes


test_making_a_list_w_a_for_loop

I can make a list with list() or with square brackets ([]), I can also add items one at a time with the append method


RED: make it fail


I change test_failure to test_making_a_list_w_a_for_loop to show what happens when I use the append method with more than one item

 4class TestListComprehensions(unittest.TestCase):
 5
 6    def test_making_a_list_w_a_for_loop(self):
 7        a_list = []
 8        a_list.append(0)
 9        a_list.append(1)
10        a_list.append(2)
11        a_list.append(3)
12        a_list.append(4)
13        a_list.append(5)
14        a_list.append(6)
15        a_list.append(7)
16        a_list.append(8)
17        a_list.append(9)
18        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 change the expectation to match

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

the test passes


REFACTOR: make it better

what is a for loop?

I just called the append method 10 times in a row, the only things that changed were the numbers I added to the list, there is a better way. I can use a for loop.

A for loop is a way to repeat the same command over an iterable (a collection of items)

  • I add a for loop to the test

     6    def test_making_a_list_w_a_for_loop(self):
     7        a_list = []
     8        # a_list.append(0)
     9        # a_list.append(1)
    10        # a_list.append(2)
    11        # a_list.append(3)
    12        # a_list.append(4)
    13        # a_list.append(5)
    14        # a_list.append(6)
    15        # a_list.append(7)
    16        # a_list.append(8)
    17        # a_list.append(9)
    18
    19        for number in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9):
    20            a_list.append(number)
    21
    22        self.assertEqual(a_list, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    

    the test is still green

  • I remove the commented lines. This for loop removed 10 lines of code and I can use it for any number of items, the other way gets busy very quickly once I have to add more numbers

     6    def test_making_a_list_w_a_for_loop(self):
     7        a_list = []
     8
     9        for number in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9):
    10            a_list.append(number)
    11
    12        self.assertEqual(a_list, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    
    • for number in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9): goes over every number in the tuple

    • a_list.append(number) adds each number from the tuple to a_list

  • Python has an iterable I can use to make a sequence of numbers, it is called the range object. I add it to the for loop

     6    def test_making_a_list_w_a_for_loop(self):
     7        a_list = []
     8
     9        # for number in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9):
    10        for number in range(0, 10):
    11            a_list.append(number)
    12
    13        self.assertEqual(a_list, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    

    the test is still green

  • I remove the commented line

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

    range(0, 10) makes a range object that goes from the first number in the parentheses to the second number minus 1, in this case it goes from 0 to 9

  • The for loop is simpler than calling the append method for each item I want to add to a list, but there is an easier way. I can do the same thing with list()

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

    the terminal shows AssertionError

    AssertionError: Lists differ: [] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
  • I add the range object to the assertion

    13        self.assertEqual(list(range(0, 10)), a_list)
    

    the test passes

  • I add a variable to remove the duplication of the range object

     6    def test_making_a_list_w_a_for_loop(self):
     7        a_list = []
     8        iterable = range(0, 10)
     9
    10        for number in iterable:
    11            a_list.append(number)
    12
    13        self.assertEqual(a_list, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    14        self.assertEqual(list(iterable), a_list)
    

    the test is still green

  • I add another assertion to practice writing a for loop

    14    self.assertEqual(a_list, list(iterable))
    15    self.assertEqual(
    16        src.list_comprehensions.a_for_loop(iterable),
    17        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    18    )
    

    the terminal shows NameError

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

    21# Exceptions seen
    22# AssertionError
    23# NameError
    
  • I add an import statement at the top of the file

    1import src.list_comprehensions
    2import unittest
    

    the terminal shows AttributeError

    AttributeError: module 'src.list_comprehensions' has no attribute 'a_for_loop'
    
  • I add AttributeError to the list of Exceptions seen

    22# Exceptions seen
    23# AssertionError
    24# NameError
    25# AttributeError
    
  • I open list_comprehensions.py in the editor

  • then add a function to list_comprehensions.py

    1def a_for_loop():
    2    return None
    

    the terminal shows TypeError

    TypeError: a_for_loop() takes 0 positional arguments but 1 was given
    
  • I add TypeError to the list of Exceptions seen in test_list_comprehensions.py

    22# Exceptions seen
    23# AssertionError
    24# NameError
    25# AttributeError
    26# TypeError
    
  • I add a name for the argument in list_comprehensions.py

    1def a_for_loop(a_container):
    2    return None
    

    the terminal shows AssertionError

    AssertionError: None != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
  • I change the return statement to match

    1def a_for_loop(a_container):
    2    return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    the test passes. The problem with this solution is that it will fail when I have a range of numbers that is different. I want a solution that can take any iterable and return the right list

  • I import the random module to use random numbers in test_list_comprehensions.py

    1import random
    2import src.list_comprehensions
    3import unittest
    
  • I change the second value in the parentheses for the range object to a random number

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

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1, 2, 3, ...] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
    • random.randint(2, 1000) gives me a random integer between the first number in parentheses and the second number minus 1, in this case the number can be anything from 2 to 999

    • this range object now goes from 0 to anywhere between 1 and 999 because it also goes from the first number in parentheses to the second number minus 1, and the second number in this case is a random integer that can be anything from 2 to 999

    • The values change every time the test runs

  • I change the expectation in the first assertion

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

    the test passes

  • I remove the second line because it is now a duplicate

    12        for number in iterable:
    13            a_list.append(number)
    14
    15        self.assertEqual(a_list, list(iterable))
    16        self.assertEqual(
    17            src.list_comprehensions.a_for_loop(iterable),
    18            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    19        )
    
  • I change the expectation of the second assertion

    15        self.assertEqual(a_list, list(iterable))
    16        self.assertEqual(
    17            src.list_comprehensions.a_for_loop(iterable),
    18            a_list
    19        )
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] != [0, 1, 2, 3, ...]
    
  • I change the function in list_comprehensions.py

    1def a_for_loop(a_container):
    2    result = []
    3    for stuff in a_container:
    4        result.append(stuff)
    5    return result
    6    return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    the test passes

  • I remove the second return statement

I know how to make a list with a for loop

Why did I use a for loop when I can use list() to do the same thing with less characters? Sometimes one is better than the other


test_making_a_list_w_extend

I can also use the extend method to make a list from an iterable


RED: make it fail


I add a new test

18              a_list
19          )
20
21      def test_making_a_list_w_extend(self):
22          a_list = []
23          iterable = range(0, random.randint(2, 1000))
24          self.assertIsNone(a_list.extend())
25
26
27  # Exceptions seen

the terminal shows TypeError

TypeError: list.extend() takes exactly one argument (0 given)

GREEN: make it pass


I add the iterable

24        self.assertIsNone(a_list.extend(iterable))

the terminal shows green again, the extend method returns None


REFACTOR: make it better


  • I add another assertion to see what changed in the list

    24        self.assertIsNone(a_list.extend(iterable))
    25        self.assertEqual(a_list, list())
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1, 2, 3, ...] != []
    
  • I change the expectation

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

    the test passes. extend uses less lines than the for loop but is not better than list()

  • I made the same variables twice, one for the empty list and one for the iterable, I add class attributes (variables) to remove the duplication

     6class TestListComprehensions(unittest.TestCase):
     7
     8    a_list = []
     9    iterable = range(0, random.randint(2, 1000))
    10
    11    def test_making_a_list_w_a_for_loop(self):
    
  • then I use them in test_making_a_list_w_a_for_loop

    11    def test_making_a_list_w_a_for_loop(self):
    12        # a_list = []
    13        # iterable = range(0, random.randint(2, 1000))
    14        a_list = self.a_list
    15        iterable = self.iterable
    

    the test is still green

  • I remove the commented lines and use the class attributes directly

    11    def test_making_a_list_w_a_for_loop(self):
    12        # a_list = self.a_list
    13        # iterable = self.iterable
    14
    15        for number in self.iterable:
    16            self.a_list.append(number)
    17
    18        self.assertEqual(self.a_list, list(self.iterable))
    19        self.assertEqual(
    20            src.list_comprehensions.a_for_loop(self.iterable),
    21            self.a_list
    22        )
    

    still green

  • I remove the commented lines

    11    def test_making_a_list_w_a_for_loop(self):
    12        for number in self.iterable:
    13            self.a_list.append(number)
    14
    15        self.assertEqual(self.a_list, list(self.iterable))
    16        self.assertEqual(
    17            src.list_comprehensions.a_for_loop(self.iterable),
    18            self.a_list
    19        )
    
  • I make the same change in test_making_a_list_w_extend

    21    def test_making_a_list_w_extend(self):
    22        # a_list = []
    23        # iterable = range(0, random.randint(2, 1000))
    24        a_list = self.a_list
    25        iterable = self.iterable
    

    the terminal shows AssertionError

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

    the values in a_list are double what the test expects. I broke something by using the class attributes

  • I change a_list back

    21    def test_making_a_list_w_extend(self):
    22        a_list = []
    23        # iterable = range(0, random.randint(2, 1000))
    24        # a_list = self.a_list
    25        iterable = self.iterable
    

    the test is green again

how to reset class attributes (variables) for every test

The problem is that both tests append to self.a_list. I was making an empty list for each test before, I need a better way. The unittest.TestCase class has a method I can use to make sure the class attributes are always reset at the beginning of the test, so that the values are new for each test.

  • I add it to the TestListComprehensions class

     6class TestListComprehensions(unittest.TestCase):
     7
     8    def setUp(self):
     9        self.a_list = []
    10        self.iterable = range(0, random.randint(2, 1000))
    11
    12    def test_making_a_list_w_a_for_loop(self):
    

    the test is still green.

    The unittest.TestCase.setUp method runs before every test, in this case it sets the following class attributes (variables)

  • I try to use self.a_list again in test_making_a_list_w_extend

    22    def test_making_a_list_w_extend(self):
    23        # a_list = []
    24        # iterable = range(0, random.randint(2, 1000))
    25        a_list = self.a_list
    26        iterable = self.iterable
    

    still green

  • I remove the commented lines and use the class attributes directly

    22    def test_making_a_list_w_extend(self):
    23        # a_list = self.a_list
    24        # iterable = self.iterable
    25        self.assertIsNone(self.a_list.extend(self.iterable))
    26        self.assertEqual(self.a_list, list(self.iterable))
    

    green

  • I remove the commented lines

    22    def test_making_a_list_w_extend(self):
    23        self.assertIsNone(self.a_list.extend(self.iterable))
    24        self.assertEqual(self.a_list, list(self.iterable))
    25
    26
    27# Exceptions seen
    

I know how to make a list with the extend method


test_making_a_list_w_a_list_comprehension

Time for the title of this chapter. I can use a list comprehension to make a list from an iterable with one line


RED: make it fail


I add a failing test

24        self.assertEqual(self.a_list, list(self.iterable))
25
26    def test_making_a_list_w_a_list_comprehension(self):
27        self.assertEqual(
28            list(self.iterable),
29            []
30        )
31
32
33# Exceptions seen

the terminal shows AssertionError

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

GREEN: make it pass


I add a list comprehension as the expectation

27        self.assertEqual(
28            list(self.iterable),
29            [item for item in self.iterable]
30        )

the test passes. The list comprehension is like the for loop without the append line


REFACTOR: make it better


  • I add another assertion for practice

    27        self.assertEqual(
    28            list(self.iterable),
    29            [item for item in self.iterable]
    30        )
    31        self.assertEqual(
    32            src.list_comprehensions.a_list_comprehension(self.iterable),
    33            [item for item in self.iterable]
    34        )
    

    the terminal shows AttributeError

    AttributeError: module 'src.list_comprehensions' has no attribute 'a_list_comprehension'
    
  • I add the function to list_comprehensions.py

    1def a_for_loop(a_container):
    2    result = []
    3    for stuff in a_container:
    4        result.append(stuff)
    5    return result
    6
    7
    8def a_list_comprehension(a_collection):
    9    return [element for element in a_collection]
    

    the test passes

  • I made 2 functions that do the same thing - one that uses a for loop and another that uses a list comprehension

    result = []
    for stuff in a_container:
        result.append(stuff)
    

    and

    [element for element in a_collection]
    

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

    with the list comprehension, I do all the steps in one line, and still none of the other ways are better than using list(), yet.

I know how to use a list comprehension to make a list


test_making_a_list_w_conditions

What if I had to make a list from an iterable based on a condition?


RED: make it fail


I add a failing test to test_list_comprehensions.py

33            [item for item in self.iterable]
34        )
35
36    def test_making_a_list_w_conditions(self):
37        even_numbers = []
38        for item in self.iterable:
39            if item % 2 == 0:
40                even_numbers.append(item)
41
42        self.assertEqual(
43            even_numbers,
44            list(self.iterable)
45        )
46
47
48# Exceptions seen

the terminal shows AssertionError

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

the numbers on the left are even numbers from the iterable, and the numbers on the right are every number in the iterable

  • if item % 2 == 0: checks if the number in self.iterable leaves a remainder of 0 when it is divided by 2, this is the rule for even numbers

  • % is the modulo operator, which divides the number on the left by the number on the right and returns a remainder, there is a test for it in test_the_modulo_operation


GREEN: make it pass


How can I make the even_numbers list with list() without changing the iterable? Since I can make the list with a for loop, I can do it with a list comprehension. I change the expectation

42        self.assertEqual(
43            even_numbers,
44            [item for item in self.iterable]
45        )

the terminal still shows AssertionError

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

I add the if statement

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

the test passes. This is a case where a list comprehension or a for loop is better than using list()


REFACTOR: make it better


  • I add another assertion for practice

    44            [item for item in self.iterable if item % 2 == 0]
    45        )
    46        self.assertEqual(
    47            src.list_comprehensions.get_even_numbers(self.iterable),
    48            [item for item in self.iterable if item % 2 == 0]
    49        )
    

    the terminal shows AttributeError

    AttributeError: module 'src.list_comprehensions' has no attribute 'get_even_numbers'
    
  • I add a function to list_comprehensions.py

     8def a_list_comprehension(a_collection):
     9    return [element for element in a_collection]
    10
    11
    12def get_even_numbers(numbers):
    13    return [number for number in numbers if number % 2 == 0]
    

    the test passes

  • I wrote the same condition in the test 3 times. I have to make the same change everywhere I wrote it if I want to change it. Let us say the new condition is that the number should be divisible by 3. I make the change in test_list_comprehensions.py

    36def test_making_a_list_w_conditions(self):
    37    even_numbers = []
    38    for item in self.iterable:
    39        if item % 3 == 0:
    40            even_numbers.append(item)
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 3, 6, 9, ...] != [0, 2, 4, 6, ...]
    
  • I change the condition in the list comprehension of the first assertion

    42        self.assertEqual(
    43            even_numbers,
    44            [item for item in self.iterable if item % 3 == 0]
    45        )
    

    the terminal shows green

  • I change the condition in the list comprehension of the second assertion

    46        self.assertEqual(
    47            src.list_comprehensions.get_even_numbers(self.iterable),
    48            [item for item in self.iterable if item % 3 == 0]
    49        )
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 2, 4, 6, ...] != [0, 3, 6, 9, ...]
    
  • I change the condition in the get_even_numbers function in list_comprehensions.py

    def get_even_numbers(numbers):
        return [number for number in iterable if number % 3 == 0]
    

    the test passes

  • I add a function to remove the duplication in test_list_comprehensions.py

     1import random
     2import src.list_comprehensions
     3import unittest
     4
     5
     6def condition(number):
     7    return number % 3 == 0
     8
     9
    10class TestListComprehensions(unittest.TestCase):
    
  • I change the if statement in the test to call the new function

    40    def test_making_a_list_w_conditions(self):
    41        even_numbers = []
    42        for item in self.iterable:
    43            # if item % 3 == 0:
    44            if condition(item):
    45                even_numbers.append(item)
    

    the terminal still shows green

  • I remove the commented line and use the new function in the first assertion

    40    def test_making_a_list_w_conditions(self):
    41        even_numbers = []
    42        for item in self.iterable:
    43            if condition(item):
    44                even_numbers.append(item)
    45
    46        self.assertEqual(
    47            even_numbers,
    48            # [item for item in self.iterable if item % 3 == 0]
    49            [item for item in self.iterable if condition(item)]
    50        )
    

    still green

  • I remove the commented line and use the new function in the next assertion

    46        self.assertEqual(
    47            even_numbers,
    48            [item for item in self.iterable if condition(item)]
    49        )
    50        self.assertEqual(
    51            src.list_comprehensions.get_even_numbers(self.iterable),
    52            # [item for item in self.iterable if item % 3 == 0]
    53            [item for item in self.iterable if condition(item)]
    54        )
    

    the terminal still shows green

  • I remove the commented line

    50        self.assertEqual(
    51            src.list_comprehensions.get_even_numbers(self.iterable),
    52            [item for item in self.iterable if condition(item)]
    53        )
    54
    55
    56# Exceptions seen
    

    Note

    condition is NOT a good name for a function because it is general, it does not tell what it does. I use it to show that I think of a list comprehension as [item for item in iterable if condition]

  • I change the condition in the new function

    6def condition(number):
    7    return number % 2 == 0
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 3, 6, 9, ...] != [0, 2, 4, 6, ...]
    
  • I change the condition in get_even_numbers in list_comprehensions.py

    12def get_even_numbers(numbers):
    13    return [number for number in numbers if number % 2 == 0]
    

    the test passes. Adding the function adds extra lines, and makes managing the code easier because I now only have to make a change in one place in the test when I need

  • I add a new empty list to test another condition in test_list_comprehensions.py

    40    def test_making_a_list_w_conditions(self):
    41        even_numbers, odd_numbers = [], []
    42        for item in self.iterable:
    

    even_numbers, odd_numbers = [], [] makes 2 empty lists and names them

  • I add an else clause to the if statement in the for loop

    41        even_numbers, odd_numbers = [], []
    42        for item in self.iterable:
    43            if condition(item):
    44                even_numbers.append(item)
    45            else:
    46                odd_numbers.append(item)
    
  • I add an assertion

    54            [item for item in self.iterable if condition(item)]
    55        )
    56        self.assertEqual(
    57            odd_numbers,
    58            [item for item in self.iterable]
    59        )
    

    the terminal shows AssertionError

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

    the numbers on the left are odd numbers from the iterable, and the numbers on the right are every number in the iterable

  • I add the if statement to the assertion with logical negation(NOT) and the condition function

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

    the test passes

  • I add another assertion

    58            [item for item in self.iterable if not condition(item)]
    59        )
    60        self.assertEqual(
    61            src.list_comprehensions.get_odd_numbers(self.iterable),
    62            [item for item in self.iterable if not condition(item)]
    63        )
    

    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 to list_comprehensions.py

    12def get_even_numbers(numbers):
    13    return [number for number in numbers if number % 2 == 0]
    14
    15
    16def get_odd_numbers(numbers):
    17    return [number for number in numbers if number % 2 != 0]
    

    the test passes

  • These two conditions look the same

    if number % 2 == 0
    if number % 2 != 0
    

    the difference is the equality symbols == and !=

    I add a function to remove the duplication

     8def a_list_comprehension(a_collection):
     9    return [element for element in a_collection]
    10
    11
    12def is_even(number):
    13    return number % 2 == 0
    14
    15
    16def get_even_numbers(numbers):
    
  • I call the function in get_even_numbers

    16def get_even_numbers(numbers):
    17    return [number for number in numbers if is_even(number)]
    18    return [number for number in numbers if number % 2 == 0]
    

    the test is still green

  • I remove the second return statement and use logical negation (NOT) with the new function in get_odd_numbers

    16def get_even_numbers(numbers):
    17    return [number for number in numbers if is_even(number)]
    18
    19
    20def get_odd_numbers(numbers):
    21    return [number for number in numbers if not is_even(number)]
    22    return [number for number in numbers if number % 2 != 0]
    

    the test is still passing

  • I remove the second return statement

  • There is a Python Built-in Function I can use to do the same thing as this list comprehension, it is called filter, which is a way to say “make a list based on a condition” or “give me only the things that meet this condition”. I try it in the get_even_numbers function

    16def get_even_numbers(numbers):
    17    return filter(is_even, numbers)
    18    return [number for number in numbers if is_even(number)]
    

    the terminal shows AssertionError

    AssertionError: <filter object at 0xffffab1c23d4> != [0, 2, ...]
    

    I have to make the filter object a list

  • I put it in list()

    17    return list(filter(is_even, numbers))
    

    the test passes. Which do you like better: filter or the list comprehension?

  • filter takes a function as input, which means I would have to make one for odd numbers like is_even

    21def get_odd_numbers(numbers):
    22    return list(filter(not is_even, numbers))
    23    return [number for number in numbers if not is_even(number)]
    

    the terminal shows TypeError

    TypeError: 'bool' object is not callable
    
  • I do not want to write a new function. I can use a lambda function which is a function with no name to make it work

    21def get_odd_numbers(numbers):
    22    return list(filter(lambda number: not is_even(number), numbers))
    23    return [number for number in numbers if not is_even(number)]
    

    the test passes, but this is not sexy

  • another option is to use the filterfalse method from the itertools module, it is part of the Python standard library and needs an import statement

    21def get_odd_numbers(numbers):
    22    import itertools
    23    return list(itertools.filterfalse(is_even, numbers))
    24    return list(filter(lambda number: not is_even(number), numbers))
    25    return [number for number in numbers if not is_even(number)]
    

    the test is still green

    filterfalse returns the items from the iterable that do not meet the condition of the function

I like the list comprehension from the options, it is simpler, easier to remember, and does not need any import statements. I know how to filter a list


test_making_a_list_w_processes

I can also do other operations with a list comprehension that are not append


RED: make it fail


I add a failing test to test_list_comprehensions.py

62            [item for item in self.iterable if not condition(item)]
63        )
64
65    def test_making_a_list_w_processes(self):
66        square_club = []
67        for item in self.iterable:
68            square_club.append(item*item)
69
70        self.assertEqual(
71            square_club,
72            [item for item in self.iterable]
73        )
74
75
76# Exceptions seen

the terminal shows AssertionError

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

the numbers on the left are squares of the numbers on the right


GREEN: make it pass


I add the calculation to the list comprehension

70        self.assertEqual(
71            square_club,
72            [item*item for item in self.iterable]
73        )

the test passes


REFACTOR: make it better


  • I add another assertion

    72            [item*item for item in self.iterable]
    73        )
    74        self.assertEqual(
    75            src.list_comprehensions.square(self.iterable),
    76            [item*item for item in self.iterable]
    77        )
    

    the terminal shows AttributeError

    AttributeError: module 'src.list_comprehensions' has no attribute 'square'
    
  • I add the function to list_comprehensions.py

    25    return [number for number in numbers if not is_even(number)]
    26
    27
    28def square(numbers):
    29    return [number**2 for number in numbers]
    

    the test passes

    x**y is how to write x raised to the power of y

    \[x ^ y\]
  • There is a Python Built-in Function that I can use to process a list, just like filter, this one is called map, I add it to the square function with a lambda function

    28def square(numbers):
    29    return map(lambda number: number**2, numbers)
    30    return [number**2 for number in numbers]
    

    the terminal shows AssertionError

    AssertionError: <map object at 0xffffa1b234c5> != [0, 1, 4, 9, ...]
    

    I have to change the map object to a list

  • I add list()

    28def square(numbers):
    29    return list(map(lambda number: number**2, numbers))
    30    return [number**2 for number in numbers]
    

    the test passes. map takes in a function as input. I still like the list comprehension better, it is simpler and easier to read

  • I add a function for the calculation I just did 3 times in test_list_comprehensions.py

     3import unittest
     4
     5
     6def process(number):
     7    return number ** 2
     8
     9
    10def condition(number):
    
  • I call the new function in the for loop in test_making_a_list_w_processes

    69def test_making_a_list_w_processes(self):
    70    square_club = []
    71    for item in self.iterable:
    72        # square_club.append(item*item)
    73        square_club.append(process(item))
    

    the test is still green

  • I remove the commented line and call the function in the first assertion

    71        for item in self.iterable:
    72            square_club.append(process(item))
    73
    74        self.assertEqual(
    75            square_club,
    76            # [item*item for item in self.iterable]
    77            [process(item) for item in self.iterable]
    78        )
    

    still green

  • I remove the commented line and call the function in the second assertion

    75            square_club,
    76            [process(item) for item in self.iterable]
    77        )
    78        self.assertEqual(
    79            src.list_comprehensions.square(self.iterable),
    80            # [item*item for item in self.iterable]
    81            [process(item) for item in self.iterable]
    82        )
    

    the terminal still shows green

  • I remove the commented line

    78        self.assertEqual(
    79            src.list_comprehensions.square(self.iterable),
    80            [process(item) for item in self.iterable]
    81        )
    82
    83
    84# Exceptions seen
    

    Note

    process is NOT a good name for a function because it is general, it does not tell what it does. I use it to show that I think of a list comprehension as [process(item) for item in iterable]

I know how to process(transform) a list with list comprehensions


test_making_a_list_w_processes_and_conditions

I can use both processes and conditions in a list comprehension


RED: make it fail


I add a failing test

80            [process(item) for item in self.iterable]
81        )
82
83    def test_making_a_list_w_processes_and_conditions(self):
84        even_squares, odd_squares = [], []
85        for item in self.iterable:
86            if condition(item):
87                even_squares.append(process(item))
88            else:
89                odd_squares.append(process(item))
90
91        self.assertEqual(
92            even_squares,
93            [item for item in self.iterable]
94        )
95
96
97# Exceptions seen

the terminal shows AssertionError

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

the numbers on the left are the squares of the even numbers from the right


GREEN: make it pass


  • I add a call to the condition function

    91        self.assertEqual(
    92            even_squares,
    93            [item for item in self.iterable if condition(item)]
    94        )
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 4, 16, 36, ...] != [0, 2, 4, 6, ...]
    

    the numbers on the left are squares of the numbers on the right

  • I add a call to the process function

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

    the test passes


REFACTOR: make it better


  • I add another assertion

    93            [process(item) for item in self.iterable if condition(item)]
    94        )
    95        self.assertEqual(
    96            odd_squares,
    97            [item for item in self.iterable]
    98        )
    

    the terminal shows AssertionError

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

    the numbers on the left are the squares of the odd numbers from the right

  • I add a call to condition with logical negation(NOT)

    95        self.assertEqual(
    96            odd_squares,
    97            [item for item in self.iterable if not condition(item)]
    98        )
    

    the terminal shows AssertionError

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

    the numbers on the left are squares of the numbers on the right

  • I add a call to process

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

    the test passes

  • I add a variable to the for loop to remove the duplication of the call to the process function

    85        for item in self.iterable:
    86            item = process(item)
    87            if condition(item):
    88                # even_squares.append(process(item))
    89                even_squares.append(item)
    90            else:
    91                # odd_squares.append(process(item))
    92                odd_squares.append(item)
    

    the test is still green

  • I remove the commented lines

    85        for item in self.iterable:
    86            item = process(item)
    87            if condition(item):
    88                even_squares.append(item)
    89            else:
    90                odd_squares.append(item)
    

    still green

I know how to use list comprehensions to make a list based on conditions (filter) with processes (transform)


close the project

  • I close test_list_comprehensions.py and list_comprehensions.py in the editors

  • I click in the terminal and exit the tests with ctrl+c on the keyboard, the terminal shows

    .../pumping_python
    

    I am back in the pumping_python directory

Note

on Windows without Windows Subsystem for Linux

  • the terminal shows

    (.venv) ...\pumping_python\list_comprehensions
    
  • I deactivate the virtual environment

    deactivate
    

    the terminal goes back to the command line, (.venv) is no longer on the left side

    ...\pumping_python\list_comprehensions
    
  • I change directory to the parent of list_comprehensions

    cd ..
    

    the terminal shows

    ...\pumping_python
    

    I am back in the pumping_python directory


review

I ran tests to show I can make a list from an iterable with

I can use functions and conditions with list comprehensions to make a list with one line. I think of it as [process(item) for item in iterable if condition/NOT condition]

I can also do this with dictionaries, it is called a dict comprehension and the syntax is any mix of the following

{
    a_process(key): another_process(value)
    for key/value in iterable
    if condition/not condition
}

How many questions can you answer after going through this chapter?


code from the chapter

Do you want to see all the CODE I typed in this chapter?


what is next?

you know

would you like to test the calculator with your new found magic powers?


rate pumping python

If this has been a 7 star experience for you, please CLICK HERE to leave a 5 star review of pumping python. It helps other people get into the book too