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 <st...@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
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@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/ZPXCFPNSVIIV2TLTBEMMSEEJZZNEENKL/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to