Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generator expression uses list assigned after the generator's creation

Tags:

I found this example and I can't understand why it works unpredictably? I supposed it must output [1, 8, 15] or [2, 8, 22].

array = [1, 8, 15] g = (x for x in array if array.count(x) > 0) array = [2, 8, 22] print(list(g))   >>>[8] 
like image 623
Gvyntyk Avatar asked Oct 24 '18 11:10

Gvyntyk


People also ask

What symbols are used to create a generator expression?

In terms of syntax, a generator expression uses square brackets [] while a list comprehension uses parentheses () .

When should you use generator expressions and when should you use list comprehensions in Python?

So what's the difference between Generator Expressions and List Comprehensions? The generator yields one item at a time and generates item only when in demand. Whereas, in a list comprehension, Python reserves memory for the whole list. Thus we can say that the generator expressions are memory efficient than the lists.

Is a list comprehension a generator?

List comprehensions and generators are not different at all; they are just different ways of writing the same thing. A list comprehension produces a list as output, a generator produces a generator object.

What is the use of generator in Python?

Python Generator functions allow you to declare a function that behaves likes an iterator, allowing programmers to make an iterator in a fast, easy, and clean way. An iterator is an object that can be iterated or looped upon. It is used to abstract a container of data to make it behave like an iterable object.

What are generator expressions?

What are Generator Expressions? Generator Expressions are somewhat similar to list comprehensions, but the former doesn’t construct list object. Instead of creating a list and keeping the whole sequence in the memory, the generator generates the next element in demand.

What is genergenerator expressions in Python?

Generator Expressions are somewhat similar to list comprehensions, but the former doesn’t construct list object. Instead of creating a list and keeping the whole sequence in the memory, the generator generates the next element in demand.

What is the difference between list comprehensions and generator expressions?

There are various other expressions that can be simply coded similar to list comprehensions but instead of brackets we use parenthesis. These expressions are designed for situations where the generator is used right away by an enclosing function. Generator expression allows creating a generator without a yield keyword.

When should the first for-expression of a generator expression be evaluated?

After much discussion, it was decided that the first (outermost) for-expression [of the generator expression] should be evaluated immediately and that the remaining expressions be evaluated when the generator is executed. [...] Python takes a late binding approach to lambda expressions and has no precedent for automatic, early binding.


2 Answers

The reason is that, at creation time, the generator (a for b in c if d) only evaluates c (which sometimes makes b predictable as well). But a, b, d are evaluated at consumption time (at each iteration). Here, it uses the current binding of array from the enclosing scope when evaluating d (array.count(x) > 0).

You can for instance do:

g = (x for x in [] if a) 

Without having declared a in advance. But, you have to make sure a exists when the generator is consumed.

But you cannot do similarly:

g = (x for x in a if True) 

Upon request:

You can observe similar (however not identical) patterns with a common generator function:

def yielder():     for x in array:         if array.count(x) > 0:             yield x  array = [1, 8, 15] y = yielder() array = [2, 8, 22] list(y) # [2, 8, 22] 

The generator function does not execute any of its body ahead of consumption. Hence, even the array in the for-loop header is bound late. An even more disturbing example occurs where we "switch out" array during iteration:

array = [1, 8, 15] y = yielder() next(y) # 1 array = [3, 7] next(y)  # still iterating [1, 8, 15], but evaluating condition on [3, 7] # StopIteration raised 
like image 142
user2390182 Avatar answered Oct 18 '22 15:10

user2390182


From the docs on Generator expressions:

Variables used in the generator expression are evaluated lazily when the __next__() method is called for the generator object (in the same fashion as normal generators). However, the iterable expression in the leftmost for clause is immediately evaluated, so that an error produced by it will be emitted at the point where the generator expression is defined, rather than at the point where the first value is retrieved.

So when you run

array = [1, 8, 15] g = (x for x in array if array.count(x) > 0) 

only the first array in the generator expression is evaluated. x and array.count(x) will only be evaluated when you call next(g). Since you make array point to another list [2, 8, 22] before consuming the generator you get the 'unexpected' result.

array = [2, 8, 22] print(list(g))  # [8] 
like image 28
Eugene Yarmash Avatar answered Oct 18 '22 13:10

Eugene Yarmash