0

in python3, why is it that the following code will print numbers 0, 1, 2, 3, 4?

[print(i) for i in range(5)]

but if you do the same thing with a generator it doesn't do anything:

(print(i) for i in range(5))

the only way that you could get the same result with a generator is to iterate through it like this:

z = (print(i) for i in range(5))
for i in z:
   i
reza karimi
  • 164
  • 1
  • 8
  • Have a look at this [question](https://stackoverflow.com/questions/47789/generator-expressions-vs-list-comprehension) – Sohaib Farooqi Dec 22 '17 at 04:08
  • @GarbageCollector Thanks for your response. I have seen this question and I have to say that I know the difference between lists and generators. In my case, I am calling a function instead of just putting an element in the list or generator. I want to know that what is happening on the background that causes the first one to actually call the function. – reza karimi Dec 22 '17 at 04:15
  • 1
    Imagine instead of `print` we have some function `f` that takes a long time to execute. The reason we would use a generator is to take advantage of lazy evaluate, where we don't do any calculation that we don't have to. When we write `z = (f(i) for i in range(5))`, we're setting up a generator comprehension, but we aren't actually doing any of the calculation it describes. I would recommend doing some more research on lazy evaluation and how to write your own generators in python using the `def` syntax. – Patrick Haugh Dec 22 '17 at 04:30

2 Answers2

2

This is because you are creating a generator object with

(print(i) for i in range(5))

NOT a list. If you change it to

list(print(i) for i in range(5))

it will act the same as your first statement.

Generators are special types of iterators. For more information you can look at the Python documentation here.

The code in between the parenthesis in (print(i) for i in range(5)) is telling the generator how to 'act' once iterated over it will not actually call the print function until iterated over. The list calls the print function because of list comprehension. Using [print(i) for i in range(5)] or list(print(i) for i in range(5)) is equivalent to

for i in range(5):
   print i

due to list comprehension. So you are actually calling the print function. For more info on list comprehension check out this website.

B3W
  • 166
  • 11
  • but why is this happening? why doesn't the generator call the print function and list does? – reza karimi Dec 22 '17 at 04:16
  • 1
    The generator does not call the print function because you are creating an 'iterator' object. Check out my edit. – B3W Dec 22 '17 at 04:34
  • @B3W. The generator does nothing until you ask it to iterate. That's the nature of lazy processing and the whole purpose of generators. – Mad Physicist Dec 22 '17 at 05:33
  • The list will contain a bunch of `None`s, since that's what `print` returns. If you want to process a generator without that side effect, do something like `collections.deque(gen, maxlen=0)`, where `gen` is your generator. It's the fastest consumer I've been able to find so far. – Mad Physicist Dec 22 '17 at 05:36
  • Yes that is correct. I didn't address that because OP was just asking about the printing difference between the list and the generator. `[print(i) for i in range(5)]` will also create a list of `Nones`. – B3W Dec 22 '17 at 05:41
2

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 printing 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.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
Blckknght
  • 100,903
  • 11
  • 120
  • 169