![](https://secure.gravatar.com/avatar/8450f961b45ebf6b853f219863431f1e.jpg?s=120&d=mm&r=g)
Has anyone considered the idea of adding a "do at least once" loop to Python? This is frequently referred to as a do ... while or repeat ... until. At the moment, it's a bit of a hack to achieve this in that we do a 'while True: ( do thing ; if cond: ( break ) )'. Since I don't know how to format these messages, I've used '{' for line-beak-and-indent, ')' for line-break-and-dedent, and ';' for line-break-keeping-same-indent-level. My initial thoughts are that it would be reasonably easy to add a 'repeat: ( do thing ) until condition' which would far better specify intent of the loop (despite the possibility of break, while-true loops give no indication that it's not an infinite loop. And using repeat...until will ensure whoever had to add the code to the Python interpreter wouldn't have any clashes with the current while loop. Thoughts, anyone? Anyone? Bueller? :-)
![](https://secure.gravatar.com/avatar/3b73b776444fa777acfa37bbdcff23fe.jpg?s=120&d=mm&r=g)
As you probably suspect, yes, it comes up every couple of years. Here's one of the recent threads (there are more, just search for 'until' in the archives), that might give you some ideas for how this discussion will progress. :) https://mail.python.org/archives/list/python-ideas@python.org/thread/EDNARFL... On Tue, Mar 1, 2022 at 7:09 AM <lynneandallan@optusnet.com.au> wrote:
Has anyone considered the idea of adding a "do at least once" loop to Python? This is frequently referred to as a do ... while or repeat ... until.
At the moment, it's a bit of a hack to achieve this in that we do a 'while True: ( do thing ; if cond: ( break ) )'. Since I don't know how to format these messages, I've used '{' for line-beak-and-indent, ')' for line-break-and-dedent, and ';' for line-break-keeping-same-indent-level.
My initial thoughts are that it would be reasonably easy to add a 'repeat: ( do thing ) until condition' which would far better specify intent of the loop (despite the possibility of break, while-true loops give no indication that it's not an infinite loop.
And using repeat...until will ensure whoever had to add the code to the Python interpreter wouldn't have any clashes with the current while loop.
Thoughts, anyone? Anyone? Bueller? :-) _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/2PM6QE... Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/880ac02012dcaf99f0392f69af4f8597.jpg?s=120&d=mm&r=g)
I have use cases for "do exactly once". Basically a sequence of actions which can be broken off (when something goes wrong and the whole process should be aborted, or when something succeeds and there is no need to try alternatives) at various points with `break`. Thus avoiding multiple if...then indentation levels. Of course it can be spelled as for _ in '1': for _ in ['once']: etc. etc., so this is only a cosmetic need. Best wishes Rob Cliffe On 01/03/2022 15:19, Eric Fahlgren wrote:
As you probably suspect, yes, it comes up every couple of years. Here's one of the recent threads (there are more, just search for 'until' in the archives), that might give you some ideas for how this discussion will progress. :)
https://mail.python.org/archives/list/python-ideas@python.org/thread/EDNARFL...
On Tue, Mar 1, 2022 at 7:09 AM <lynneandallan@optusnet.com.au> wrote:
Has anyone considered the idea of adding a "do at least once" loop to Python? This is frequently referred to as a do ... while or repeat ... until.
At the moment, it's a bit of a hack to achieve this in that we do a 'while True: ( do thing ; if cond: ( break ) )'. Since I don't know how to format these messages, I've used '{' for line-beak-and-indent, ')' for line-break-and-dedent, and ';' for line-break-keeping-same-indent-level.
My initial thoughts are that it would be reasonably easy to add a 'repeat: ( do thing ) until condition' which would far better specify intent of the loop (despite the possibility of break, while-true loops give no indication that it's not an infinite loop.
And using repeat...until will ensure whoever had to add the code to the Python interpreter wouldn't have any clashes with the current while loop.
Thoughts, anyone? Anyone? Bueller? :-) _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/2PM6QE... Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list --python-ideas@python.org To unsubscribe send an email topython-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived athttps://mail.python.org/archives/list/python-ideas@python.org/message/BYNJ6C... Code of Conduct:http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/d6eda6eac4cecd80f188ee458d40f921.jpg?s=120&d=mm&r=g)
How does `try/except` (with raise AppropriateException inside the block) compare to a len-1 loop?Om ---- On Tue, 01 Mar 2022 10:04:31 -0600 python-ideas@python.org wrote ---- I have use cases for "do exactly once". Basically a sequence of actions which can be broken off (when something goes wrong and the whole process should be aborted, or when something succeeds and there is no need to try alternatives) at various points with `break`. Thus avoiding multiple if...then indentation levels. Of course it can be spelled as for _ in '1': for _ in ['once']: etc. etc., so this is only a cosmetic need. Best wishes Rob Cliffe On 01/03/2022 15:19, Eric Fahlgren wrote: As you probably suspect, yes, it comes up every couple of years. Here's one of the recent threads (there are more, just search for 'until' in the archives), that might give you some ideas for how this discussion will progress. :) https://mail.python.org/archives/list/python-ideas@python.org/thread/EDNARFL... On Tue, Mar 1, 2022 at 7:09 AM <lynneandallan@optusnet.com.au> wrote: Has anyone considered the idea of adding a "do at least once" loop to Python? This is frequently referred to as a do ... while or repeat ... until. At the moment, it's a bit of a hack to achieve this in that we do a 'while True: ( do thing ; if cond: ( break ) )'. Since I don't know how to format these messages, I've used '{' for line-beak-and-indent, ')' for line-break-and-dedent, and ';' for line-break-keeping-same-indent-level. My initial thoughts are that it would be reasonably easy to add a 'repeat: ( do thing ) until condition' which would far better specify intent of the loop (despite the possibility of break, while-true loops give no indication that it's not an infinite loop. And using repeat...until will ensure whoever had to add the code to the Python interpreter wouldn't have any clashes with the current while loop. Thoughts, anyone? Anyone? Bueller? :-) _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/2PM6QE... Code of Conduct: http://python.org/psf/codeofconduct/ _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/BYNJ6C... Code of Conduct: http://python.org/psf/codeofconduct/ _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/QAGHJP... Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Tue, Mar 01, 2022 at 04:04:31PM +0000, Rob Cliffe via Python-ideas wrote:
I have use cases for "do exactly once". Basically a sequence of actions which can be broken off (when something goes wrong and the whole process should be aborted, or when something succeeds and there is no need to try alternatives) at various points with `break`.
class MyBreak(Exception): pass try: do_this() if condition: raise MyBreak do_that() if condition: raise MyBreak do_next_step() if condition: raise MyBreak do_last_step() except MyBreak: pass -- Steve
![](https://secure.gravatar.com/avatar/d67ab5d94c2fed8ab6b727b62dc1b213.jpg?s=120&d=mm&r=g)
On Wed, 2 Mar 2022 at 09:24, Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, Mar 01, 2022 at 04:04:31PM +0000, Rob Cliffe via Python-ideas wrote:
I have use cases for "do exactly once". Basically a sequence of actions which can be broken off (when something goes wrong and the whole process should be aborted, or when something succeeds and there is no need to try alternatives) at various points with `break`.
class MyBreak(Exception): pass
try: do_this() if condition: raise MyBreak do_that() if condition: raise MyBreak do_next_step() if condition: raise MyBreak do_last_step() except MyBreak: pass
All this is assuming that you can't refactor it into a function and 'return' each time. That's also a viable option, where applicable. ChrisA
![](https://secure.gravatar.com/avatar/880ac02012dcaf99f0392f69af4f8597.jpg?s=120&d=mm&r=g)
On 01/03/2022 22:25, Chris Angelico wrote:
On Wed, 2 Mar 2022 at 09:24, Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, Mar 01, 2022 at 04:04:31PM +0000, Rob Cliffe via Python-ideas wrote:
I have use cases for "do exactly once". Basically a sequence of actions which can be broken off (when something goes wrong and the whole process should be aborted, or when something succeeds and there is no need to try alternatives) at various points with `break`. class MyBreak(Exception): pass
try: do_this() if condition: raise MyBreak do_that() if condition: raise MyBreak do_next_step() if condition: raise MyBreak do_last_step() except MyBreak: pass
All this is assuming that you can't refactor it into a function and 'return' each time. That's also a viable option, where applicable. Yes, it is a viable (even preferable) option when applicable. But there are times when it is not applicable, or is very inconvenient. E.g. when a long list of local variables have to be turned into long verbose error-prone and hard-to maintain lists of parameters and return values (if the code is factored off into a function). Rob Cliffe
ChrisA _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/GN5MM3... Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/d67ab5d94c2fed8ab6b727b62dc1b213.jpg?s=120&d=mm&r=g)
On Wed, 2 Mar 2022 at 10:32, Rob Cliffe via Python-ideas <python-ideas@python.org> wrote:
On 01/03/2022 22:25, Chris Angelico wrote:
On Wed, 2 Mar 2022 at 09:24, Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, Mar 01, 2022 at 04:04:31PM +0000, Rob Cliffe via Python-ideas wrote:
I have use cases for "do exactly once". Basically a sequence of actions which can be broken off (when something goes wrong and the whole process should be aborted, or when something succeeds and there is no need to try alternatives) at various points with `break`. class MyBreak(Exception): pass
try: do_this() if condition: raise MyBreak do_that() if condition: raise MyBreak do_next_step() if condition: raise MyBreak do_last_step() except MyBreak: pass
All this is assuming that you can't refactor it into a function and 'return' each time. That's also a viable option, where applicable. Yes, it is a viable (even preferable) option when applicable. But there are times when it is not applicable, or is very inconvenient. E.g. when a long list of local variables have to be turned into long verbose error-prone and hard-to maintain lists of parameters and return values (if the code is factored off into a function).
Inner functions are spectacular for that :) ChrisA
![](https://secure.gravatar.com/avatar/880ac02012dcaf99f0392f69af4f8597.jpg?s=120&d=mm&r=g)
On 02/03/2022 01:02, Chris Angelico wrote:
On Wed, 2 Mar 2022 at 10:32, Rob Cliffe via Python-ideas <python-ideas@python.org> wrote:
On 01/03/2022 22:25, Chris Angelico wrote:
On Wed, 2 Mar 2022 at 09:24, Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, Mar 01, 2022 at 04:04:31PM +0000, Rob Cliffe via Python-ideas wrote:
I have use cases for "do exactly once". Basically a sequence of actions which can be broken off (when something goes wrong and the whole process should be aborted, or when something succeeds and there is no need to try alternatives) at various points with `break`. class MyBreak(Exception): pass
try: do_this() if condition: raise MyBreak do_that() if condition: raise MyBreak do_next_step() if condition: raise MyBreak do_last_step() except MyBreak: pass
All this is assuming that you can't refactor it into a function and 'return' each time. That's also a viable option, where applicable. Yes, it is a viable (even preferable) option when applicable. But there are times when it is not applicable, or is very inconvenient. E.g. when a long list of local variables have to be turned into long verbose error-prone and hard-to maintain lists of parameters and return values (if the code is factored off into a function).
Inner functions are spectacular for that :)
Well, they can do the job. 'spectacular' is not a word I would use: (a) you have to define your inner function, then call it: def innerfunc(): <possibly long function body> innerfunc() which obscures the control flow. (b) AFAICS If you want to alter variables local to the outer function, you have to declare them non-local in the inner function. (c) AFAIK you can not create identifiers in the inner function which the outer function can "see". Example: def f(): def innerfunc(): foo = 1 innerfunc() print(foo) f() print(foo) fails. You have to create foo in the outer function, then declare it non-local: def f(): foo = None def innerfunc(): nonlocal foo foo = 1 innerfunc() print(foo) f() This "works": print(foo) prints 1. Pretty much as inconvenient as creating parameter and return lists, ISTM. No, so far I have seen no improvement on this which I use (including a suitable helpful comment, as below) in production code: for _ in '1': # Execute this 'loop' once only (break once we know how to ...) <code containing `break`s> Best wishes, Rob Cliffe
![](https://secure.gravatar.com/avatar/d67ab5d94c2fed8ab6b727b62dc1b213.jpg?s=120&d=mm&r=g)
On Wed, 2 Mar 2022 at 12:33, Rob Cliffe via Python-ideas <python-ideas@python.org> wrote:
On 02/03/2022 01:02, Chris Angelico wrote:
On Wed, 2 Mar 2022 at 10:32, Rob Cliffe via Python-ideas <python-ideas@python.org> wrote:
On 01/03/2022 22:25, Chris Angelico wrote:
On Wed, 2 Mar 2022 at 09:24, Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, Mar 01, 2022 at 04:04:31PM +0000, Rob Cliffe via Python-ideas wrote:
I have use cases for "do exactly once". Basically a sequence of actions which can be broken off (when something goes wrong and the whole process should be aborted, or when something succeeds and there is no need to try alternatives) at various points with `break`. class MyBreak(Exception): pass
try: do_this() if condition: raise MyBreak do_that() if condition: raise MyBreak do_next_step() if condition: raise MyBreak do_last_step() except MyBreak: pass
All this is assuming that you can't refactor it into a function and 'return' each time. That's also a viable option, where applicable. Yes, it is a viable (even preferable) option when applicable. But there are times when it is not applicable, or is very inconvenient. E.g. when a long list of local variables have to be turned into long verbose error-prone and hard-to maintain lists of parameters and return values (if the code is factored off into a function).
Inner functions are spectacular for that :)
Well, they can do the job. 'spectacular' is not a word I would use: (a) you have to define your inner function, then call it: def innerfunc(): <possibly long function body> innerfunc() which obscures the control flow.
If this is something you find yourself doing a lot, it wouldn't be hard to make a "breakable" decorator: def breakable(f): return f() def do_lots_of_work(): x, y, z = 1, 2, "spam" @breakable def inner(): if x < y: return print(z) ... This doesn't obscure the control flow any more than a 'while' loop, or any other breakable construct you might come up with. It has a header that says what it does, and then you have the body, which ends at the unindent.
(b) AFAICS If you want to alter variables local to the outer function, you have to declare them non-local in the inner function.
Yes, that's true. But that's not very common. Bear in mind that the inner function can 'return x' to pass a value back, which can easily be captured when you call it (in the decorator example I gave, it's automatically captured into the name of the block), and of course you can mutate any object owned by the outer scope. Chances are you'll never have more than a small handful of nonlocals in the inner function.
(c) AFAIK you can not create identifiers in the inner function which the outer function can "see". Example:
def f(): def innerfunc(): foo = 1 innerfunc() print(foo) f()
print(foo) fails. You have to create foo in the outer function, then declare it non-local:
def f(): foo = None def innerfunc(): nonlocal foo foo = 1 innerfunc() print(foo)
f()
This "works": print(foo) prints 1.
This is also true, but for the use-case you're describing, where you want to break at arbitrary points, it seems highly likely that you'd want to initialize it to some default. So in theory, this is a problem, but in practice, almost never.
Pretty much as inconvenient as creating parameter and return lists, ISTM. No, so far I have seen no improvement on this which I use (including a suitable helpful comment, as below) in production code:
Not NEARLY as inconvenient in practice. I don't have absolute proof of this, but I can assure you that when I need to break out a function in a language that doesn't support closures (like deferring execution of part of some code in SourcePawn - the only way is to create a standalone function, set a timer to call the function at the right point, and pass it al the arguments), it feels way WAY clunkier than simply referencing variables. The main reason for this is that it's a lot more common to *read* variables from outer scopes than to *write* to them.
for _ in '1': # Execute this 'loop' once only (break once we know how to ...) <code containing `break`s>
That's also viable, and personally, I'd probably prefer this over "while True:" and "break" at the end, although I've used both. There are many ways to do things, and very few design decisions are fundamentally WRONG. They all have consequences, and you can accept whichever consequences you choose. ChrisA
![](https://secure.gravatar.com/avatar/880ac02012dcaf99f0392f69af4f8597.jpg?s=120&d=mm&r=g)
On Wed, 2 Mar 2022 at 12:33, Rob Cliffe via Python-ideas <python-ideas@python.org> wrote:
On 02/03/2022 01:02, Chris Angelico wrote:
On Wed, 2 Mar 2022 at 10:32, Rob Cliffe via Python-ideas <python-ideas@python.org> wrote:
On 01/03/2022 22:25, Chris Angelico wrote:
On Wed, 2 Mar 2022 at 09:24, Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, Mar 01, 2022 at 04:04:31PM +0000, Rob Cliffe via Python-ideas wrote:
> I have use cases for "do exactly once". > Basically a sequence of actions which can be broken off (when something > goes wrong and the whole process should be aborted, or when something > succeeds and there is no need to try alternatives) at various points > with `break`. class MyBreak(Exception): pass
try: do_this() if condition: raise MyBreak do_that() if condition: raise MyBreak do_next_step() if condition: raise MyBreak do_last_step() except MyBreak: pass
All this is assuming that you can't refactor it into a function and 'return' each time. That's also a viable option, where applicable. Yes, it is a viable (even preferable) option when applicable. But there are times when it is not applicable, or is very inconvenient. E.g. when a long list of local variables have to be turned into long verbose error-prone and hard-to maintain lists of parameters and return values (if the code is factored off into a function).
Inner functions are spectacular for that :)
Well, they can do the job. 'spectacular' is not a word I would use: (a) you have to define your inner function, then call it: def innerfunc(): <possibly long function body> innerfunc() which obscures the control flow.
If this is something you find yourself doing a lot, it wouldn't be hard to make a "breakable" decorator:
def breakable(f): return f()
def do_lots_of_work(): x, y, z = 1, 2, "spam" @breakable def inner(): if x < y: return print(z) ...
This doesn't obscure the control flow any more than a 'while' loop, It certainly does! I see a decorated function. Nothing tells me that
On 02/03/2022 02:01, Chris Angelico wrote: the decorator actually *calls* the function. This really is obscurantism.
or any other breakable construct you might come up with. It has a header that says what it does, and then you have the body, which ends at the unindent.
(b) AFAICS If you want to alter variables local to the outer function, you have to declare them non-local in the inner function. Yes, that's true. But that's not very common. That's your opinion. It happens in my code. Bear in mind that the inner function can 'return x' to pass a value back, which can easily be captured when you call it (in the decorator example I gave, it's automatically captured into the name of the block), and of course you can mutate any object owned by the outer scope. Chances are you'll never have more than a small handful of nonlocals in the inner function. It's still extra boilerplate to be (mis-)managed.
(c) AFAIK you can not create identifiers in the inner function which the outer function can "see". Example:
def f(): def innerfunc(): foo = 1 innerfunc() print(foo) f()
print(foo) fails. You have to create foo in the outer function, then declare it non-local:
def f(): foo = None def innerfunc(): nonlocal foo foo = 1 innerfunc() print(foo)
f()
This "works": print(foo) prints 1. This is also true, but for the use-case you're describing, where you want to break at arbitrary points, it seems highly likely that you'd want to initialize it to some default. So in theory, this is a problem, but in practice, almost never. AFAIK it doesn't happen in my code, but I can easily imagine that it might.
Pretty much as inconvenient as creating parameter and return lists, ISTM. No, so far I have seen no improvement on this which I use (including a suitable helpful comment, as below) in production code: Not NEARLY as inconvenient in practice. I don't have absolute proof of this, but I can assure you that when I need to break out a function in a language that doesn't support closures (like deferring execution of part of some code in SourcePawn - the only way is to create a standalone function, set a timer to call the function at the right point, and pass it al the arguments), it feels way WAY clunkier than simply referencing variables. The main reason for this is that it's a lot more common to *read* variables from outer scopes than to *write* to them.
for _ in '1': # Execute this 'loop' once only (break once we know how to ...) <code containing `break`s>
You've not convinced me that there is anything better than the above (for my needs). No new idioms, no opaque decorators, no obscuring the control flow, no need to worry about variable scopes. I would like a better way to spell it, but that's a minor cosmetic issue.
That's also viable, and personally, I'd probably prefer this over "while True:" and "break" at the end, I hope so. "while True:" suggests a real loop. And risks you forgetting the final "break". Rob Cliffe although I've used both.
There are many ways to do things, and very few design decisions are fundamentally WRONG. They all have consequences, and you can accept whichever consequences you choose.
ChrisA _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/ZPXCFP... Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/d67ab5d94c2fed8ab6b727b62dc1b213.jpg?s=120&d=mm&r=g)
On Wed, 2 Mar 2022 at 18:35, Rob Cliffe via Python-ideas <python-ideas@python.org> wrote:
This doesn't obscure the control flow any more than a 'while' loop, It certainly does! I see a decorated function. Nothing tells me that
On 02/03/2022 02:01, Chris Angelico wrote: the decorator actually *calls* the function. This really is obscurantism.
"while True:" doesn't say that the loop will be executed at most once. How is this any worse? Given that you get to choose the name of the function AND the name of the decorator, I hardly think that you're really losing out here/
(b) AFAICS If you want to alter variables local to the outer function, you have to declare them non-local in the inner function. Yes, that's true. But that's not very common. That's your opinion. It happens in my code.
Can you post an example where you need a large number of nonlocals? A single nonlocal is hardly a major problem.
This is also true, but for the use-case you're describing, where you want to break at arbitrary points, it seems highly likely that you'd want to initialize it to some default. So in theory, this is a problem, but in practice, almost never. AFAIK it doesn't happen in my code, but I can easily imagine that it might.
Like I said, in theory a problem, in practice, not. I have *never once* run into this; in fact, I had to quickly check a couple of options to confirm that they indeed didn't work, because I've never been in a position of needing that.
for _ in '1': # Execute this 'loop' once only (break once we know how to ...) <code containing `break`s>
You've not convinced me that there is anything better than the above (for my needs). No new idioms, no opaque decorators, no obscuring the control flow, no need to worry about variable scopes. I would like a better way to spell it, but that's a minor cosmetic issue.
Very minor. You have quite a number of alternatives to choose from, and not one of them needs new syntax. ChrisA
![](https://secure.gravatar.com/avatar/880ac02012dcaf99f0392f69af4f8597.jpg?s=120&d=mm&r=g)
On 01/03/2022 22:25, Chris Angelico wrote:
class MyBreak(Exception): pass
try: do_this() if condition: raise MyBreak do_that() if condition: raise MyBreak do_next_step() if condition: raise MyBreak do_last_step() except MyBreak: pass
It doesn't feel right to me to use exceptions purely to direct control flow. YMMV. As I see it, the original meaning of an exception (in whatever language) is "something unexpected has happened" or "something has gone wrong". While it is too narrow to insist that they only be used with that meaning, this use doesn't feel like a good fit. Rob Cliffe
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Tue, Mar 01, 2022 at 11:50:42PM +0000, Rob Cliffe via Python-ideas wrote:
It doesn't feel right to me to use exceptions purely to direct control flow. YMMV.
Then Python is the wrong language for you, because it uses exceptions to direct control flow *wink* The iteration protocol uses StopIteration to end iteration. The older sequence protocol uses IndexError for the same purpose.
As I see it, the original meaning of an exception (in whatever language) is "something unexpected has happened" or "something has gone wrong".
No. An exception is *something EXCEPTIONAL has happened*. That could be something unexpected, or an error, but it can also be something boringly expected and normal, such as KeyError (key not found), or StopIteration (finite iterator has reached the end) etc. Really, pretty much every SpamError exception can be considered a non-error, depending on the semantics of how it is being used. duck.quack # AttributeError signals an error condition dog.quack # AttributeError signals the expected, non-error condition but this goes double for KeyError, where "missing key" is boringly normal. Using signals for flow control is an old, and fundamental, technique. Exceptions are just another kind of signal. https://ix.cs.uoregon.edu/~norris/cis330/slides/CIS330_Week9.pdf -- Steve
![](https://secure.gravatar.com/avatar/b01753e0c78849bd34045ed730d59db6.jpg?s=120&d=mm&r=g)
On Tue, Mar 1, 2022 at 4:51 PM Steven D'Aprano <steve@pearwood.info> wrote:
Then Python is the wrong language for you, because it uses exceptions to direct control flow *wink*
The iteration protocol uses StopIteration to end iteration. The older sequence protocol uses IndexError for the same purpose.
I think it's better to say that exceptions are used in these cases to cover for the lack of disjoint-union types. Many functions return "a value or no value". When the values they can return are limited, a value they can't return is often used as a stand-in for "no value", like None or NotImplemented, or -1 for find(). When that isn't the case, an exception tends to be used, although letting the caller choose a sentinel is also popular. Haskell tends to use Maybe in all of these cases, because it's a disjoint union: the no-value return can't overlap with the yes-value returns, so there's never a need for anything else. (And it forces you to unwrap the returned value, so you can't forget to check for the no-value case.) The MyBreak proposal doesn't really fit that pattern. I've never seen it used, and I feel like it would be rejected in most places that have style guides and code review, but perhaps I'm wrong.
![](https://secure.gravatar.com/avatar/72ee673975357d43d79069ac1cd6abda.jpg?s=120&d=mm&r=g)
On 2/03/22 12:50 pm, Rob Cliffe via Python-ideas wrote:
As I see it, the original meaning of an exception (in whatever language) is "something unexpected has happened" or "something has gone wrong".
Using exceptions for flow control has always been acceptable in Python. The iterator protocol relies on it, for example. -- Greg
![](https://secure.gravatar.com/avatar/b01753e0c78849bd34045ed730d59db6.jpg?s=120&d=mm&r=g)
On Tue, Mar 1, 2022 at 2:23 PM Steven D'Aprano <steve@pearwood.info> wrote:
try: do_this() if condition: raise MyBreak do_that() if condition: raise MyBreak do_next_step() if condition: raise MyBreak do_last_step() except MyBreak: pass
Why not: while True: [loop body with ordinary breaks] break It's less to write and almost free of charge*, and it also supports redo (continue). MyBreak has the advantage that you can raise it in nested loops and subfunctions, although that could be a disadvantage since it makes the control flow confusing. This goes for repeat-until also: I always write it while True: ... if cond: break. I just grepped through some personal Python projects and about 40% of my while-loop conditions are True. * The break at the end is free, but Python 3.10.2 inserts a NOP at the location of the while True, and the peephole optimizers of earlier Python versions have done other things. It looks like there's been a lot of work on bytecode efficiency in 3.11, so maybe it will become truly free, but I haven't checked. 3.11 has range-based exception handling (issue 40222), so the try: approach is free if you don't raise MyBreak (but expensive if you do).
![](https://secure.gravatar.com/avatar/880ac02012dcaf99f0392f69af4f8597.jpg?s=120&d=mm&r=g)
On 01/03/2022 23:57, Ben Rudiak-Gould wrote:
On Tue, Mar 1, 2022 at 2:23 PM Steven D'Aprano <steve@pearwood.info> wrote:
try: do_this() if condition: raise MyBreak do_that() if condition: raise MyBreak do_next_step() if condition: raise MyBreak do_last_step() except MyBreak: pass
Why not:
while True: [loop body with ordinary breaks] break
It's less to write and almost free of charge*, and it also supports redo (continue).
"Almost free" is not free. One cost is when you forget to add the final `break`. (I'm sure I've done it!) More important, it obscures the fact that the "loop" is intended to be executed only once (particularly if the loop body is long, so that the final `break` is a long way from the `while True`). `while True` normally introduces a loop that can be executed multiple times or indefinitely. Best wishes Rob cliffe
![](https://secure.gravatar.com/avatar/031ef3f855faf3a584e8a1fbf000233c.jpg?s=120&d=mm&r=g)
If you don't like: while True: ... if whatever: break One thing I've seen people do is: condition = True while condition: ... condition = whatever You can use it if you really hate `while True` loops with `break`.
![](https://secure.gravatar.com/avatar/e44b7db2f44578dfb6c1623e62c162c3.jpg?s=120&d=mm&r=g)
Along similar lines, you could also use the fact that Python does lazy-evaluation to make a do-while which forward-references variables: ``` enter_dw = True while enter_dw or (condition_with_vars_not_defined_the_first_time_through): enter_dw = False define_those_vars() ``` Sketch of a use case: get user to input a number greater than 3. ``` enter_dw = True while enter_dw or x <= 3: enter_dw = False x = int(input("enter a number greater than 3: ")) ``` Though the enter_dw variable is a bit clunky. Tangentially, using this same pattern you could also turn `for x in range(10)` into a C-style for loop ``` x = 0 while x < 10: do_something() x += 1 ``` so you have more control of x inside the loop (ie not just incrementing each cycle like the range). ---- On Tue, 01 Mar 2022 12:59:26 -0600 Kevin Mills <kevin.mills226@gmail.com> wrote ----
If you don't like:
while True: ... if whatever: break
One thing I've seen people do is:
condition = True while condition: ... condition = whatever
You can use it if you really hate `while True` loops with `break`. _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/4BSR3Y... Code of Conduct: http://python.org/psf/codeofconduct/
participants (10)
-
Ben Rudiak-Gould
-
Chris Angelico
-
Eric Fahlgren
-
Greg Ewing
-
Kevin Mills
-
lynneandallan@optusnet.com.au
-
om
-
Om Joshi
-
Rob Cliffe
-
Steven D'Aprano