BUG in standard while statement
![](https://secure.gravatar.com/avatar/5fc1daab3447cd4c54bacc0565c87d5a.jpg?s=120&d=mm&r=g)
Dear all I found a BUG in the standard while statement, which appears both in python 2.7 and python 3.4 on my system. It usually won't appear because I only stumbled upon it after trying to implement a nice repeat structure. Look: ``` class repeat(object): def __init__(self, n): self.n = n def __bool__(self): self.n -= 1 return self.n >= 0 __nonzero__=__bool__ a = repeat(2) ``` the meaning of the above is that bool(a) returns True 2-times, and after that always False. Now executing ``` while a: print('foo') ``` will in fact print 'foo' two times. HOWEVER ;-) .... ``` while repeat(2): print('foo') ``` will go on and go on, printing 'foo' until I kill it. Please comment, explain or recommend this further if you also think that both while statements should behave identically. hoping for responses, best, Stephan
![](https://secure.gravatar.com/avatar/e6cd26321174752221a340fb76534707.jpg?s=120&d=mm&r=g)
This appears as intended. The body of the while condition is executed each time the condition is checked. In the first case, you are creating a single instance of repeat, and then calling bool on the expression with each iteration of the loop. With the second case, you are constructing a _new_ repeat instance each time. Think about the difference between: while should_stop(): ... and: a = should_stop() while a: ... One would expect should_stop to be called each time in the first case; but, in the second case it is only called once. With all that said, I think you want to use the __iter__ and __next__ protocols to implement this in a more supported way. On Wed, Sep 9, 2015 at 1:10 PM, Stephan Sahm <Stephan.Sahm@gmx.de> wrote:
Dear all
I found a BUG in the standard while statement, which appears both in python 2.7 and python 3.4 on my system.
It usually won't appear because I only stumbled upon it after trying to implement a nice repeat structure. Look: ``` class repeat(object): def __init__(self, n): self.n = n
def __bool__(self): self.n -= 1 return self.n >= 0
__nonzero__=__bool__
a = repeat(2) ``` the meaning of the above is that bool(a) returns True 2-times, and after that always False.
Now executing ``` while a: print('foo') ``` will in fact print 'foo' two times. HOWEVER ;-) .... ``` while repeat(2): print('foo') ``` will go on and go on, printing 'foo' until I kill it.
Please comment, explain or recommend this further if you also think that both while statements should behave identically.
hoping for responses, best, Stephan
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/5fc1daab3447cd4c54bacc0565c87d5a.jpg?s=120&d=mm&r=g)
It is so true! thanks for pointing that out. It makes sense to do it that way I probably never used while in different places than ``while 1: pass`` until now. I admit, I am looking for something alternative to a for structure like ``for _ in range(10)`` -- I don't like the ``_`` ;-) How can I use the iterator protocoll to make a nice repeat syntax? On 9 September 2015 at 19:15, Joseph Jevnik <joejev@gmail.com> wrote:
This appears as intended. The body of the while condition is executed each time the condition is checked. In the first case, you are creating a single instance of repeat, and then calling bool on the expression with each iteration of the loop. With the second case, you are constructing a _new_ repeat instance each time. Think about the difference between:
while should_stop(): ...
and: a = should_stop() while a: ...
One would expect should_stop to be called each time in the first case; but, in the second case it is only called once.
With all that said, I think you want to use the __iter__ and __next__ protocols to implement this in a more supported way.
On Wed, Sep 9, 2015 at 1:10 PM, Stephan Sahm <Stephan.Sahm@gmx.de> wrote:
Dear all
I found a BUG in the standard while statement, which appears both in python 2.7 and python 3.4 on my system.
It usually won't appear because I only stumbled upon it after trying to implement a nice repeat structure. Look: ``` class repeat(object): def __init__(self, n): self.n = n
def __bool__(self): self.n -= 1 return self.n >= 0
__nonzero__=__bool__
a = repeat(2) ``` the meaning of the above is that bool(a) returns True 2-times, and after that always False.
Now executing ``` while a: print('foo') ``` will in fact print 'foo' two times. HOWEVER ;-) .... ``` while repeat(2): print('foo') ``` will go on and go on, printing 'foo' until I kill it.
Please comment, explain or recommend this further if you also think that both while statements should behave identically.
hoping for responses, best, Stephan
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/870d613430249e453343efc9667ef636.jpg?s=120&d=mm&r=g)
On Wed, Sep 9, 2015 at 7:20 PM, Stephan Sahm <Stephan.Sahm@gmx.de> wrote:
It is so true! thanks for pointing that out. It makes sense to do it that way I probably never used while in different places than ``while 1: pass`` until now.
I admit, I am looking for something alternative to a for structure like ``for _ in range(10)`` -- I don't like the ``_`` ;-) How can I use the iterator protocoll to make a nice repeat syntax?
If you don't like the ``_``, you can use ``for iteration_index in range(10):``. You always need to store the iteration number somewhere (the original post has it in ``self.n``). Chances are you'll want to access it later, when you debug your code. Hiding it in a class is just making it harder to get. It's also making the whole thing less maintainable, because other people, who are used to seeing ``for i in range(...)``, would now need to understand your custom class and new idiom. (And as always with maintainability, "other people" includes you in a few years.) Instead, just use the current syntax. Eventually it will start looking nice to you.
On 9 September 2015 at 19:15, Joseph Jevnik <joejev@gmail.com> wrote:
This appears as intended. The body of the while condition is executed each time the condition is checked. In the first case, you are creating a single instance of repeat, and then calling bool on the expression with each iteration of the loop. With the second case, you are constructing a _new_ repeat instance each time. Think about the difference between:
while should_stop(): ...
and: a = should_stop() while a: ...
One would expect should_stop to be called each time in the first case; but, in the second case it is only called once.
With all that said, I think you want to use the __iter__ and __next__ protocols to implement this in a more supported way.
On Wed, Sep 9, 2015 at 1:10 PM, Stephan Sahm <Stephan.Sahm@gmx.de> wrote:
Dear all
I found a BUG in the standard while statement, which appears both in python 2.7 and python 3.4 on my system.
It usually won't appear because I only stumbled upon it after trying to implement a nice repeat structure. Look: ``` class repeat(object): def __init__(self, n): self.n = n
def __bool__(self): self.n -= 1 return self.n >= 0
__nonzero__=__bool__
a = repeat(2) ``` the meaning of the above is that bool(a) returns True 2-times, and after that always False.
Now executing ``` while a: print('foo') ``` will in fact print 'foo' two times. HOWEVER ;-) .... ``` while repeat(2): print('foo') ``` will go on and go on, printing 'foo' until I kill it.
Please comment, explain or recommend this further if you also think that both while statements should behave identically.
hoping for responses, best, Stephan
![](https://secure.gravatar.com/avatar/43f93d6fe1e533016fd4243948e81a1f.jpg?s=120&d=mm&r=g)
while repeat(2): creates a new repeat instance each time through the loop and initializes the variable as 2 each time through the loop i.e. repeat(2) returns a new, different instance each time. On 9/9/15, Stephan Sahm <Stephan.Sahm@gmx.de> wrote:
Dear all
I found a BUG in the standard while statement, which appears both in python 2.7 and python 3.4 on my system.
It usually won't appear because I only stumbled upon it after trying to implement a nice repeat structure. Look: ``` class repeat(object): def __init__(self, n): self.n = n
def __bool__(self): self.n -= 1 return self.n >= 0
__nonzero__=__bool__
a = repeat(2) ``` the meaning of the above is that bool(a) returns True 2-times, and after that always False.
Now executing ``` while a: print('foo') ``` will in fact print 'foo' two times. HOWEVER ;-) .... ``` while repeat(2): print('foo') ``` will go on and go on, printing 'foo' until I kill it.
Please comment, explain or recommend this further if you also think that both while statements should behave identically.
hoping for responses, best, Stephan
-- With the simplicity of true nature, there shall be no desire. Without desire, one's original nature will be at peace. And the world will naturally be in accord with the right Way. Tao Te Ching
![](https://secure.gravatar.com/avatar/d6b9415353e04ffa6de5a8f3aaea0553.jpg?s=120&d=mm&r=g)
On 9/9/2015 1:10 PM, Stephan Sahm wrote:
I found a BUG in the standard while statement, which appears both in python 2.7 and python 3.4 on my system.
No you did not, but aside from that: python-ideas is for ideas about future versions of python, not for bug reports, valid or otherwise. You should have sent this to python-list, which is a place to report possible bugs. -- Terry Jan Reedy
![](https://secure.gravatar.com/avatar/5dde29b54a3f1b76b2541d0a4a9b232c.jpg?s=120&d=mm&r=g)
however, he did bring up a python-idea worthy general topic: Sometimes you want to iterate without doing anything with the results of the iteration. So the obvious is his example -- iterate N times: for i in range(N): do_something but you may need to look into the code (probably more than one line) to see if i is used for anything. I know there was talk way back about making integers iterable, so you could do: for i in 32: do something. which would be slightly cleaner, but still has an extra i in there, and this was soundly rejected anyway (for good reason). IN fact, Python's "for" is not really about iterating N times, it's about iteraton over a sequence of objects. Ans I for one find: for _ in range(N): To be just fine -- really very little noise or performance overhead or anything else. However, I've found myself wanting a "make nothing comprehension". For some reason, I find myself frequently following a pattern where I want to call the same method on all the objects in a sequence: for obj in a_sequence: obj.a_method() but I like the compactness of comprehensions, so I do: [obj.a_method() for obj in a_sequence] but then this creates a list of the result from that method call. Which I don't want, so I don't assign the results to anything, and it just goes away. But somehow it bugs me that I'm creating this (maybe big) ol' list full of junk, just to have it deleted. Anyone else think this is a use-case worth supporting better? Or should I jstu get over it -- it's really not that expensive to create a list, after all. -Chris On Thu, Sep 10, 2015 at 12:07 AM, Terry Reedy <tjreedy@udel.edu> wrote:
On 9/9/2015 1:10 PM, Stephan Sahm wrote:
I found a BUG in the standard while statement, which appears both in
python 2.7 and python 3.4 on my system.
No you did not, but aside from that: python-ideas is for ideas about future versions of python, not for bug reports, valid or otherwise. You should have sent this to python-list, which is a place to report possible bugs.
-- Terry Jan Reedy
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- Christopher Barker, Ph.D. Oceanographer Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception Chris.Barker@noaa.gov
![](https://secure.gravatar.com/avatar/5ce43469c0402a7db8d0cf86fa49da5a.jpg?s=120&d=mm&r=g)
On 2015-09-10 20:04, Chris Barker wrote:
however, he did bring up a python-idea worthy general topic:
Sometimes you want to iterate without doing anything with the results of the iteration.
So the obvious is his example -- iterate N times:
for i in range(N): do_something
but you may need to look into the code (probably more than one line) to see if i is used for anything.
I know there was talk way back about making integers iterable, so you could do:
for i in 32: do something.
which would be slightly cleaner, but still has an extra i in there, and this was soundly rejected anyway (for good reason). IN fact, Python's "for" is not really about iterating N times, it's about iteraton over a sequence of objects. Ans I for one find:
for _ in range(N):
To be just fine -- really very little noise or performance overhead or anything else.
However, I've found myself wanting a "make nothing comprehension". For some reason, I find myself frequently following a pattern where I want to call the same method on all the objects in a sequence:
for obj in a_sequence: obj.a_method()
but I like the compactness of comprehensions, so I do:
[obj.a_method() for obj in a_sequence]
but then this creates a list of the result from that method call. Which I don't want, so I don't assign the results to anything, and it just goes away.
But somehow it bugs me that I'm creating this (maybe big) ol' list full of junk, just to have it deleted.
Anyone else think this is a use-case worth supporting better? Or should I jstu get over it -- it's really not that expensive to create a list, after all.
You could use a generator expression with a function that discards the results: def every(iterable): for _ in iterable: pass every(obj.a_method() for obj in a_sequence)
On Thu, Sep 10, 2015 at 12:07 AM, Terry Reedy <tjreedy@udel.edu <mailto:tjreedy@udel.edu>> wrote:
On 9/9/2015 1:10 PM, Stephan Sahm wrote:
I found a BUG in the standard while statement, which appears both in python 2.7 and python 3.4 on my system.
No you did not, but aside from that: python-ideas is for ideas about future versions of python, not for bug reports, valid or otherwise. You should have sent this to python-list, which is a place to report possible bugs.
![](https://secure.gravatar.com/avatar/5dde29b54a3f1b76b2541d0a4a9b232c.jpg?s=120&d=mm&r=g)
On Thu, Sep 10, 2015 at 3:12 PM, MRAB <python@mrabarnett.plus.com> wrote:
You could use a generator expression with a function that discards the results:
def every(iterable): for _ in iterable: pass
every(obj.a_method() for obj in a_sequence)
sure -- though this adds a new function that people reading my code need to grok. Andrew Barnert wrote:
[obj.a_method() for obj in a_sequence]
I think this is an anti-pattern. The point of a comprehension is that it's
an expression, which gathers up results. You're trying to hide side effects inside an expression, which is a bad thing to do, and lamenting the fact that you get a useless value back, which of course you do because expressions have values, so that should be a sign that you don't actually want an expression here.
Exactly -- I don't want a comprehension, I don't want a expression, I want a concise way to spell : do this thing to all of these things.... Also, compare the actual brevity here:
[obj.a_method() for obj in a_sequence] for obj in a_sequence: obj.a_method() You've replaced a colon with a pair of brackets, so it's actually less concise.
Fair enough -- removing a newline does make that pretty simple looking! I guess I got all comprehension-happy there -- back when it was the shiny new toy, and then I got stuck on it. -CHB -- Christopher Barker, Ph.D. Oceanographer Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception Chris.Barker@noaa.gov
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Thu, Sep 10, 2015 at 11:12:39PM +0100, MRAB wrote:
On 2015-09-10 20:04, Chris Barker wrote:
however, he did bring up a python-idea worthy general topic:
Sometimes you want to iterate without doing anything with the results of the iteration.
So the obvious is his example -- iterate N times:
for i in range(N): do_something
but you may need to look into the code (probably more than one line) to see if i is used for anything.
Solution is obvious: for throw_away_variable_not_used_for_anythng in range(N): ... *wink* Just use one of the usual conventions for throw-away variables: call it _ or whocares. But, why do you care so much about whether i is being used for something? Today, you have: for whocares in range(10): print(message) Next week, you decide you need to number them, now you do care about the loop variable: for whocares in range(10): print(whocares, message) Having a loop variable that may remain unused is not exactly a big deal. [Chris]
However, I've found myself wanting a "make nothing comprehension". For some reason, I find myself frequently following a pattern where I want to call the same method on all the objects in a sequence:
for obj in a_sequence: obj.a_method()
but I like the compactness of comprehensions, so I do:
[obj.a_method() for obj in a_sequence]
Ew, ew, ew, ew. You're calling the method for its side-effects, not its return result (which is probably None, but might not be). Turning it into a list comp is abuse of comprehensions: you're collecting the return results, potentially creating an enormous list, which you don't actually want and immediately throw away. Just write it as a one-liner for-loop, which is *more* compact (by exactly one character) as the list comp): [obj.a_method() for obj in a_sequence] for obj in a_sequence: obj.a_method() [MRAB]
You could use a generator expression with a function that discards the results:
def every(iterable): for _ in iterable: pass
every(obj.a_method() for obj in a_sequence)
If you're going to do such a horrid thing, at least name it accurately. "every" sounds like a synonym for the built-in "all". A more accurate name would be "consume", as in consuming the iterator, and I seem to recall that there's a recipe in the itertools docs to do that as fast as possible. But, whether you call it "every" or "consume", the code still looks misleading: every(obj.a_method() for obj in a_sequence) looks like you care about the return results of a_method, but you don't. List comps and generator expressions are for cases where you care about the expression's result. -- Steve
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Sep 10, 2015, at 12:04, Chris Barker <chris.barker@noaa.gov> wrote:
However, I've found myself wanting a "make nothing comprehension". For some reason, I find myself frequently following a pattern where I want to call the same method on all the objects in a sequence:
for obj in a_sequence: obj.a_method()
but I like the compactness of comprehensions, so I do:
[obj.a_method() for obj in a_sequence]
I think this is an anti-pattern. The point of a comprehension is that it's an expression, which gathers up results. You're trying to hide side effects inside an expression, which is a bad thing to do, and lamenting the fact that you get a useless value back, which of course you do because expressions have values, so that should be a sign that you don't actually want an expression here. Also, compare the actual brevity here: [obj.a_method() for obj in a_sequence] for obj in a_sequence: obj.a_method() You've replaced a colon with a pair of brackets, so it's actually less concise. If you really want to do this anyway, you can use the consume recipe from the itertools docs or the more-itertools library or write your own one-liner: consume = partial(deque, maxlen=0) consume(obj.a_method() for obj in a_sequence) At least this makes it explicit that you're creating and ignoring a bunch of values. But I still think it's much clearer to just use a for statement.
participants (9)
-
Andrew Barnert
-
Chris Barker
-
David Blaschke
-
Joseph Jevnik
-
MRAB
-
Petr Viktorin
-
Stephan Sahm
-
Steven D'Aprano
-
Terry Reedy