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) oroption
(mac) on the keyboard and use the mouse to click ontests/test_list_comprehensions.py:7
to open it in the editorthen change
True
toFalse
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
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 initerable
leaves a remainder of0
when divided by2
%
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 inlist_comprehensions.py
and use a descriptive namedef 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
a for loop loop
the list constructor
I can use functions and conditions with list comprehensions to make a list with one line
Would you like to test dictionaries?