Show the difference between a Python list comprehension and a generator expression.
The syntax is almost identical — square brackets versus round ones — but the object you get is completely different.
squares_list = [n * n for n in range(5)]
squares_gen = (n * n for n in range(5))
print(type(squares_list).__name__) # list
print(type(squares_gen).__name__) # generator
print(squares_list) # [0, 1, 4, 9, 16]
print(list(squares_gen)) # [0, 1, 4, 9, 16]
# The generator is now exhausted
print(list(squares_gen)) # []The list comprehension evaluates all five values immediately and stores them. The generator expression creates a generator object that holds nothing but the range and a position, and computes each square only when it is asked for. list on the generator then drains it and produces the same five values.
The last line is the second important difference. The generator is single-use, so once it has been drained, listing it again gives an empty list. The list comprehension, by contrast, can be traversed as many times as you like.
A third difference is that the generator cannot be indexed, has no length, and cannot be sorted or reversed. Any of those operations would need to consume the whole generator, and once consumed there is nothing left.
One syntax note: a generator expression must always be parenthesised when it is the sole argument to a function, because the call parentheses would otherwise be ambiguous. That is why you see sum(n * n for n in range(5)) with the inner parentheses, and list(...) with the brackets making the outer grouping explicit.