What the heck are Python Generator Expressions and List Comprehensions?

Vinicius Mesel - Feb 10 '19 - - Dev Community

Have you ever thought on initializing a list with its values with a one liner expression? That's totally possible in Python using list comprehensions. But sometimes you wont use the list stored value for a while and you just wanted to initialize the values for a future use... That's a simple use case of or Generator Expressions.

PS: If you have read Fluent Python from @ramalhoorg , there is nothing new right here, but you can share this text to a friend, so she will be able to learn more about Python.

PS 2: Thanks @ramalhoorg for the examples on the book, they were very useful and some of them are used right here!

What are List Comprehensions?

List comprehensions are a list expression that creates a list with values already inside it, take a look at the example below:

>>> my_incredible_list = [x for x in range(5)]
>>> my_incredible_list
[0, 1, 2, 3, 4]

This list comprehension is the same as if you were doing a for loop appending values to a list.

>>> my_incredible_list = []
>>> for x in range(5):
>>>     my_incredible_list.append(x)
>>> my_incredible_list
[0, 1, 2, 3, 4]

With list comprehensions, you are able to generate lists with tuples inside iterating two other lists.

>>> foods = "apple banana sausages".split() # We declare a list here with foods
>>> beverages = "coca-cola pepsi guarana".split() # We declare a list of beverages
>>> [(food, beverage) for food in foods for beverage in beverages]
[('apple', 'coca-cola'), ('apple', 'pepsi'), ('apple', 'guarana'), ('banana', 'coca-cola'), ('banana', 'pepsi'), ('banana', 'guarana'), ('sausages', 'coca-cola'), ('sausages', 'pepsi'), ('sausages', 'guarana')]

What are Generator Expressions?

So generator expressions are just like a list comprehension, but it doesn't store it's values on the memory, it only generate the value you are iterating on when the generator is called. Take a look at the example below:

>>> my_incredible_list = (x for x in range(5))
>>> my_incredible_list
<generator object <genexpr> at 0x7f14149a3db0>

To check for a value on a generator you can either iterate over the generator using a for loop or you can check the values using the next() operator.


>>> # Using the next operator
>>> next(my_incredible_list)
0
>>> next(my_incredible_list)
1
>>> next(my_incredible_list)
2 
>>> next(my_incredible_list)
3
>>> next(my_incredible_list)
4
>>> next(my_incredible_list)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>
>>> # Using a for loop
>>> for item in my_incredible_list:
...     print(item)
... 
0
1
2
3
4

Checking out the Assembly of both of them to see the differences

Assembly instructions from Python disassambler

>>> import dis
>>> my_incredible_list = [x for x in range(5)]
>>> dis.dis(my_incredible_list)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.6/dis.py", line 67, in dis
    type(x).__name__)
TypeError: don't know how to disassemble list objects
>>> my_incredible_list = (x for x in range(5))
>>> dis.dis(my_incredible_list)
  1           0 LOAD_FAST                0 (.0)
        >>    2 FOR_ITER                10 (to 14)
              4 STORE_FAST               1 (x)
              6 LOAD_FAST                1 (x)
              8 YIELD_VALUE
             10 POP_TOP
             12 JUMP_ABSOLUTE            2
        >>   14 LOAD_CONST               0 (None)
             16 RETURN_VALUE
>>> 

So as we see right here, the disassemble of the list comprehension is not possible, because we cannot disassemble a data structure, but we can still disassemble the generator expression.

When we run dis.dis(<genexpr> right here) we are able to see that the expression is treated like a queue, we yield the value and then we pop it, so it's not called anymore.

If we call the dis.dis(<listcomp> here) we are able to see the running procedure to create a list using the listcomp expression:

>>> dis.dis("[x for x in range(5)]")
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x7f14149c84b0, file "<dis>", line 1>)
              2 LOAD_CONST               1 ('<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_NAME                0 (range)
              8 LOAD_CONST               2 (5)
             10 CALL_FUNCTION            1
             12 GET_ITER
             14 CALL_FUNCTION            1
             16 RETURN_VALUE

So as we see, the MAKE_FUNCTION piece is run generating the values for the list, we instantiate the function range, load a value to it (5), we call it and them we save it to the list instead of just yielding the value every time the generator is called.

. . . . . . . . . .