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_comprehensionsI open
makePythonTdd.shormakePythonTdd.ps1in the editorTip
Here is a quick way to open
makePythonTdd.shormakePythonTdd.ps1if you are using Visual Studio Codecode makePythonTdd.shon Windows without Windows Subsystem for Linux use
code makePythonTdd.ps1I change everywhere I have
liststo the name of this project1#!/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.pyAttention
on Windows without Windows Subsystem for Linux use
makePythonTdd.ps1NOTmakePythonTdd.sh1mkdir 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.pyI run the program in the terminal
./makePythonTdd.shAttention
on Windows without Windows Subsystem for Linux use
makePythonTdd.ps1NOTmakePythonTdd.sh./makePythonTdd.ps1the 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:7to open it in the editor-
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])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 minus1, in this case it goes from0to9The 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 )NameError: name 'src' is not definedI add NameError to the list of Exceptions seen
21# Exceptions seen 22# AssertionError 23# NameErrorI add an import statement at the top of the file
1import src.list_comprehensions 2import unittestthe 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# AttributeErrorI open
list_comprehensions.pyin the editorthen add a function to
list_comprehensions.py1def a_for_loop(): 2 return NoneTypeError: a_for_loop() takes 0 positional arguments but 1 was givenI add TypeError to the list of Exceptions seen in
test_list_comprehensions.py22# Exceptions seen 23# AssertionError 24# NameError 25# AttributeError 26# TypeErrorI add a name for the argument in
list_comprehensions.py1def a_for_loop(a_container): 2 return Nonethe 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.py1import random 2import src.list_comprehensions 3import unittestI 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 minus1, in this case the number can be anything from2to999this range object now goes from
0to anywhere between1and999because it also goes from the first number in parentheses to the second number minus1, and the second number in this case is a random integer that can be anything from2to999The 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.py1def 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
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_loop11 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.iterablethe 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_extend21 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.iterablethe 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_listare double what the test expects. I broke something by using the class attributesI change
a_listback21 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.iterablethe 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
TestListComprehensionsclass6class 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)
self.a_listto an empty listself.iterableto a range object that goes from0to anywhere between1and999
I try to use
self.a_listagain intest_making_a_list_w_extend22 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.iterablestill 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
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.py1def 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.
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 inself.iterableleaves a remainder of0when it is divided by2, 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.py8def 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 intest_list_comprehensions.py36def 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_numbersfunction inlist_comprehensions.pydef 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.py1import 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 seenNote
conditionis 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 == 0the terminal shows AssertionError
AssertionError: Lists differ: [0, 3, 6, 9, ...] != [0, 2, 4, 6, ...]I change the condition in
get_even_numbersinlist_comprehensions.py12def 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.py40 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 themI 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
conditionfunction56 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.py12def 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 != 0the 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_numbers16def 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_numbers16def 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_numbersfunction16def 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_even21def get_odd_numbers(numbers): 22 return list(filter(not is_even, numbers)) 23 return [number for number in numbers if not is_even(number)]TypeError: 'bool' object is not callableI 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.py25 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**yis how to writexraised to the power ofy\[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
squarefunction with a lambda function28def 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.py3import 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_processes69def 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 seenNote
processis 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
conditionfunction91 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
processfunction91 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
conditionwith 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
process95 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
processfunction85 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
close the project
I close
test_list_comprehensions.pyandlist_comprehensions.pyin the editorsI click in the terminal and exit the tests with ctrl+c on the keyboard, the terminal shows
.../pumping_pythonI am back in the
pumping_pythondirectory
Note
on Windows without Windows Subsystem for Linux
the terminal shows
(.venv) ...\pumping_python\list_comprehensionsI deactivate the virtual environment
deactivatethe terminal goes back to the command line,
(.venv)is no longer on the left side...\pumping_python\list_comprehensionsI change directory to the parent of
list_comprehensionscd ..the terminal shows
...\pumping_pythonI am back in the
pumping_pythondirectory
review
I ran tests to show I can make a list from an iterable with
list comprehensions where I can
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
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