A generator, such as the object returned by your generator expression is a lazy iterator. It only runs as much code as it needs to provide you with the values you ask for. If you don't iterate on the generator, you don't ask for any values, so it doesn't run any code and so nothing gets printed.
You can request individual values from an iterator by calling next
on it:
gen = (print(i) for i in range(5))
x = next(gen) # causes 0 to be printed
y = next(gen) # causes 1 to be printed
The first call to next
asks for the first value from the generator, so it calls print
with the first value from the range
. The second call to next
will cause the second value from the range
to be printed. Both x
and y
will be assigned None
, since that's what print
returns, and so what is yielded by the generator.
If you put the generator in a for
loop (or pass it to a function like list
that iterates over an argument), the whole generator will be consumed, just like if you called next
on it a bunch of times. If you kept calling next
manually, you'd eventually get a StopIteration
exception, which is how an iterator signals that it has no more values to yield.
The list comprehension you were trying didn't have the same behavior because unlike a generator, a list comprehension is not lazy. It runs all the code immediately, and creates a list from all the values it gets. Your list comprehension will create a list full of None
values (since that's what print
returns), and all the numbers will be printed as the list is being filled.
Note that neither of the versions of your code are very Pythonic. You generally shouldn't use either generator expressions or list comprehensions only for their side effects (such as print
ing values). Use them when you care about the values being yielded (or put in the list). If you just want to print a range of numbers, just use a for
loop on the range directly, and call print
in the body of the loop:
for i in range(5):
print(i)
It's acceptable list comprehension or generator expression with code that does have side effects, as long as the value being computed is also useful. For example, if you wanted to send a message to a bunch of clients over an unreliable network, you might use some code that looked something like this:
results = [client.send(message) for client in clients] # client.send may return an error code
for result in results: # process the results after all the messages were sent
if result is not None: # no error means success
print("client.send error:", result) # report failures, but keep going
The list comprehension on the first line is mostly being used for its side effects (sending the messages), but the list of return values is also useful, since it lets us see what errors occurred.