Last active
April 10, 2018 04:57
-
-
Save jbasko/31c70457e0875b1ca68432039b892445 to your computer and use it in GitHub Desktop.
This doesn't do what you think it does
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def filter1(r): | |
return r % 3 == 0 | |
def filter2(r): | |
return r % 5 == 0 | |
def apply_all(records, *filters): | |
for f in filters: | |
records = (r for r in records if f(r)) | |
return records | |
# This prints [0, 5, 10, 15, 20, 25, 30, 35] | |
# NOT [0, 15, 30] | |
print(list(apply_all(range(40), filter1, filter2))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice one!
To see why this is happening, first replace the generator expression with its moral equivalent:
Notice that
__gen
is a closure. In particular, its reference tof
is bound to the variablef
ofapply_all
. So when __gen is executed, the value it sees forf
is whatever is contained in that variable at the time of execution.Finally, note that the expression,
for f in filters:
updates thef
variable ofapply_all
. It does not create a "new" variable at each iteration, even though we sometimes think of it that way.Putting this together, when you call
apply_all(range(40), filter1, filter2)
the result is a nested chain of generators, each referring to the single variable,f
that was set in theapply_all
invocation. The value of that variable is the last value that was assigned to it, which isfilter2
.Here's a slightly simpler example of the same phenomenon: