Unexpected behaviour with a conditional generator expression [duplicate]












53
















This question already has an answer here:




  • Generator expression uses list assigned after the generator's creation

    5 answers




I was running a piece of code that unexpectedly gave a logic error at one part of the program. When investigating the section, I created a test file to test the set of statements being run and found out an unusual bug that seems very odd.



I tested this simple code:



array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
array = [5, 6, 1, 2, 9] # Updates original to something else

print(list(f)) # Outputs filtered


And the output was:



>>> 


Yes, nothing. I was expecting the filter comprehension to get items in the array with a count of 2 and output this, but I didn't get that:



# Expected output
>>> [2, 2]


When I commented out the third line to test it once again:



array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
### array = [5, 6, 1, 2, 9] # Ignore line

print(list(f)) # Outputs filtered


The output was correct (you can test it for yourself):



>>> [2, 2]


At one point I outputted the type of the variable f:



array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
array = [5, 6, 1, 2, 9] # Updates original

print(type(f))
print(list(f)) # Outputs filtered


And I got:



>>> <class 'generator'>
>>>


Why is updating a list in Python changing the output of another generator variable? This seems very odd to me.










share|improve this question















marked as duplicate by Eugene Yarmash python
Users with the  python badge can single-handedly close python questions as duplicates and reopen them as needed.

StackExchange.ready(function() {
if (StackExchange.options.isMobile) return;

$('.dupe-hammer-message-hover:not(.hover-bound)').each(function() {
var $hover = $(this).addClass('hover-bound'),
$msg = $hover.siblings('.dupe-hammer-message');

$hover.hover(
function() {
$hover.showInfoMessage('', {
messageElement: $msg.clone().show(),
transient: false,
position: { my: 'bottom left', at: 'top center', offsetTop: -7 },
dismissable: false,
relativeToBody: true
});
},
function() {
StackExchange.helpers.removeMessages();
}
);
});
});
Jan 23 at 10:23


