4.7. List Comprehensions Within List Comprehensions

List comprehensions are a concise way to create complex list values. For example, to create a list of strings of digits for the numbers 0 through 100, excluding all multiples of 5, you’d typically need a for loop:

>>> spam = []
>>> for number in range(100):
>>>
>>>     if number % 5 != 0:
>>>
>>>         spam.append(str(number))
>>>
>>> spam
['1',
 '2',
 '3',
 '4',
 '6',
 '7',
 '8',
 '9',
 '11',
 '12',
 '13',
 '14',
 '16',
 '17',
 '18',
 '19',
 '21',
 '22',
 '23',
 '24',
 '26',
 '27',
 '28',
 '29',
 '31',
 '32',
 '33',
 '34',
 '36',
 '37',
 '38',
 '39',
 '41',
 '42',
 '43',
 '44',
 '46',
 '47',
 '48',
 '49',
 '51',
 '52',
 '53',
 '54',
 '56',
 '57',
 '58',
 '59',
 '61',
 '62',
 '63',
 '64',
 '66',
 '67',
 '68',
 '69',
 '71',
 '72',
 '73',
 '74',
 '76',
 '77',
 '78',
 '79',
 '81',
 '82',
 '83',
 '84',
 '86',
 '87',
 '88',
 '89',
 '91',
 '92',
 '93',
 '94',
 '96',
 '97',
 '98',
 '99']

Alternatively, you can create this same list in a single line of code by using the list comprehension syntax:

>>> spam = [str(number) for number in range(100) if number % 5 != 0]
>>> spam
['1',
 '2',
 '3',
 '4',
 '6',
 '7',
 '8',
 '9',
 '11',
 '12',
 '13',
 '14',
 '16',
 '17',
 '18',
 '19',
 '21',
 '22',
 '23',
 '24',
 '26',
 '27',
 '28',
 '29',
 '31',
 '32',
 '33',
 '34',
 '36',
 '37',
 '38',
 '39',
 '41',
 '42',
 '43',
 '44',
 '46',
 '47',
 '48',
 '49',
 '51',
 '52',
 '53',
 '54',
 '56',
 '57',
 '58',
 '59',
 '61',
 '62',
 '63',
 '64',
 '66',
 '67',
 '68',
 '69',
 '71',
 '72',
 '73',
 '74',
 '76',
 '77',
 '78',
 '79',
 '81',
 '82',
 '83',
 '84',
 '86',
 '87',
 '88',
 '89',
 '91',
 '92',
 '93',
 '94',
 '96',
 '97',
 '98',
 '99']

Python also has syntax for set comprehensions and dictionary comprehensions:

>>> spam = {str(number) for number in range(100) if number % 5 != 0}
>>> spam
{'1',
 '11',
 '12',
 '13',
 '14',
 '16',
 '17',
 '18',
 '19',
 '2',
 '21',
 '22',
 '23',
 '24',
 '26',
 '27',
 '28',
 '29',
 '3',
 '31',
 '32',
 '33',
 '34',
 '36',
 '37',
 '38',
 '39',
 '4',
 '41',
 '42',
 '43',
 '44',
 '46',
 '47',
 '48',
 '49',
 '51',
 '52',
 '53',
 '54',
 '56',
 '57',
 '58',
 '59',
 '6',
 '61',
 '62',
 '63',
 '64',
 '66',
 '67',
 '68',
 '69',
 '7',
 '71',
 '72',
 '73',
 '74',
 '76',
 '77',
 '78',
 '79',
 '8',
 '81',
 '82',
 '83',
 '84',
 '86',
 '87',
 '88',
 '89',
 '9',
 '91',
 '92',
 '93',
 '94',
 '96',
 '97',
 '98',
 '99'}
>>> spam = {str(number): number for number in range(100) if number % 5 != 0}
>>> spam
{'1': 1,
 '2': 2,
 '3': 3,
 '4': 4,
 '6': 6,
 '7': 7,
 '8': 8,
 '9': 9,
 '11': 11,
 '12': 12,
 '13': 13,
 '14': 14,
 '16': 16,
 '17': 17,
 '18': 18,
 '19': 19,
 '21': 21,
 '22': 22,
 '23': 23,
 '24': 24,
 '26': 26,
 '27': 27,
 '28': 28,
 '29': 29,
 '31': 31,
 '32': 32,
 '33': 33,
 '34': 34,
 '36': 36,
 '37': 37,
 '38': 38,
 '39': 39,
 '41': 41,
 '42': 42,
 '43': 43,
 '44': 44,
 '46': 46,
 '47': 47,
 '48': 48,
 '49': 49,
 '51': 51,
 '52': 52,
 '53': 53,
 '54': 54,
 '56': 56,
 '57': 57,
 '58': 58,
 '59': 59,
 '61': 61,
 '62': 62,
 '63': 63,
 '64': 64,
 '66': 66,
 '67': 67,
 '68': 68,
 '69': 69,
 '71': 71,
 '72': 72,
 '73': 73,
 '74': 74,
 '76': 76,
 '77': 77,
 '78': 78,
 '79': 79,
 '81': 81,
 '82': 82,
 '83': 83,
 '84': 84,
 '86': 86,
 '87': 87,
 '88': 88,
 '89': 89,
 '91': 91,
 '92': 92,
 '93': 93,
 '94': 94,
 '96': 96,
 '97': 97,
 '98': 98,
 '99': 99}

A set comprehension 1 uses braces instead of square brackets and produces a set value. A dictionary comprehension 2 produces a dictionary value and uses a colon to separate the key and value in the comprehension. These comprehensions are concise and can make your code more read- able. But notice that the comprehensions produce a list, set, or dictionary based on an iterable object (in this example, the range object returned by the range(100) call). Lists, sets, and dictionaries are iterable objects, which means you could have comprehensions nested inside of comprehensions, as in the following example:

>>> nestedIntList = [[0, 1, 2, 3], [4], [5, 6], [7, 8, 9]]
>>> nestedStrList = [[str(i) for i in sublist] for sublist in nestedIntList]
>>> nestedStrList
[['0', '1', '2', '3'], ['4'], ['5', '6'], ['7', '8', '9']]

But nested list comprehensions (or nested set and dictionary compre- hensions) cram a lot of complexity into a small amount of code, making your code hard to read. It’s better to expand the list comprehension into one or more for loops instead:

>>> nestedIntList = [[0, 1, 2, 3], [4], [5, 6], [7, 8, 9]]
>>> nestedStrList = []
>>> for sublist in nestedIntList:
>>>     nestedStrList.append([str(i) for i in sublist])
>>>
>>> nestedStrList
>>> [['0', '1', '2', '3'], ['4'], ['5', '6'], ['7', '8', '9']]
[['0', '1', '2', '3'], ['4'], ['5', '6'], ['7', '8', '9']]

Comprehensions can also contain multiple for expressions, although this tends to produce unreadable code as well. For example, the following list comprehension produces a flattened list from a nested list:

>>> nestedList = [[0, 1, 2, 3], [4], [5, 6], [7, 8, 9]]
>>> flatList = [num for sublist in nestedList for num in sublist]
>>> flatList
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

This list comprehension contains two for expressions, but it’s difficult for even experienced Python developers to understand. The expanded form, which uses two for loops, creates the same flattened list but is much easier to read:

>>> nestedList = [[0, 1, 2, 3], [4], [5, 6], [7, 8, 9]]
>>> flatList = []
>>> for sublist in nestedList:
>>>     for num in sublist:
>>>         flatList.append(num)
>>> flatList
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Comprehensions are syntactic shortcuts that can produce concise code, but don’t go overboard and nest them within each other.