This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.














  • 3





    You redefine array and your new array is what gets referenced by the lazy generator comprehension.

    – jpp
    Jan 17 at 23:14













  • Would be good to see an answer that mentions scope.

    – trailing_whitespace
    Jan 18 at 6:12






  • 3





    This is a variation of the question of "late binding" of python closures. The generator is essentially acting like a closure here. (I'm not sure why the answers are so focused on laziness... that, I think, is obvious to anyone using a generator.)

    – Mateen Ulhaq
    Jan 18 at 7:07


















53
















This question already has an answer here:




  • Generator expression uses list assigned after the generator's creation

    5 answers




I was running a piece of code that unexpectedly gave a logic error at one part of the program. When investigating the section, I created a test file to test the set of statements being run and found out an unusual bug that seems very odd.



I tested this simple code:



array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
array = [5, 6, 1, 2, 9] # Updates original to something else

print(list(f)) # Outputs filtered


And the output was:



>>> 


Yes, nothing. I was expecting the filter comprehension to get items in the array with a count of 2 and output this, but I didn't get that:



# Expected output
>>> [2, 2]


When I commented out the third line to test it once again:



array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
### array = [5, 6, 1, 2, 9] # Ignore line

print(list(f)) # Outputs filtered


The output was correct (you can test it for yourself):



>>> [2, 2]


At one point I outputted the type of the variable f:



array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
array = [5, 6, 1, 2, 9] # Updates original

print(type(f))
print(list(f)) # Outputs filtered


And I got:



>>> <class 'generator'>
>>>


Why is updating a list in Python changing the output of another generator variable? This seems very odd to me.










share|improve this question















marked as duplicate by Eugene Yarmash python
Users with the  python badge can single-handedly close python questions as duplicates and reopen them as needed.

StackExchange.ready(function() {
if (StackExchange.options.isMobile) return;

$('.dupe-hammer-message-hover:not(.hover-bound)').each(function() {
var $hover = $(this).addClass('hover-bound'),
$msg = $hover.siblings('.dupe-hammer-message');

$hover.hover(
function() {
$hover.showInfoMessage('', {
messageElement: $msg.clone().show(),
transient: false,
position: { my: 'bottom left', at: 'top center', offsetTop: -7 },
dismissable: false,
relativeToBody: true
});
},
function() {
StackExchange.helpers.removeMessages();
}
);
});
});
Jan 23 at 10:23


This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.














  • 3





    You redefine array and your new array is what gets referenced by the lazy generator comprehension.

    – jpp
    Jan 17 at 23:14













  • Would be good to see an answer that mentions scope.

    – trailing_whitespace
    Jan 18 at 6:12






  • 3





    This is a variation of the question of "late binding" of python closures. The generator is essentially acting like a closure here. (I'm not sure why the answers are so focused on laziness... that, I think, is obvious to anyone using a generator.)

    – Mateen Ulhaq
    Jan 18 at 7:07
















53












53








53


9







This question already has an answer here:




  • Generator expression uses list assigned after the generator's creation

    5 answers




I was running a piece of code that unexpectedly gave a logic error at one part of the program. When investigating the section, I created a test file to test the set of statements being run and found out an unusual bug that seems very odd.



I tested this simple code:



array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
array = [5, 6, 1, 2, 9] # Updates original to something else

print(list(f)) # Outputs filtered


And the output was:



>>> 


Yes, nothing. I was expecting the filter comprehension to get items in the array with a count of 2 and output this, but I didn't get that:



# Expected output
>>> [2, 2]


When I commented out the third line to test it once again:



array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
### array = [5, 6, 1, 2, 9] # Ignore line

print(list(f)) # Outputs filtered


The output was correct (you can test it for yourself):



>>> [2, 2]


At one point I outputted the type of the variable f:



array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
array = [5, 6, 1, 2, 9] # Updates original

print(type(f))
print(list(f)) # Outputs filtered


And I got:



>>> <class 'generator'>
>>>


Why is updating a list in Python changing the output of another generator variable? This seems very odd to me.










share|improve this question

















This question already has an answer here:




  • Generator expression uses list assigned after the generator's creation

    5 answers




I was running a piece of code that unexpectedly gave a logic error at one part of the program. When investigating the section, I created a test file to test the set of statements being run and found out an unusual bug that seems very odd.



I tested this simple code:



array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
array = [5, 6, 1, 2, 9] # Updates original to something else

print(list(f)) # Outputs filtered


And the output was:



>>> 


Yes, nothing. I was expecting the filter comprehension to get items in the array with a count of 2 and output this, but I didn't get that:



# Expected output
>>> [2, 2]


When I commented out the third line to test it once again:



array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
### array = [5, 6, 1, 2, 9] # Ignore line

print(list(f)) # Outputs filtered


The output was correct (you can test it for yourself):



>>> [2, 2]


At one point I outputted the type of the variable f:



array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
array = [5, 6, 1, 2, 9] # Updates original

print(type(f))
print(list(f)) # Outputs filtered


And I got:



>>> <class 'generator'>
>>>


Why is updating a list in Python changing the output of another generator variable? This seems very odd to me.





This question already has an answer here:




  • Generator expression uses list assigned after the generator's creation

    5 answers








python generator variable-assignment generator-expression






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Feb 4 at 17:42









Martijn Pieters

711k13724812301




711k13724812301










asked Jan 17 at 23:11









Suraj KothariSuraj Kothari

915619




915619




marked as duplicate by Eugene Yarmash python
Users with the  python badge can single-handedly close python questions as duplicates and reopen them as needed.

StackExchange.ready(function() {
if (StackExchange.options.isMobile) return;

$('.dupe-hammer-message-hover:not(.hover-bound)').each(function() {
var $hover = $(this).addClass('hover-bound'),
$msg = $hover.siblings('.dupe-hammer-message');

$hover.hover(
function() {
$hover.showInfoMessage('', {
messageElement: $msg.clone().show(),
transient: false,
position: { my: 'bottom left', at: 'top center', offsetTop: -7 },
dismissable: false,
relativeToBody: true
});
},
function() {
StackExchange.helpers.removeMessages();
}
);
});
});
Jan 23 at 10:23


This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.









marked as duplicate by Eugene Yarmash python
Users with the  python badge can single-handedly close python questions as duplicates and reopen them as needed.

StackExchange.ready(function() {
if (StackExchange.options.isMobile) return;

$('.dupe-hammer-message-hover:not(.hover-bound)').each(function() {
var $hover = $(this).addClass('hover-bound'),
$msg = $hover.siblings('.dupe-hammer-message');

$hover.hover(
function() {
$hover.showInfoMessage('', {
messageElement: $msg.clone().show(),
transient: false,
position: { my: 'bottom left', at: 'top center', offsetTop: -7 },
dismissable: false,
relativeToBody: true
});
},
function() {
StackExchange.helpers.removeMessages();
}
);
});
});
Jan 23 at 10:23


This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.










  • 3





    You redefine array and your new array is what gets referenced by the lazy generator comprehension.

    – jpp
    Jan 17 at 23:14













  • Would be good to see an answer that mentions scope.

    – trailing_whitespace
    Jan 18 at 6:12






  • 3





    This is a variation of the question of "late binding" of python closures. The generator is essentially acting like a closure here. (I'm not sure why the answers are so focused on laziness... that, I think, is obvious to anyone using a generator.)

    – Mateen Ulhaq
    Jan 18 at 7:07
















  • 3





    You redefine array and your new array is what gets referenced by the lazy generator comprehension.

    – jpp
    Jan 17 at 23:14













  • Would be good to see an answer that mentions scope.

    – trailing_whitespace
    Jan 18 at 6:12






  • 3





    This is a variation of the question of "late binding" of python closures. The generator is essentially acting like a closure here. (I'm not sure why the answers are so focused on laziness... that, I think, is obvious to anyone using a generator.)

    – Mateen Ulhaq
    Jan 18 at 7:07










3




3





You redefine array and your new array is what gets referenced by the lazy generator comprehension.

– jpp
Jan 17 at 23:14







You redefine array and your new array is what gets referenced by the lazy generator comprehension.

– jpp
Jan 17 at 23:14















Would be good to see an answer that mentions scope.

– trailing_whitespace
Jan 18 at 6:12





Would be good to see an answer that mentions scope.

– trailing_whitespace
Jan 18 at 6:12




3




3





This is a variation of the question of "late binding" of python closures. The generator is essentially acting like a closure here. (I'm not sure why the answers are so focused on laziness... that, I think, is obvious to anyone using a generator.)

– Mateen Ulhaq
Jan 18 at 7:07







This is a variation of the question of "late binding" of python closures. The generator is essentially acting like a closure here. (I'm not sure why the answers are so focused on laziness... that, I think, is obvious to anyone using a generator.)

– Mateen Ulhaq
Jan 18 at 7:07














8 Answers
8






active

oldest

votes


















56














Python's generator expressions are late binding (see PEP 289 -- Generator Expressions) (what the other answers call "lazy"):




Early Binding versus Late Binding



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. It was felt that introducing a new paradigm would unnecessarily introduce complexity.



After exploring many possibilities, a consensus emerged that binding issues were hard to understand and that users should be strongly encouraged to use generator expressions inside functions that consume their arguments immediately. For more complex applications, full generator definitions are always superior in terms of being obvious about scope, lifetime, and binding.




That means it only evaluates the outermost for when creating the generator expression. So it actually binds the value with the name array in the "subexpression" in array (in fact it's binding the equivalent to iter(array) at this point). But when you iterate over the generator the if array.count call actually refers to what is currently named array.





Since it's actually a list not an array I changed the variable names in the rest of the answer to be more accurate.



In your first case the list you iterate over and the list you count in will be different. It's as if you used:



list1 = [1, 2, 2, 4, 5]
list2 = [5, 6, 1, 2, 9]
f = (x for x in list1 if list2.count(x) == 2)


So you check for each element in list1 if its count in list2 is two.



You can easily verify this by modifying the second list:



>>> lst = [1, 2, 2]
>>> f = (x for x in lst if lst.count(x) == 2)
>>> lst = [1, 1, 2]
>>> list(f)
[1]


If it iterated over the first list and counted in the first list it would've returned [2, 2] (because the first list contains two 2). If it iterated over and counted in the second list the output should be [1, 1]. But since it iterates over the first list (containing one 1) but checks the second list (which contains two 1s) the output is just a single 1.



Solution using a generator function



There are several possible solutions, I generally prefer not to use "generator expressions" if they aren't iterated over immediately. A simple generator function will suffice to make it work correctly:



def keep_only_duplicated_items(lst):
for item in lst:
if lst.count(item) == 2:
yield item


And then use it like this:



lst = [1, 2, 2, 4, 5]
f = keep_only_duplicated_items(lst)
lst = [5, 6, 1, 2, 9]

>>> list(f)
[2, 2]


Note that the PEP (see the link above) also states that for anything more complicated a full generator definition is preferrable.



A better solution using a generator function with a Counter



A better solution (avoiding the quadratic runtime behavior because you iterate over the whole array for each element in the array) would be to count (collections.Counter) the elements once and then do the lookup in constant time (resulting in linear time):



from collections import Counter

def keep_only_duplicated_items(lst):
cnts = Counter(lst)
for item in lst:
if cnts[item] == 2:
yield item


Appendix: Using a subclass to "visualize" what happens and when it happens



It's quite easy to create a list subclass that prints when specific methods are called, so one can verify that it really works like that.



In this case I just override the methods __iter__ and count because I'm interested over which list the generator expression iterates and in which list it counts. The method bodies actually just delegate to the superclass and print something (since it uses super without arguments and f-strings it requires Python 3.6 but it should be easy to adapt for other Python versions):



class MyList(list):
def __iter__(self):
print(f'__iter__() called on {self!r}')
return super().__iter__()

def count(self, item):
cnt = super().count(item)
print(f'count({item!r}) called on {self!r}, result: {cnt}')
return cnt


This is a simple subclass just printing when the __iter__ and count method are called:



>>> lst = MyList([1, 2, 2, 4, 5])

>>> f = (x for x in lst if lst.count(x) == 2)
__iter__() called on [1, 2, 2, 4, 5]

>>> lst = MyList([5, 6, 1, 2, 9])

>>> print(list(f))
count(1) called on [5, 6, 1, 2, 9], result: 1
count(2) called on [5, 6, 1, 2, 9], result: 1
count(2) called on [5, 6, 1, 2, 9], result: 1
count(4) called on [5, 6, 1, 2, 9], result: 0
count(5) called on [5, 6, 1, 2, 9], result: 1






share|improve this answer





















  • 3





    This is the only answer that explains all the subtleties involved in the questioned behavior.

    – hkBst
    Jan 18 at 11:46











  • Your example as given (with result [1]) might only look at the second list. It would be even better if you used something like [1, 1, 2, 2, 3, 4, 5] and [1, 2, 2, 3, 3, 4, 6], with result [2, 2, 3].

    – hkBst
    Jan 18 at 11:50











  • See for example tio.run/…

    – hkBst
    Jan 18 at 11:59











  • @hkBst Thank you for the additional example. But I'm not sure what you mean with my example being ambguous. I thought in case it would look only at the first list the result would be [2,2], if it would only look at the second list the result would be [1, 1]. That the result is [1] shows that it iterates over the first list, but filters based on the second list. Is my thinking incorrect there?

    – MSeifert
    Jan 18 at 12:30






  • 1





    Wow, that's about as counter-intuitive as it gets. Usually Python is easier to explain than that.

    – Mark Ransom
    Jan 18 at 14:45



















18














As others have mentioned Python generators are lazy. When this line is run:



f = (x for x in array if array.count(x) == 2) # Filters original


nothing actually happens yet. You've just declared how the generator function f will work. Array is not looked at yet. Then, you create a new array that replaces the first one, and finally when you call



print(list(f)) # Outputs filtered


the generator now needs the actual values and starts pulling them from the generator f. But at this point, array already refers to the second one, so you get an empty list.



If you need to reassign the list, and can't use a different variable to hold it, consider creating the list instead of a generator in the second line:



f = [x for x in array if array.count(x) == 2] # Filters original
...
print(f)





share|improve this answer





















  • 5





    This is incorrect. As stackoverflow.com/a/54249614/5600363 explains array in in array is bound immediately but array in array.count only later. You could also try to explain tio.run/…

    – hkBst
    Jan 18 at 11:55





















8














Others have already explained the root cause of the issue - the generator is binding to the name of the array local variable, rather than its value.



The most pythonic solution is definitely the list comprehension:



f = [x for x in array if array.count(x) == 2]




However, if there is some reason that you don't want to create a list, you can also force a scope close over array:



f = (lambda array=array: (x for x in array if array.count(x) == 2))()


What's happening here is that the lambda captures the reference to array at the time the line is run, ensuring that the generator sees the variable you expect, even if the variable is later redefined.



Note that this still binds to the variable (reference), not the value, so, for example, the following will print [2, 2, 4, 4]:



array = [1, 2, 2, 4, 5] # Original array

f = (lambda array=array: (x for x in array if array.count(x) == 2))() # Close over array
array.append(4) # This *will* be captured

array = [5, 6, 1, 2, 9] # Updates original to something else

print(list(f)) # Outputs [2, 2, 4, 4]




This is a common pattern in some languages, but it's not very pythonic, so only really makes sense if there's a very good reason for not using the list comprehension (e.g., if array is very long, or is being used in a nested generator comprehension, and you're concerned about memory).






share|improve this answer
























  • Useful answer for showing how to override the default behavior!

    – hkBst
    Jan 18 at 12:03



















7














You are not using a generator correctly if this is the primary use of this code. Use a list comprehension instead of a generator comprehension. Just replace the parentheses with brackets. It evaluates to a list if you don't know.



array = [1, 2, 2, 4, 5]
f = [x for x in array if array.count(x) == 2]
array = [5, 6, 1, 2, 9]

print(f)
#[2, 2]


You are getting this response because of the nature of a generator. You're calling the generator when it't contents will evaluate to






share|improve this answer


























  • Thank you. I seem to have used the wrong brackets. But in general using a generator comprehension seems odd.

    – Suraj Kothari
    Jan 17 at 23:22











  • With your change, list(f) becomes redundant.

    – Mark Ransom
    Jan 17 at 23:22











  • Lol @Mark Ransom, copy paste got me, I edited.

    – Jaba
    Jan 17 at 23:24








  • 1





    @SurajKothari It is not odd, it's a great tool! It just takes some time to wrap the ole brain around. Do some research you'll find that generators are amazing!

    – Jaba
    Jan 17 at 23:26






  • 1





    This does not explain the observed behavior and so does not answer the question.

    – hkBst
    Jan 18 at 12:00



















5














Generators are lazy, they won't be evaluated until you iterate through them. In this case that's at the point you create the list with the generator as input, at the print.






share|improve this answer


























  • When am I iterating through them. Am I meant to?

    – Suraj Kothari
    Jan 17 at 23:16











  • @SurajKothari when you create the list it will iterate for you without you needing to do it explicitly.

    – Mark Ransom
    Jan 17 at 23:17











  • Also which list? When I declare the first one, or re-assign the second?

    – Suraj Kothari
    Jan 17 at 23:17











  • What first & second? You define only one list, at the final line of your code.

    – Prune
    Jan 17 at 23:18






  • 2





    This could have been my own answer, but it is incorrect (see MSeifert's answer) or try to explain tio.run/…

    – hkBst
    Jan 18 at 12:02





















4














The root cause of the problem is that generators are lazy; variables are evaluated each time:



>>> l = [1, 2, 2, 4, 5, 5, 5]
>>> filtered = (x for x in l if l.count(x) == 2)
>>> l = [1, 2, 4, 4, 5, 6, 6]
>>> list(filtered)
[4]


It iterates over the original list and evaluates the condition with the current list. In this case, 4 appeared twice in the new list, causing it to appear in the result. It only appears once in the result because it only appeared once in the original list. The 6s appear twice in the new list, but never appear in the old list and are hence never shown.



Full function introspection for the curious (the line with the comment is the important line):



>>> l = [1, 2, 2, 4, 5]
>>> filtered = (x for x in l if l.count(x) == 2)
>>> l = [1, 2, 4, 4, 5, 6, 6]
>>> list(filtered)
[4]
>>> def f(original, new, count):
current = original
filtered = (x for x in current if current.count(x) == count)
current = new
return list(filtered)

>>> from dis import dis
>>> dis(f)
2 0 LOAD_FAST 0 (original)
3 STORE_DEREF 1 (current)

3 6 LOAD_CLOSURE 0 (count)
9 LOAD_CLOSURE 1 (current)
12 BUILD_TUPLE 2
15 LOAD_CONST 1 (<code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>)
18 LOAD_CONST 2 ('f.<locals>.<genexpr>')
21 MAKE_CLOSURE 0
24 LOAD_DEREF 1 (current)
27 GET_ITER
28 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
31 STORE_FAST 3 (filtered)

4 34 LOAD_FAST 1 (new)
37 STORE_DEREF 1 (current)

5 40 LOAD_GLOBAL 0 (list)
43 LOAD_FAST 3 (filtered)
46 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
49 RETURN_VALUE
>>> f.__code__.co_varnames
('original', 'new', 'count', 'filtered')
>>> f.__code__.co_cellvars
('count', 'current')
>>> f.__code__.co_consts
(None, <code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>, 'f.<locals>.<genexpr>')
>>> f.__code__.co_consts[1]
<code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>
>>> dis(f.__code__.co_consts[1])
3 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 32 (to 38)
6 STORE_FAST 1 (x)
9 LOAD_DEREF 1 (current) # This loads the current list every time, as opposed to loading a constant.
12 LOAD_ATTR 0 (count)
15 LOAD_FAST 1 (x)
18 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
21 LOAD_DEREF 0 (count)
24 COMPARE_OP 2 (==)
27 POP_JUMP_IF_FALSE 3
30 LOAD_FAST 1 (x)
33 YIELD_VALUE
34 POP_TOP
35 JUMP_ABSOLUTE 3
>> 38 LOAD_CONST 0 (None)
41 RETURN_VALUE
>>> f.__code__.co_consts[1].co_consts
(None,)


To reiterate: The list to be iterated is only loaded once. Any closures in the condition or expression, however, are loaded from the enclosing scope each iteration. They are not stored in a constant.



The best solution for your problem would be to create a new variable referencing the original list and use that in your generator expression,.






share|improve this answer

































    2














    Generator evaluation is "lazy" -- it doesn't get executed until you actualize it with a proper reference. With your line:



    Look again at your output with the type of f: that object is a generator, not a sequence. It's waiting to be used, an iterator of sorts.



    Your generator isn't evaluated until you start requiring values from it. At that point, it uses the available values at that point, not the point at which it was defined.





    Code to "make it work"



    That depends on what you mean by "make it work". If you want f to be a filtered list, then use a list, not a generator:



    f = [x for x in array if array.count(x) == 2] # Filters original





    share|improve this answer


























    • I somewhat understand. Could you show some code to make it work, because I need to re-assign the same list again in the main code.

      – Suraj Kothari
      Jan 17 at 23:19



















    1














    Generators are lazy and your newly defined array is used when you exhaust your generator after redefining. Therefore, the output is correct. A quick fix is to use a list comprehension by replacing parentheses () by brackets .



    Moving on to how better to write your logic, counting a value in a loop has quadratic complexity. For an algorithm that works in linear time, you can use collections.Counter to count values, and keep a copy of your original list:



    from collections import Counter

    array = [1, 2, 2, 4, 5] # original array
    counts = Counter(array) # count each value in array
    old_array = array.copy() # make copy
    array = [5, 6, 1, 2, 9] # updates array

    # order relevant
    res = [x for x in old_array if counts[x] >= 2]
    print(res)
    # [2, 2]

    # order irrelevant
    from itertools import chain
    res = list(chain.from_iterable([x]*count for x, count in counts.items() if count >= 2))
    print(res)
    # [2, 2]


    Notice the second version doesn't even require old_array and is useful if there is no need to maintain ordering of values in your original array.






    share|improve this answer






























      8 Answers
      8






      active

      oldest

      votes








      8 Answers
      8






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      56














      Python's generator expressions are late binding (see PEP 289 -- Generator Expressions) (what the other answers call "lazy"):




      Early Binding versus Late Binding



      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. It was felt that introducing a new paradigm would unnecessarily introduce complexity.



      After exploring many possibilities, a consensus emerged that binding issues were hard to understand and that users should be strongly encouraged to use generator expressions inside functions that consume their arguments immediately. For more complex applications, full generator definitions are always superior in terms of being obvious about scope, lifetime, and binding.




      That means it only evaluates the outermost for when creating the generator expression. So it actually binds the value with the name array in the "subexpression" in array (in fact it's binding the equivalent to iter(array) at this point). But when you iterate over the generator the if array.count call actually refers to what is currently named array.





      Since it's actually a list not an array I changed the variable names in the rest of the answer to be more accurate.



      In your first case the list you iterate over and the list you count in will be different. It's as if you used:



      list1 = [1, 2, 2, 4, 5]
      list2 = [5, 6, 1, 2, 9]
      f = (x for x in list1 if list2.count(x) == 2)


      So you check for each element in list1 if its count in list2 is two.



      You can easily verify this by modifying the second list:



      >>> lst = [1, 2, 2]
      >>> f = (x for x in lst if lst.count(x) == 2)
      >>> lst = [1, 1, 2]
      >>> list(f)
      [1]


      If it iterated over the first list and counted in the first list it would've returned [2, 2] (because the first list contains two 2). If it iterated over and counted in the second list the output should be [1, 1]. But since it iterates over the first list (containing one 1) but checks the second list (which contains two 1s) the output is just a single 1.



      Solution using a generator function



      There are several possible solutions, I generally prefer not to use "generator expressions" if they aren't iterated over immediately. A simple generator function will suffice to make it work correctly:



      def keep_only_duplicated_items(lst):
      for item in lst:
      if lst.count(item) == 2:
      yield item


      And then use it like this:



      lst = [1, 2, 2, 4, 5]
      f = keep_only_duplicated_items(lst)
      lst = [5, 6, 1, 2, 9]

      >>> list(f)
      [2, 2]


      Note that the PEP (see the link above) also states that for anything more complicated a full generator definition is preferrable.



      A better solution using a generator function with a Counter



      A better solution (avoiding the quadratic runtime behavior because you iterate over the whole array for each element in the array) would be to count (collections.Counter) the elements once and then do the lookup in constant time (resulting in linear time):



      from collections import Counter

      def keep_only_duplicated_items(lst):
      cnts = Counter(lst)
      for item in lst:
      if cnts[item] == 2:
      yield item


      Appendix: Using a subclass to "visualize" what happens and when it happens



      It's quite easy to create a list subclass that prints when specific methods are called, so one can verify that it really works like that.



      In this case I just override the methods __iter__ and count because I'm interested over which list the generator expression iterates and in which list it counts. The method bodies actually just delegate to the superclass and print something (since it uses super without arguments and f-strings it requires Python 3.6 but it should be easy to adapt for other Python versions):



      class MyList(list):
      def __iter__(self):
      print(f'__iter__() called on {self!r}')
      return super().__iter__()

      def count(self, item):
      cnt = super().count(item)
      print(f'count({item!r}) called on {self!r}, result: {cnt}')
      return cnt


      This is a simple subclass just printing when the __iter__ and count method are called:



      >>> lst = MyList([1, 2, 2, 4, 5])

      >>> f = (x for x in lst if lst.count(x) == 2)
      __iter__() called on [1, 2, 2, 4, 5]

      >>> lst = MyList([5, 6, 1, 2, 9])

      >>> print(list(f))
      count(1) called on [5, 6, 1, 2, 9], result: 1
      count(2) called on [5, 6, 1, 2, 9], result: 1
      count(2) called on [5, 6, 1, 2, 9], result: 1
      count(4) called on [5, 6, 1, 2, 9], result: 0
      count(5) called on [5, 6, 1, 2, 9], result: 1






      share|improve this answer





















      • 3





        This is the only answer that explains all the subtleties involved in the questioned behavior.

        – hkBst
        Jan 18 at 11:46











      • Your example as given (with result [1]) might only look at the second list. It would be even better if you used something like [1, 1, 2, 2, 3, 4, 5] and [1, 2, 2, 3, 3, 4, 6], with result [2, 2, 3].

        – hkBst
        Jan 18 at 11:50











      • See for example tio.run/…

        – hkBst
        Jan 18 at 11:59











      • @hkBst Thank you for the additional example. But I'm not sure what you mean with my example being ambguous. I thought in case it would look only at the first list the result would be [2,2], if it would only look at the second list the result would be [1, 1]. That the result is [1] shows that it iterates over the first list, but filters based on the second list. Is my thinking incorrect there?

        – MSeifert
        Jan 18 at 12:30






      • 1





        Wow, that's about as counter-intuitive as it gets. Usually Python is easier to explain than that.

        – Mark Ransom
        Jan 18 at 14:45
















      56














      Python's generator expressions are late binding (see PEP 289 -- Generator Expressions) (what the other answers call "lazy"):




      Early Binding versus Late Binding



      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. It was felt that introducing a new paradigm would unnecessarily introduce complexity.



      After exploring many possibilities, a consensus emerged that binding issues were hard to understand and that users should be strongly encouraged to use generator expressions inside functions that consume their arguments immediately. For more complex applications, full generator definitions are always superior in terms of being obvious about scope, lifetime, and binding.




      That means it only evaluates the outermost for when creating the generator expression. So it actually binds the value with the name array in the "subexpression" in array (in fact it's binding the equivalent to iter(array) at this point). But when you iterate over the generator the if array.count call actually refers to what is currently named array.





      Since it's actually a list not an array I changed the variable names in the rest of the answer to be more accurate.



      In your first case the list you iterate over and the list you count in will be different. It's as if you used:



      list1 = [1, 2, 2, 4, 5]
      list2 = [5, 6, 1, 2, 9]
      f = (x for x in list1 if list2.count(x) == 2)


      So you check for each element in list1 if its count in list2 is two.



      You can easily verify this by modifying the second list:



      >>> lst = [1, 2, 2]
      >>> f = (x for x in lst if lst.count(x) == 2)
      >>> lst = [1, 1, 2]
      >>> list(f)
      [1]


      If it iterated over the first list and counted in the first list it would've returned [2, 2] (because the first list contains two 2). If it iterated over and counted in the second list the output should be [1, 1]. But since it iterates over the first list (containing one 1) but checks the second list (which contains two 1s) the output is just a single 1.



      Solution using a generator function



      There are several possible solutions, I generally prefer not to use "generator expressions" if they aren't iterated over immediately. A simple generator function will suffice to make it work correctly:



      def keep_only_duplicated_items(lst):
      for item in lst:
      if lst.count(item) == 2:
      yield item


      And then use it like this:



      lst = [1, 2, 2, 4, 5]
      f = keep_only_duplicated_items(lst)
      lst = [5, 6, 1, 2, 9]

      >>> list(f)
      [2, 2]


      Note that the PEP (see the link above) also states that for anything more complicated a full generator definition is preferrable.



      A better solution using a generator function with a Counter



      A better solution (avoiding the quadratic runtime behavior because you iterate over the whole array for each element in the array) would be to count (collections.Counter) the elements once and then do the lookup in constant time (resulting in linear time):



      from collections import Counter

      def keep_only_duplicated_items(lst):
      cnts = Counter(lst)
      for item in lst:
      if cnts[item] == 2:
      yield item


      Appendix: Using a subclass to "visualize" what happens and when it happens



      It's quite easy to create a list subclass that prints when specific methods are called, so one can verify that it really works like that.



      In this case I just override the methods __iter__ and count because I'm interested over which list the generator expression iterates and in which list it counts. The method bodies actually just delegate to the superclass and print something (since it uses super without arguments and f-strings it requires Python 3.6 but it should be easy to adapt for other Python versions):



      class MyList(list):
      def __iter__(self):
      print(f'__iter__() called on {self!r}')
      return super().__iter__()

      def count(self, item):
      cnt = super().count(item)
      print(f'count({item!r}) called on {self!r}, result: {cnt}')
      return cnt


      This is a simple subclass just printing when the __iter__ and count method are called:



      >>> lst = MyList([1, 2, 2, 4, 5])

      >>> f = (x for x in lst if lst.count(x) == 2)
      __iter__() called on [1, 2, 2, 4, 5]

      >>> lst = MyList([5, 6, 1, 2, 9])

      >>> print(list(f))
      count(1) called on [5, 6, 1, 2, 9], result: 1
      count(2) called on [5, 6, 1, 2, 9], result: 1
      count(2) called on [5, 6, 1, 2, 9], result: 1
      count(4) called on [5, 6, 1, 2, 9], result: 0
      count(5) called on [5, 6, 1, 2, 9], result: 1






      share|improve this answer





















      • 3





        This is the only answer that explains all the subtleties involved in the questioned behavior.

        – hkBst
        Jan 18 at 11:46











      • Your example as given (with result [1]) might only look at the second list. It would be even better if you used something like [1, 1, 2, 2, 3, 4, 5] and [1, 2, 2, 3, 3, 4, 6], with result [2, 2, 3].

        – hkBst
        Jan 18 at 11:50











      • See for example tio.run/…

        – hkBst
        Jan 18 at 11:59











      • @hkBst Thank you for the additional example. But I'm not sure what you mean with my example being ambguous. I thought in case it would look only at the first list the result would be [2,2], if it would only look at the second list the result would be [1, 1]. That the result is [1] shows that it iterates over the first list, but filters based on the second list. Is my thinking incorrect there?

        – MSeifert
        Jan 18 at 12:30






      • 1





        Wow, that's about as counter-intuitive as it gets. Usually Python is easier to explain than that.

        – Mark Ransom
        Jan 18 at 14:45














      56












      56








      56







      Python's generator expressions are late binding (see PEP 289 -- Generator Expressions) (what the other answers call "lazy"):




      Early Binding versus Late Binding



      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. It was felt that introducing a new paradigm would unnecessarily introduce complexity.



      After exploring many possibilities, a consensus emerged that binding issues were hard to understand and that users should be strongly encouraged to use generator expressions inside functions that consume their arguments immediately. For more complex applications, full generator definitions are always superior in terms of being obvious about scope, lifetime, and binding.




      That means it only evaluates the outermost for when creating the generator expression. So it actually binds the value with the name array in the "subexpression" in array (in fact it's binding the equivalent to iter(array) at this point). But when you iterate over the generator the if array.count call actually refers to what is currently named array.





      Since it's actually a list not an array I changed the variable names in the rest of the answer to be more accurate.



      In your first case the list you iterate over and the list you count in will be different. It's as if you used:



      list1 = [1, 2, 2, 4, 5]
      list2 = [5, 6, 1, 2, 9]
      f = (x for x in list1 if list2.count(x) == 2)


      So you check for each element in list1 if its count in list2 is two.



      You can easily verify this by modifying the second list:



      >>> lst = [1, 2, 2]
      >>> f = (x for x in lst if lst.count(x) == 2)
      >>> lst = [1, 1, 2]
      >>> list(f)
      [1]


      If it iterated over the first list and counted in the first list it would've returned [2, 2] (because the first list contains two 2). If it iterated over and counted in the second list the output should be [1, 1]. But since it iterates over the first list (containing one 1) but checks the second list (which contains two 1s) the output is just a single 1.



      Solution using a generator function



      There are several possible solutions, I generally prefer not to use "generator expressions" if they aren't iterated over immediately. A simple generator function will suffice to make it work correctly:



      def keep_only_duplicated_items(lst):
      for item in lst:
      if lst.count(item) == 2:
      yield item


      And then use it like this:



      lst = [1, 2, 2, 4, 5]
      f = keep_only_duplicated_items(lst)
      lst = [5, 6, 1, 2, 9]

      >>> list(f)
      [2, 2]


      Note that the PEP (see the link above) also states that for anything more complicated a full generator definition is preferrable.



      A better solution using a generator function with a Counter



      A better solution (avoiding the quadratic runtime behavior because you iterate over the whole array for each element in the array) would be to count (collections.Counter) the elements once and then do the lookup in constant time (resulting in linear time):



      from collections import Counter

      def keep_only_duplicated_items(lst):
      cnts = Counter(lst)
      for item in lst:
      if cnts[item] == 2:
      yield item


      Appendix: Using a subclass to "visualize" what happens and when it happens



      It's quite easy to create a list subclass that prints when specific methods are called, so one can verify that it really works like that.



      In this case I just override the methods __iter__ and count because I'm interested over which list the generator expression iterates and in which list it counts. The method bodies actually just delegate to the superclass and print something (since it uses super without arguments and f-strings it requires Python 3.6 but it should be easy to adapt for other Python versions):



      class MyList(list):
      def __iter__(self):
      print(f'__iter__() called on {self!r}')
      return super().__iter__()

      def count(self, item):
      cnt = super().count(item)
      print(f'count({item!r}) called on {self!r}, result: {cnt}')
      return cnt


      This is a simple subclass just printing when the __iter__ and count method are called:



      >>> lst = MyList([1, 2, 2, 4, 5])

      >>> f = (x for x in lst if lst.count(x) == 2)
      __iter__() called on [1, 2, 2, 4, 5]

      >>> lst = MyList([5, 6, 1, 2, 9])

      >>> print(list(f))
      count(1) called on [5, 6, 1, 2, 9], result: 1
      count(2) called on [5, 6, 1, 2, 9], result: 1
      count(2) called on [5, 6, 1, 2, 9], result: 1
      count(4) called on [5, 6, 1, 2, 9], result: 0
      count(5) called on [5, 6, 1, 2, 9], result: 1






      share|improve this answer















      Python's generator expressions are late binding (see PEP 289 -- Generator Expressions) (what the other answers call "lazy"):




      Early Binding versus Late Binding



      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. It was felt that introducing a new paradigm would unnecessarily introduce complexity.



      After exploring many possibilities, a consensus emerged that binding issues were hard to understand and that users should be strongly encouraged to use generator expressions inside functions that consume their arguments immediately. For more complex applications, full generator definitions are always superior in terms of being obvious about scope, lifetime, and binding.




      That means it only evaluates the outermost for when creating the generator expression. So it actually binds the value with the name array in the "subexpression" in array (in fact it's binding the equivalent to iter(array) at this point). But when you iterate over the generator the if array.count call actually refers to what is currently named array.





      Since it's actually a list not an array I changed the variable names in the rest of the answer to be more accurate.



      In your first case the list you iterate over and the list you count in will be different. It's as if you used:



      list1 = [1, 2, 2, 4, 5]
      list2 = [5, 6, 1, 2, 9]
      f = (x for x in list1 if list2.count(x) == 2)


      So you check for each element in list1 if its count in list2 is two.



      You can easily verify this by modifying the second list:



      >>> lst = [1, 2, 2]
      >>> f = (x for x in lst if lst.count(x) == 2)
      >>> lst = [1, 1, 2]
      >>> list(f)
      [1]


      If it iterated over the first list and counted in the first list it would've returned [2, 2] (because the first list contains two 2). If it iterated over and counted in the second list the output should be [1, 1]. But since it iterates over the first list (containing one 1) but checks the second list (which contains two 1s) the output is just a single 1.



      Solution using a generator function



      There are several possible solutions, I generally prefer not to use "generator expressions" if they aren't iterated over immediately. A simple generator function will suffice to make it work correctly:



      def keep_only_duplicated_items(lst):
      for item in lst:
      if lst.count(item) == 2:
      yield item


      And then use it like this:



      lst = [1, 2, 2, 4, 5]
      f = keep_only_duplicated_items(lst)
      lst = [5, 6, 1, 2, 9]

      >>> list(f)
      [2, 2]


      Note that the PEP (see the link above) also states that for anything more complicated a full generator definition is preferrable.



      A better solution using a generator function with a Counter



      A better solution (avoiding the quadratic runtime behavior because you iterate over the whole array for each element in the array) would be to count (collections.Counter) the elements once and then do the lookup in constant time (resulting in linear time):



      from collections import Counter

      def keep_only_duplicated_items(lst):
      cnts = Counter(lst)
      for item in lst:
      if cnts[item] == 2:
      yield item


      Appendix: Using a subclass to "visualize" what happens and when it happens



      It's quite easy to create a list subclass that prints when specific methods are called, so one can verify that it really works like that.



      In this case I just override the methods __iter__ and count because I'm interested over which list the generator expression iterates and in which list it counts. The method bodies actually just delegate to the superclass and print something (since it uses super without arguments and f-strings it requires Python 3.6 but it should be easy to adapt for other Python versions):



      class MyList(list):
      def __iter__(self):
      print(f'__iter__() called on {self!r}')
      return super().__iter__()

      def count(self, item):
      cnt = super().count(item)
      print(f'count({item!r}) called on {self!r}, result: {cnt}')
      return cnt


      This is a simple subclass just printing when the __iter__ and count method are called:



      >>> lst = MyList([1, 2, 2, 4, 5])

      >>> f = (x for x in lst if lst.count(x) == 2)
      __iter__() called on [1, 2, 2, 4, 5]

      >>> lst = MyList([5, 6, 1, 2, 9])

      >>> print(list(f))
      count(1) called on [5, 6, 1, 2, 9], result: 1
      count(2) called on [5, 6, 1, 2, 9], result: 1
      count(2) called on [5, 6, 1, 2, 9], result: 1
      count(4) called on [5, 6, 1, 2, 9], result: 0
      count(5) called on [5, 6, 1, 2, 9], result: 1







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited Jan 22 at 20:05

























      answered Jan 18 at 7:43









      MSeifertMSeifert

      75.6k18146179




      75.6k18146179








      • 3





        This is the only answer that explains all the subtleties involved in the questioned behavior.

        – hkBst
        Jan 18 at 11:46











      • Your example as given (with result [1]) might only look at the second list. It would be even better if you used something like [1, 1, 2, 2, 3, 4, 5] and [1, 2, 2, 3, 3, 4, 6], with result [2, 2, 3].

        – hkBst
        Jan 18 at 11:50











      • See for example tio.run/…

        – hkBst
        Jan 18 at 11:59











      • @hkBst Thank you for the additional example. But I'm not sure what you mean with my example being ambguous. I thought in case it would look only at the first list the result would be [2,2], if it would only look at the second list the result would be [1, 1]. That the result is [1] shows that it iterates over the first list, but filters based on the second list. Is my thinking incorrect there?

        – MSeifert
        Jan 18 at 12:30






      • 1





        Wow, that's about as counter-intuitive as it gets. Usually Python is easier to explain than that.

        – Mark Ransom
        Jan 18 at 14:45














      • 3





        This is the only answer that explains all the subtleties involved in the questioned behavior.

        – hkBst
        Jan 18 at 11:46











      • Your example as given (with result [1]) might only look at the second list. It would be even better if you used something like [1, 1, 2, 2, 3, 4, 5] and [1, 2, 2, 3, 3, 4, 6], with result [2, 2, 3].

        – hkBst
        Jan 18 at 11:50











      • See for example tio.run/…

        – hkBst
        Jan 18 at 11:59











      • @hkBst Thank you for the additional example. But I'm not sure what you mean with my example being ambguous. I thought in case it would look only at the first list the result would be [2,2], if it would only look at the second list the result would be [1, 1]. That the result is [1] shows that it iterates over the first list, but filters based on the second list. Is my thinking incorrect there?

        – MSeifert
        Jan 18 at 12:30






      • 1





        Wow, that's about as counter-intuitive as it gets. Usually Python is easier to explain than that.

        – Mark Ransom
        Jan 18 at 14:45








      3




      3





      This is the only answer that explains all the subtleties involved in the questioned behavior.

      – hkBst
      Jan 18 at 11:46





      This is the only answer that explains all the subtleties involved in the questioned behavior.

      – hkBst
      Jan 18 at 11:46













      Your example as given (with result [1]) might only look at the second list. It would be even better if you used something like [1, 1, 2, 2, 3, 4, 5] and [1, 2, 2, 3, 3, 4, 6], with result [2, 2, 3].

      – hkBst
      Jan 18 at 11:50





      Your example as given (with result [1]) might only look at the second list. It would be even better if you used something like [1, 1, 2, 2, 3, 4, 5] and [1, 2, 2, 3, 3, 4, 6], with result [2, 2, 3].

      – hkBst
      Jan 18 at 11:50













      See for example tio.run/…

      – hkBst
      Jan 18 at 11:59





      See for example tio.run/…

      – hkBst
      Jan 18 at 11:59













      @hkBst Thank you for the additional example. But I'm not sure what you mean with my example being ambguous. I thought in case it would look only at the first list the result would be [2,2], if it would only look at the second list the result would be [1, 1]. That the result is [1] shows that it iterates over the first list, but filters based on the second list. Is my thinking incorrect there?

      – MSeifert
      Jan 18 at 12:30





      @hkBst Thank you for the additional example. But I'm not sure what you mean with my example being ambguous. I thought in case it would look only at the first list the result would be [2,2], if it would only look at the second list the result would be [1, 1]. That the result is [1] shows that it iterates over the first list, but filters based on the second list. Is my thinking incorrect there?

      – MSeifert
      Jan 18 at 12:30




      1




      1





      Wow, that's about as counter-intuitive as it gets. Usually Python is easier to explain than that.

      – Mark Ransom
      Jan 18 at 14:45





      Wow, that's about as counter-intuitive as it gets. Usually Python is easier to explain than that.

      – Mark Ransom
      Jan 18 at 14:45













      18














      As others have mentioned Python generators are lazy. When this line is run:



      f = (x for x in array if array.count(x) == 2) # Filters original


      nothing actually happens yet. You've just declared how the generator function f will work. Array is not looked at yet. Then, you create a new array that replaces the first one, and finally when you call



      print(list(f)) # Outputs filtered


      the generator now needs the actual values and starts pulling them from the generator f. But at this point, array already refers to the second one, so you get an empty list.



      If you need to reassign the list, and can't use a different variable to hold it, consider creating the list instead of a generator in the second line:



      f = [x for x in array if array.count(x) == 2] # Filters original
      ...
      print(f)





      share|improve this answer





















      • 5





        This is incorrect. As stackoverflow.com/a/54249614/5600363 explains array in in array is bound immediately but array in array.count only later. You could also try to explain tio.run/…

        – hkBst
        Jan 18 at 11:55


















      18














      As others have mentioned Python generators are lazy. When this line is run:



      f = (x for x in array if array.count(x) == 2) # Filters original


      nothing actually happens yet. You've just declared how the generator function f will work. Array is not looked at yet. Then, you create a new array that replaces the first one, and finally when you call



      print(list(f)) # Outputs filtered


      the generator now needs the actual values and starts pulling them from the generator f. But at this point, array already refers to the second one, so you get an empty list.



      If you need to reassign the list, and can't use a different variable to hold it, consider creating the list instead of a generator in the second line:



      f = [x for x in array if array.count(x) == 2] # Filters original
      ...
      print(f)





      share|improve this answer





















      • 5





        This is incorrect. As stackoverflow.com/a/54249614/5600363 explains array in in array is bound immediately but array in array.count only later. You could also try to explain tio.run/…

        – hkBst
        Jan 18 at 11:55
















      18












      18








      18







      As others have mentioned Python generators are lazy. When this line is run:



      f = (x for x in array if array.count(x) == 2) # Filters original


      nothing actually happens yet. You've just declared how the generator function f will work. Array is not looked at yet. Then, you create a new array that replaces the first one, and finally when you call



      print(list(f)) # Outputs filtered


      the generator now needs the actual values and starts pulling them from the generator f. But at this point, array already refers to the second one, so you get an empty list.



      If you need to reassign the list, and can't use a different variable to hold it, consider creating the list instead of a generator in the second line:



      f = [x for x in array if array.count(x) == 2] # Filters original
      ...
      print(f)





      share|improve this answer















      As others have mentioned Python generators are lazy. When this line is run:



      f = (x for x in array if array.count(x) == 2) # Filters original


      nothing actually happens yet. You've just declared how the generator function f will work. Array is not looked at yet. Then, you create a new array that replaces the first one, and finally when you call



      print(list(f)) # Outputs filtered


      the generator now needs the actual values and starts pulling them from the generator f. But at this point, array already refers to the second one, so you get an empty list.



      If you need to reassign the list, and can't use a different variable to hold it, consider creating the list instead of a generator in the second line:



      f = [x for x in array if array.count(x) == 2] # Filters original
      ...
      print(f)






      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited Jan 17 at 23:27

























      answered Jan 17 at 23:22









      StevenSteven

      42138




      42138








      • 5





        This is incorrect. As stackoverflow.com/a/54249614/5600363 explains array in in array is bound immediately but array in array.count only later. You could also try to explain tio.run/…

        – hkBst
        Jan 18 at 11:55
















      • 5





        This is incorrect. As stackoverflow.com/a/54249614/5600363 explains array in in array is bound immediately but array in array.count only later. You could also try to explain tio.run/…

        – hkBst
        Jan 18 at 11:55










      5




      5





      This is incorrect. As stackoverflow.com/a/54249614/5600363 explains array in in array is bound immediately but array in array.count only later. You could also try to explain tio.run/…

      – hkBst
      Jan 18 at 11:55







      This is incorrect. As stackoverflow.com/a/54249614/5600363 explains array in in array is bound immediately but array in array.count only later. You could also try to explain tio.run/…

      – hkBst
      Jan 18 at 11:55













      8














      Others have already explained the root cause of the issue - the generator is binding to the name of the array local variable, rather than its value.



      The most pythonic solution is definitely the list comprehension:



      f = [x for x in array if array.count(x) == 2]




      However, if there is some reason that you don't want to create a list, you can also force a scope close over array:



      f = (lambda array=array: (x for x in array if array.count(x) == 2))()


      What's happening here is that the lambda captures the reference to array at the time the line is run, ensuring that the generator sees the variable you expect, even if the variable is later redefined.



      Note that this still binds to the variable (reference), not the value, so, for example, the following will print [2, 2, 4, 4]:



      array = [1, 2, 2, 4, 5] # Original array

      f = (lambda array=array: (x for x in array if array.count(x) == 2))() # Close over array
      array.append(4) # This *will* be captured

      array = [5, 6, 1, 2, 9] # Updates original to something else

      print(list(f)) # Outputs [2, 2, 4, 4]




      This is a common pattern in some languages, but it's not very pythonic, so only really makes sense if there's a very good reason for not using the list comprehension (e.g., if array is very long, or is being used in a nested generator comprehension, and you're concerned about memory).






      share|improve this answer
























      • Useful answer for showing how to override the default behavior!

        – hkBst
        Jan 18 at 12:03
















      8














      Others have already explained the root cause of the issue - the generator is binding to the name of the array local variable, rather than its value.



      The most pythonic solution is definitely the list comprehension:



      f = [x for x in array if array.count(x) == 2]




      However, if there is some reason that you don't want to create a list, you can also force a scope close over array:



      f = (lambda array=array: (x for x in array if array.count(x) == 2))()


      What's happening here is that the lambda captures the reference to array at the time the line is run, ensuring that the generator sees the variable you expect, even if the variable is later redefined.



      Note that this still binds to the variable (reference), not the value, so, for example, the following will print [2, 2, 4, 4]:



      array = [1, 2, 2, 4, 5] # Original array

      f = (lambda array=array: (x for x in array if array.count(x) == 2))() # Close over array
      array.append(4) # This *will* be captured

      array = [5, 6, 1, 2, 9] # Updates original to something else

      print(list(f)) # Outputs [2, 2, 4, 4]




      This is a common pattern in some languages, but it's not very pythonic, so only really makes sense if there's a very good reason for not using the list comprehension (e.g., if array is very long, or is being used in a nested generator comprehension, and you're concerned about memory).






      share|improve this answer
























      • Useful answer for showing how to override the default behavior!

        – hkBst
        Jan 18 at 12:03














      8












      8








      8







      Others have already explained the root cause of the issue - the generator is binding to the name of the array local variable, rather than its value.



      The most pythonic solution is definitely the list comprehension:



      f = [x for x in array if array.count(x) == 2]




      However, if there is some reason that you don't want to create a list, you can also force a scope close over array:



      f = (lambda array=array: (x for x in array if array.count(x) == 2))()


      What's happening here is that the lambda captures the reference to array at the time the line is run, ensuring that the generator sees the variable you expect, even if the variable is later redefined.



      Note that this still binds to the variable (reference), not the value, so, for example, the following will print [2, 2, 4, 4]:



      array = [1, 2, 2, 4, 5] # Original array

      f = (lambda array=array: (x for x in array if array.count(x) == 2))() # Close over array
      array.append(4) # This *will* be captured

      array = [5, 6, 1, 2, 9] # Updates original to something else

      print(list(f)) # Outputs [2, 2, 4, 4]




      This is a common pattern in some languages, but it's not very pythonic, so only really makes sense if there's a very good reason for not using the list comprehension (e.g., if array is very long, or is being used in a nested generator comprehension, and you're concerned about memory).






      share|improve this answer













      Others have already explained the root cause of the issue - the generator is binding to the name of the array local variable, rather than its value.



      The most pythonic solution is definitely the list comprehension:



      f = [x for x in array if array.count(x) == 2]




      However, if there is some reason that you don't want to create a list, you can also force a scope close over array:



      f = (lambda array=array: (x for x in array if array.count(x) == 2))()


      What's happening here is that the lambda captures the reference to array at the time the line is run, ensuring that the generator sees the variable you expect, even if the variable is later redefined.



      Note that this still binds to the variable (reference), not the value, so, for example, the following will print [2, 2, 4, 4]:



      array = [1, 2, 2, 4, 5] # Original array

      f = (lambda array=array: (x for x in array if array.count(x) == 2))() # Close over array
      array.append(4) # This *will* be captured

      array = [5, 6, 1, 2, 9] # Updates original to something else

      print(list(f)) # Outputs [2, 2, 4, 4]




      This is a common pattern in some languages, but it's not very pythonic, so only really makes sense if there's a very good reason for not using the list comprehension (e.g., if array is very long, or is being used in a nested generator comprehension, and you're concerned about memory).







      share|improve this answer












      share|improve this answer



      share|improve this answer










      answered Jan 18 at 7:11









      sapisapi

      6,66263161




      6,66263161













      • Useful answer for showing how to override the default behavior!

        – hkBst
        Jan 18 at 12:03



















      • Useful answer for showing how to override the default behavior!

        – hkBst
        Jan 18 at 12:03

















      Useful answer for showing how to override the default behavior!

      – hkBst
      Jan 18 at 12:03





      Useful answer for showing how to override the default behavior!

      – hkBst
      Jan 18 at 12:03











      7














      You are not using a generator correctly if this is the primary use of this code. Use a list comprehension instead of a generator comprehension. Just replace the parentheses with brackets. It evaluates to a list if you don't know.



      array = [1, 2, 2, 4, 5]
      f = [x for x in array if array.count(x) == 2]
      array = [5, 6, 1, 2, 9]

      print(f)
      #[2, 2]


      You are getting this response because of the nature of a generator. You're calling the generator when it't contents will evaluate to






      share|improve this answer


























      • Thank you. I seem to have used the wrong brackets. But in general using a generator comprehension seems odd.

        – Suraj Kothari
        Jan 17 at 23:22











      • With your change, list(f) becomes redundant.

        – Mark Ransom
        Jan 17 at 23:22











      • Lol @Mark Ransom, copy paste got me, I edited.

        – Jaba
        Jan 17 at 23:24








      • 1





        @SurajKothari It is not odd, it's a great tool! It just takes some time to wrap the ole brain around. Do some research you'll find that generators are amazing!

        – Jaba
        Jan 17 at 23:26






      • 1





        This does not explain the observed behavior and so does not answer the question.

        – hkBst
        Jan 18 at 12:00
















      7














      You are not using a generator correctly if this is the primary use of this code. Use a list comprehension instead of a generator comprehension. Just replace the parentheses with brackets. It evaluates to a list if you don't know.



      array = [1, 2, 2, 4, 5]
      f = [x for x in array if array.count(x) == 2]
      array = [5, 6, 1, 2, 9]

      print(f)
      #[2, 2]


      You are getting this response because of the nature of a generator. You're calling the generator when it't contents will evaluate to






      share|improve this answer


























      • Thank you. I seem to have used the wrong brackets. But in general using a generator comprehension seems odd.

        – Suraj Kothari
        Jan 17 at 23:22











      • With your change, list(f) becomes redundant.

        – Mark Ransom
        Jan 17 at 23:22











      • Lol @Mark Ransom, copy paste got me, I edited.

        – Jaba
        Jan 17 at 23:24








      • 1





        @SurajKothari It is not odd, it's a great tool! It just takes some time to wrap the ole brain around. Do some research you'll find that generators are amazing!

        – Jaba
        Jan 17 at 23:26






      • 1





        This does not explain the observed behavior and so does not answer the question.

        – hkBst
        Jan 18 at 12:00














      7












      7








      7







      You are not using a generator correctly if this is the primary use of this code. Use a list comprehension instead of a generator comprehension. Just replace the parentheses with brackets. It evaluates to a list if you don't know.



      array = [1, 2, 2, 4, 5]
      f = [x for x in array if array.count(x) == 2]
      array = [5, 6, 1, 2, 9]

      print(f)
      #[2, 2]


      You are getting this response because of the nature of a generator. You're calling the generator when it't contents will evaluate to






      share|improve this answer















      You are not using a generator correctly if this is the primary use of this code. Use a list comprehension instead of a generator comprehension. Just replace the parentheses with brackets. It evaluates to a list if you don't know.



      array = [1, 2, 2, 4, 5]
      f = [x for x in array if array.count(x) == 2]
      array = [5, 6, 1, 2, 9]

      print(f)
      #[2, 2]


      You are getting this response because of the nature of a generator. You're calling the generator when it't contents will evaluate to







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited Jan 17 at 23:23

























      answered Jan 17 at 23:19









      JabaJaba

      7,501175494




      7,501175494













      • Thank you. I seem to have used the wrong brackets. But in general using a generator comprehension seems odd.

        – Suraj Kothari
        Jan 17 at 23:22











      • With your change, list(f) becomes redundant.

        – Mark Ransom
        Jan 17 at 23:22











      • Lol @Mark Ransom, copy paste got me, I edited.

        – Jaba
        Jan 17 at 23:24








      • 1





        @SurajKothari It is not odd, it's a great tool! It just takes some time to wrap the ole brain around. Do some research you'll find that generators are amazing!

        – Jaba
        Jan 17 at 23:26






      • 1





        This does not explain the observed behavior and so does not answer the question.

        – hkBst
        Jan 18 at 12:00



















      • Thank you. I seem to have used the wrong brackets. But in general using a generator comprehension seems odd.

        – Suraj Kothari
        Jan 17 at 23:22











      • With your change, list(f) becomes redundant.

        – Mark Ransom
        Jan 17 at 23:22











      • Lol @Mark Ransom, copy paste got me, I edited.

        – Jaba
        Jan 17 at 23:24








      • 1





        @SurajKothari It is not odd, it's a great tool! It just takes some time to wrap the ole brain around. Do some research you'll find that generators are amazing!

        – Jaba
        Jan 17 at 23:26






      • 1





        This does not explain the observed behavior and so does not answer the question.

        – hkBst
        Jan 18 at 12:00

















      Thank you. I seem to have used the wrong brackets. But in general using a generator comprehension seems odd.

      – Suraj Kothari
      Jan 17 at 23:22





      Thank you. I seem to have used the wrong brackets. But in general using a generator comprehension seems odd.

      – Suraj Kothari
      Jan 17 at 23:22













      With your change, list(f) becomes redundant.

      – Mark Ransom
      Jan 17 at 23:22





      With your change, list(f) becomes redundant.

      – Mark Ransom
      Jan 17 at 23:22













      Lol @Mark Ransom, copy paste got me, I edited.

      – Jaba
      Jan 17 at 23:24







      Lol @Mark Ransom, copy paste got me, I edited.

      – Jaba
      Jan 17 at 23:24






      1




      1





      @SurajKothari It is not odd, it's a great tool! It just takes some time to wrap the ole brain around. Do some research you'll find that generators are amazing!

      – Jaba
      Jan 17 at 23:26





      @SurajKothari It is not odd, it's a great tool! It just takes some time to wrap the ole brain around. Do some research you'll find that generators are amazing!

      – Jaba
      Jan 17 at 23:26




      1




      1





      This does not explain the observed behavior and so does not answer the question.

      – hkBst
      Jan 18 at 12:00





      This does not explain the observed behavior and so does not answer the question.

      – hkBst
      Jan 18 at 12:00











      5














      Generators are lazy, they won't be evaluated until you iterate through them. In this case that's at the point you create the list with the generator as input, at the print.






      share|improve this answer


























      • When am I iterating through them. Am I meant to?

        – Suraj Kothari
        Jan 17 at 23:16











      • @SurajKothari when you create the list it will iterate for you without you needing to do it explicitly.

        – Mark Ransom
        Jan 17 at 23:17











      • Also which list? When I declare the first one, or re-assign the second?

        – Suraj Kothari
        Jan 17 at 23:17











      • What first & second? You define only one list, at the final line of your code.

        – Prune
        Jan 17 at 23:18






      • 2





        This could have been my own answer, but it is incorrect (see MSeifert's answer) or try to explain tio.run/…

        – hkBst
        Jan 18 at 12:02


















      5














      Generators are lazy, they won't be evaluated until you iterate through them. In this case that's at the point you create the list with the generator as input, at the print.






      share|improve this answer


























      • When am I iterating through them. Am I meant to?

        – Suraj Kothari
        Jan 17 at 23:16











      • @SurajKothari when you create the list it will iterate for you without you needing to do it explicitly.

        – Mark Ransom
        Jan 17 at 23:17











      • Also which list? When I declare the first one, or re-assign the second?

        – Suraj Kothari
        Jan 17 at 23:17











      • What first & second? You define only one list, at the final line of your code.

        – Prune
        Jan 17 at 23:18






      • 2





        This could have been my own answer, but it is incorrect (see MSeifert's answer) or try to explain tio.run/…

        – hkBst
        Jan 18 at 12:02
















      5












      5








      5







      Generators are lazy, they won't be evaluated until you iterate through them. In this case that's at the point you create the list with the generator as input, at the print.






      share|improve this answer















      Generators are lazy, they won't be evaluated until you iterate through them. In this case that's at the point you create the list with the generator as input, at the print.







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited Jan 17 at 23:19

























      answered Jan 17 at 23:15









      Mark RansomMark Ransom

      224k29282509




      224k29282509













      • When am I iterating through them. Am I meant to?

        – Suraj Kothari
        Jan 17 at 23:16











      • @SurajKothari when you create the list it will iterate for you without you needing to do it explicitly.

        – Mark Ransom
        Jan 17 at 23:17











      • Also which list? When I declare the first one, or re-assign the second?

        – Suraj Kothari
        Jan 17 at 23:17











      • What first & second? You define only one list, at the final line of your code.

        – Prune
        Jan 17 at 23:18






      • 2





        This could have been my own answer, but it is incorrect (see MSeifert's answer) or try to explain tio.run/…

        – hkBst
        Jan 18 at 12:02





















      • When am I iterating through them. Am I meant to?

        – Suraj Kothari
        Jan 17 at 23:16











      • @SurajKothari when you create the list it will iterate for you without you needing to do it explicitly.

        – Mark Ransom
        Jan 17 at 23:17











      • Also which list? When I declare the first one, or re-assign the second?

        – Suraj Kothari
        Jan 17 at 23:17











      • What first & second? You define only one list, at the final line of your code.

        – Prune
        Jan 17 at 23:18






      • 2





        This could have been my own answer, but it is incorrect (see MSeifert's answer) or try to explain tio.run/…

        – hkBst
        Jan 18 at 12:02



















      When am I iterating through them. Am I meant to?

      – Suraj Kothari
      Jan 17 at 23:16





      When am I iterating through them. Am I meant to?

      – Suraj Kothari
      Jan 17 at 23:16













      @SurajKothari when you create the list it will iterate for you without you needing to do it explicitly.

      – Mark Ransom
      Jan 17 at 23:17





      @SurajKothari when you create the list it will iterate for you without you needing to do it explicitly.

      – Mark Ransom
      Jan 17 at 23:17













      Also which list? When I declare the first one, or re-assign the second?

      – Suraj Kothari
      Jan 17 at 23:17





      Also which list? When I declare the first one, or re-assign the second?

      – Suraj Kothari
      Jan 17 at 23:17













      What first & second? You define only one list, at the final line of your code.

      – Prune
      Jan 17 at 23:18





      What first & second? You define only one list, at the final line of your code.

      – Prune
      Jan 17 at 23:18




      2




      2





      This could have been my own answer, but it is incorrect (see MSeifert's answer) or try to explain tio.run/…

      – hkBst
      Jan 18 at 12:02







      This could have been my own answer, but it is incorrect (see MSeifert's answer) or try to explain tio.run/…

      – hkBst
      Jan 18 at 12:02













      4














      The root cause of the problem is that generators are lazy; variables are evaluated each time:



      >>> l = [1, 2, 2, 4, 5, 5, 5]
      >>> filtered = (x for x in l if l.count(x) == 2)
      >>> l = [1, 2, 4, 4, 5, 6, 6]
      >>> list(filtered)
      [4]


      It iterates over the original list and evaluates the condition with the current list. In this case, 4 appeared twice in the new list, causing it to appear in the result. It only appears once in the result because it only appeared once in the original list. The 6s appear twice in the new list, but never appear in the old list and are hence never shown.



      Full function introspection for the curious (the line with the comment is the important line):



      >>> l = [1, 2, 2, 4, 5]
      >>> filtered = (x for x in l if l.count(x) == 2)
      >>> l = [1, 2, 4, 4, 5, 6, 6]
      >>> list(filtered)
      [4]
      >>> def f(original, new, count):
      current = original
      filtered = (x for x in current if current.count(x) == count)
      current = new
      return list(filtered)

      >>> from dis import dis
      >>> dis(f)
      2 0 LOAD_FAST 0 (original)
      3 STORE_DEREF 1 (current)

      3 6 LOAD_CLOSURE 0 (count)
      9 LOAD_CLOSURE 1 (current)
      12 BUILD_TUPLE 2
      15 LOAD_CONST 1 (<code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>)
      18 LOAD_CONST 2 ('f.<locals>.<genexpr>')
      21 MAKE_CLOSURE 0
      24 LOAD_DEREF 1 (current)
      27 GET_ITER
      28 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
      31 STORE_FAST 3 (filtered)

      4 34 LOAD_FAST 1 (new)
      37 STORE_DEREF 1 (current)

      5 40 LOAD_GLOBAL 0 (list)
      43 LOAD_FAST 3 (filtered)
      46 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
      49 RETURN_VALUE
      >>> f.__code__.co_varnames
      ('original', 'new', 'count', 'filtered')
      >>> f.__code__.co_cellvars
      ('count', 'current')
      >>> f.__code__.co_consts
      (None, <code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>, 'f.<locals>.<genexpr>')
      >>> f.__code__.co_consts[1]
      <code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>
      >>> dis(f.__code__.co_consts[1])
      3 0 LOAD_FAST 0 (.0)
      >> 3 FOR_ITER 32 (to 38)
      6 STORE_FAST 1 (x)
      9 LOAD_DEREF 1 (current) # This loads the current list every time, as opposed to loading a constant.
      12 LOAD_ATTR 0 (count)
      15 LOAD_FAST 1 (x)
      18 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
      21 LOAD_DEREF 0 (count)
      24 COMPARE_OP 2 (==)
      27 POP_JUMP_IF_FALSE 3
      30 LOAD_FAST 1 (x)
      33 YIELD_VALUE
      34 POP_TOP
      35 JUMP_ABSOLUTE 3
      >> 38 LOAD_CONST 0 (None)
      41 RETURN_VALUE
      >>> f.__code__.co_consts[1].co_consts
      (None,)


      To reiterate: The list to be iterated is only loaded once. Any closures in the condition or expression, however, are loaded from the enclosing scope each iteration. They are not stored in a constant.



      The best solution for your problem would be to create a new variable referencing the original list and use that in your generator expression,.






      share|improve this answer






























        4














        The root cause of the problem is that generators are lazy; variables are evaluated each time:



        >>> l = [1, 2, 2, 4, 5, 5, 5]
        >>> filtered = (x for x in l if l.count(x) == 2)
        >>> l = [1, 2, 4, 4, 5, 6, 6]
        >>> list(filtered)
        [4]


        It iterates over the original list and evaluates the condition with the current list. In this case, 4 appeared twice in the new list, causing it to appear in the result. It only appears once in the result because it only appeared once in the original list. The 6s appear twice in the new list, but never appear in the old list and are hence never shown.



        Full function introspection for the curious (the line with the comment is the important line):



        >>> l = [1, 2, 2, 4, 5]
        >>> filtered = (x for x in l if l.count(x) == 2)
        >>> l = [1, 2, 4, 4, 5, 6, 6]
        >>> list(filtered)
        [4]
        >>> def f(original, new, count):
        current = original
        filtered = (x for x in current if current.count(x) == count)
        current = new
        return list(filtered)

        >>> from dis import dis
        >>> dis(f)
        2 0 LOAD_FAST 0 (original)
        3 STORE_DEREF 1 (current)

        3 6 LOAD_CLOSURE 0 (count)
        9 LOAD_CLOSURE 1 (current)
        12 BUILD_TUPLE 2
        15 LOAD_CONST 1 (<code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>)
        18 LOAD_CONST 2 ('f.<locals>.<genexpr>')
        21 MAKE_CLOSURE 0
        24 LOAD_DEREF 1 (current)
        27 GET_ITER
        28 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
        31 STORE_FAST 3 (filtered)

        4 34 LOAD_FAST 1 (new)
        37 STORE_DEREF 1 (current)

        5 40 LOAD_GLOBAL 0 (list)
        43 LOAD_FAST 3 (filtered)
        46 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
        49 RETURN_VALUE
        >>> f.__code__.co_varnames
        ('original', 'new', 'count', 'filtered')
        >>> f.__code__.co_cellvars
        ('count', 'current')
        >>> f.__code__.co_consts
        (None, <code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>, 'f.<locals>.<genexpr>')
        >>> f.__code__.co_consts[1]
        <code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>
        >>> dis(f.__code__.co_consts[1])
        3 0 LOAD_FAST 0 (.0)
        >> 3 FOR_ITER 32 (to 38)
        6 STORE_FAST 1 (x)
        9 LOAD_DEREF 1 (current) # This loads the current list every time, as opposed to loading a constant.
        12 LOAD_ATTR 0 (count)
        15 LOAD_FAST 1 (x)
        18 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
        21 LOAD_DEREF 0 (count)
        24 COMPARE_OP 2 (==)
        27 POP_JUMP_IF_FALSE 3
        30 LOAD_FAST 1 (x)
        33 YIELD_VALUE
        34 POP_TOP
        35 JUMP_ABSOLUTE 3
        >> 38 LOAD_CONST 0 (None)
        41 RETURN_VALUE
        >>> f.__code__.co_consts[1].co_consts
        (None,)


        To reiterate: The list to be iterated is only loaded once. Any closures in the condition or expression, however, are loaded from the enclosing scope each iteration. They are not stored in a constant.



        The best solution for your problem would be to create a new variable referencing the original list and use that in your generator expression,.






        share|improve this answer




























          4












          4








          4







          The root cause of the problem is that generators are lazy; variables are evaluated each time:



          >>> l = [1, 2, 2, 4, 5, 5, 5]
          >>> filtered = (x for x in l if l.count(x) == 2)
          >>> l = [1, 2, 4, 4, 5, 6, 6]
          >>> list(filtered)
          [4]


          It iterates over the original list and evaluates the condition with the current list. In this case, 4 appeared twice in the new list, causing it to appear in the result. It only appears once in the result because it only appeared once in the original list. The 6s appear twice in the new list, but never appear in the old list and are hence never shown.



          Full function introspection for the curious (the line with the comment is the important line):



          >>> l = [1, 2, 2, 4, 5]
          >>> filtered = (x for x in l if l.count(x) == 2)
          >>> l = [1, 2, 4, 4, 5, 6, 6]
          >>> list(filtered)
          [4]
          >>> def f(original, new, count):
          current = original
          filtered = (x for x in current if current.count(x) == count)
          current = new
          return list(filtered)

          >>> from dis import dis
          >>> dis(f)
          2 0 LOAD_FAST 0 (original)
          3 STORE_DEREF 1 (current)

          3 6 LOAD_CLOSURE 0 (count)
          9 LOAD_CLOSURE 1 (current)
          12 BUILD_TUPLE 2
          15 LOAD_CONST 1 (<code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>)
          18 LOAD_CONST 2 ('f.<locals>.<genexpr>')
          21 MAKE_CLOSURE 0
          24 LOAD_DEREF 1 (current)
          27 GET_ITER
          28 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
          31 STORE_FAST 3 (filtered)

          4 34 LOAD_FAST 1 (new)
          37 STORE_DEREF 1 (current)

          5 40 LOAD_GLOBAL 0 (list)
          43 LOAD_FAST 3 (filtered)
          46 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
          49 RETURN_VALUE
          >>> f.__code__.co_varnames
          ('original', 'new', 'count', 'filtered')
          >>> f.__code__.co_cellvars
          ('count', 'current')
          >>> f.__code__.co_consts
          (None, <code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>, 'f.<locals>.<genexpr>')
          >>> f.__code__.co_consts[1]
          <code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>
          >>> dis(f.__code__.co_consts[1])
          3 0 LOAD_FAST 0 (.0)
          >> 3 FOR_ITER 32 (to 38)
          6 STORE_FAST 1 (x)
          9 LOAD_DEREF 1 (current) # This loads the current list every time, as opposed to loading a constant.
          12 LOAD_ATTR 0 (count)
          15 LOAD_FAST 1 (x)
          18 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
          21 LOAD_DEREF 0 (count)
          24 COMPARE_OP 2 (==)
          27 POP_JUMP_IF_FALSE 3
          30 LOAD_FAST 1 (x)
          33 YIELD_VALUE
          34 POP_TOP
          35 JUMP_ABSOLUTE 3
          >> 38 LOAD_CONST 0 (None)
          41 RETURN_VALUE
          >>> f.__code__.co_consts[1].co_consts
          (None,)


          To reiterate: The list to be iterated is only loaded once. Any closures in the condition or expression, however, are loaded from the enclosing scope each iteration. They are not stored in a constant.



          The best solution for your problem would be to create a new variable referencing the original list and use that in your generator expression,.






          share|improve this answer















          The root cause of the problem is that generators are lazy; variables are evaluated each time:



          >>> l = [1, 2, 2, 4, 5, 5, 5]
          >>> filtered = (x for x in l if l.count(x) == 2)
          >>> l = [1, 2, 4, 4, 5, 6, 6]
          >>> list(filtered)
          [4]


          It iterates over the original list and evaluates the condition with the current list. In this case, 4 appeared twice in the new list, causing it to appear in the result. It only appears once in the result because it only appeared once in the original list. The 6s appear twice in the new list, but never appear in the old list and are hence never shown.



          Full function introspection for the curious (the line with the comment is the important line):



          >>> l = [1, 2, 2, 4, 5]
          >>> filtered = (x for x in l if l.count(x) == 2)
          >>> l = [1, 2, 4, 4, 5, 6, 6]
          >>> list(filtered)
          [4]
          >>> def f(original, new, count):
          current = original
          filtered = (x for x in current if current.count(x) == count)
          current = new
          return list(filtered)

          >>> from dis import dis
          >>> dis(f)
          2 0 LOAD_FAST 0 (original)
          3 STORE_DEREF 1 (current)

          3 6 LOAD_CLOSURE 0 (count)
          9 LOAD_CLOSURE 1 (current)
          12 BUILD_TUPLE 2
          15 LOAD_CONST 1 (<code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>)
          18 LOAD_CONST 2 ('f.<locals>.<genexpr>')
          21 MAKE_CLOSURE 0
          24 LOAD_DEREF 1 (current)
          27 GET_ITER
          28 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
          31 STORE_FAST 3 (filtered)

          4 34 LOAD_FAST 1 (new)
          37 STORE_DEREF 1 (current)

          5 40 LOAD_GLOBAL 0 (list)
          43 LOAD_FAST 3 (filtered)
          46 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
          49 RETURN_VALUE
          >>> f.__code__.co_varnames
          ('original', 'new', 'count', 'filtered')
          >>> f.__code__.co_cellvars
          ('count', 'current')
          >>> f.__code__.co_consts
          (None, <code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>, 'f.<locals>.<genexpr>')
          >>> f.__code__.co_consts[1]
          <code object <genexpr> at 0x02DD36B0, file "<pyshell#17>", line 3>
          >>> dis(f.__code__.co_consts[1])
          3 0 LOAD_FAST 0 (.0)
          >> 3 FOR_ITER 32 (to 38)
          6 STORE_FAST 1 (x)
          9 LOAD_DEREF 1 (current) # This loads the current list every time, as opposed to loading a constant.
          12 LOAD_ATTR 0 (count)
          15 LOAD_FAST 1 (x)
          18 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
          21 LOAD_DEREF 0 (count)
          24 COMPARE_OP 2 (==)
          27 POP_JUMP_IF_FALSE 3
          30 LOAD_FAST 1 (x)
          33 YIELD_VALUE
          34 POP_TOP
          35 JUMP_ABSOLUTE 3
          >> 38 LOAD_CONST 0 (None)
          41 RETURN_VALUE
          >>> f.__code__.co_consts[1].co_consts
          (None,)


          To reiterate: The list to be iterated is only loaded once. Any closures in the condition or expression, however, are loaded from the enclosing scope each iteration. They are not stored in a constant.



          The best solution for your problem would be to create a new variable referencing the original list and use that in your generator expression,.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Jan 18 at 3:34

























          answered Jan 18 at 3:25









          Solomon UckoSolomon Ucko

          6992819




          6992819























              2














              Generator evaluation is "lazy" -- it doesn't get executed until you actualize it with a proper reference. With your line:



              Look again at your output with the type of f: that object is a generator, not a sequence. It's waiting to be used, an iterator of sorts.



              Your generator isn't evaluated until you start requiring values from it. At that point, it uses the available values at that point, not the point at which it was defined.





              Code to "make it work"



              That depends on what you mean by "make it work". If you want f to be a filtered list, then use a list, not a generator:



              f = [x for x in array if array.count(x) == 2] # Filters original





              share|improve this answer


























              • I somewhat understand. Could you show some code to make it work, because I need to re-assign the same list again in the main code.

                – Suraj Kothari
                Jan 17 at 23:19
















              2














              Generator evaluation is "lazy" -- it doesn't get executed until you actualize it with a proper reference. With your line:



              Look again at your output with the type of f: that object is a generator, not a sequence. It's waiting to be used, an iterator of sorts.



              Your generator isn't evaluated until you start requiring values from it. At that point, it uses the available values at that point, not the point at which it was defined.





              Code to "make it work"



              That depends on what you mean by "make it work". If you want f to be a filtered list, then use a list, not a generator:



              f = [x for x in array if array.count(x) == 2] # Filters original





              share|improve this answer


























              • I somewhat understand. Could you show some code to make it work, because I need to re-assign the same list again in the main code.

                – Suraj Kothari
                Jan 17 at 23:19














              2












              2








              2







              Generator evaluation is "lazy" -- it doesn't get executed until you actualize it with a proper reference. With your line:



              Look again at your output with the type of f: that object is a generator, not a sequence. It's waiting to be used, an iterator of sorts.



              Your generator isn't evaluated until you start requiring values from it. At that point, it uses the available values at that point, not the point at which it was defined.





              Code to "make it work"



              That depends on what you mean by "make it work". If you want f to be a filtered list, then use a list, not a generator:



              f = [x for x in array if array.count(x) == 2] # Filters original





              share|improve this answer















              Generator evaluation is "lazy" -- it doesn't get executed until you actualize it with a proper reference. With your line:



              Look again at your output with the type of f: that object is a generator, not a sequence. It's waiting to be used, an iterator of sorts.



              Your generator isn't evaluated until you start requiring values from it. At that point, it uses the available values at that point, not the point at which it was defined.





              Code to "make it work"



              That depends on what you mean by "make it work". If you want f to be a filtered list, then use a list, not a generator:



              f = [x for x in array if array.count(x) == 2] # Filters original






              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited Jan 17 at 23:36

























              answered Jan 17 at 23:17









              PrunePrune

              43.9k143457




              43.9k143457













              • I somewhat understand. Could you show some code to make it work, because I need to re-assign the same list again in the main code.

                – Suraj Kothari
                Jan 17 at 23:19



















              • I somewhat understand. Could you show some code to make it work, because I need to re-assign the same list again in the main code.

                – Suraj Kothari
                Jan 17 at 23:19

















              I somewhat understand. Could you show some code to make it work, because I need to re-assign the same list again in the main code.

              – Suraj Kothari
              Jan 17 at 23:19





              I somewhat understand. Could you show some code to make it work, because I need to re-assign the same list again in the main code.

              – Suraj Kothari
              Jan 17 at 23:19











              1














              Generators are lazy and your newly defined array is used when you exhaust your generator after redefining. Therefore, the output is correct. A quick fix is to use a list comprehension by replacing parentheses () by brackets .



              Moving on to how better to write your logic, counting a value in a loop has quadratic complexity. For an algorithm that works in linear time, you can use collections.Counter to count values, and keep a copy of your original list:



              from collections import Counter

              array = [1, 2, 2, 4, 5] # original array
              counts = Counter(array) # count each value in array
              old_array = array.copy() # make copy
              array = [5, 6, 1, 2, 9] # updates array

              # order relevant
              res = [x for x in old_array if counts[x] >= 2]
              print(res)
              # [2, 2]

              # order irrelevant
              from itertools import chain
              res = list(chain.from_iterable([x]*count for x, count in counts.items() if count >= 2))
              print(res)
              # [2, 2]


              Notice the second version doesn't even require old_array and is useful if there is no need to maintain ordering of values in your original array.






              share|improve this answer




























                1














                Generators are lazy and your newly defined array is used when you exhaust your generator after redefining. Therefore, the output is correct. A quick fix is to use a list comprehension by replacing parentheses () by brackets .



                Moving on to how better to write your logic, counting a value in a loop has quadratic complexity. For an algorithm that works in linear time, you can use collections.Counter to count values, and keep a copy of your original list:



                from collections import Counter

                array = [1, 2, 2, 4, 5] # original array
                counts = Counter(array) # count each value in array
                old_array = array.copy() # make copy
                array = [5, 6, 1, 2, 9] # updates array

                # order relevant
                res = [x for x in old_array if counts[x] >= 2]
                print(res)
                # [2, 2]

                # order irrelevant
                from itertools import chain
                res = list(chain.from_iterable([x]*count for x, count in counts.items() if count >= 2))
                print(res)
                # [2, 2]


                Notice the second version doesn't even require old_array and is useful if there is no need to maintain ordering of values in your original array.






                share|improve this answer


























                  1












                  1








                  1







                  Generators are lazy and your newly defined array is used when you exhaust your generator after redefining. Therefore, the output is correct. A quick fix is to use a list comprehension by replacing parentheses () by brackets .



                  Moving on to how better to write your logic, counting a value in a loop has quadratic complexity. For an algorithm that works in linear time, you can use collections.Counter to count values, and keep a copy of your original list:



                  from collections import Counter

                  array = [1, 2, 2, 4, 5] # original array
                  counts = Counter(array) # count each value in array
                  old_array = array.copy() # make copy
                  array = [5, 6, 1, 2, 9] # updates array

                  # order relevant
                  res = [x for x in old_array if counts[x] >= 2]
                  print(res)
                  # [2, 2]

                  # order irrelevant
                  from itertools import chain
                  res = list(chain.from_iterable([x]*count for x, count in counts.items() if count >= 2))
                  print(res)
                  # [2, 2]


                  Notice the second version doesn't even require old_array and is useful if there is no need to maintain ordering of values in your original array.






                  share|improve this answer













                  Generators are lazy and your newly defined array is used when you exhaust your generator after redefining. Therefore, the output is correct. A quick fix is to use a list comprehension by replacing parentheses () by brackets .



                  Moving on to how better to write your logic, counting a value in a loop has quadratic complexity. For an algorithm that works in linear time, you can use collections.Counter to count values, and keep a copy of your original list:



                  from collections import Counter

                  array = [1, 2, 2, 4, 5] # original array
                  counts = Counter(array) # count each value in array
                  old_array = array.copy() # make copy
                  array = [5, 6, 1, 2, 9] # updates array

                  # order relevant
                  res = [x for x in old_array if counts[x] >= 2]
                  print(res)
                  # [2, 2]

                  # order irrelevant
                  from itertools import chain
                  res = list(chain.from_iterable([x]*count for x, count in counts.items() if count >= 2))
                  print(res)
                  # [2, 2]


                  Notice the second version doesn't even require old_array and is useful if there is no need to maintain ordering of values in your original array.







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Jan 18 at 1:32









                  jppjpp

                  100k2161111




                  100k2161111















                      Popular posts from this blog

                      Human spaceflight

                      Can not write log (Is /dev/pts mounted?) - openpty in Ubuntu-on-Windows?

                      File:DeusFollowingSea.jpg