<div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">On 2 March 2017 at 21:06, Wolfgang Maier <span dir="ltr"><<a href="mailto:wolfgang.maier@biologie.uni-freiburg.de" target="_blank">wolfgang.maier@biologie.uni-freiburg.de</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><span class="gmail-">On 02.03.2017 06:46, Nick Coghlan wrote:</span><br><div><div class="gmail-h5"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
The proposal in this thread then has the significant downside of only<br>
covering the "nested side effect" case:<br>
<br>
    for item in iterable:<br>
        if condition(item):<br>
            break<br>
    except break:<br>
        operation(item)<br>
    else:<br>
        condition_was_never_true(itera<wbr>ble)<br>
<br>
While being even *less* amenable to being pushed down into a helper<br>
function (since converting the "break" to a "return" would bypass the<br>
"except break" clause).<br>
</blockquote>
<br></div></div>
I'm actually not quite buying this last argument. If you wanted to refactor this to "return" instead of "break", you could simply put the return into the except break block. In many real-world situations with multiple breaks from a loop this could actually make things easier instead of worse.<br></blockquote><div><br></div><div>Fair point - so that would be even with the "single nested side effect" case, but simpler when you had multiple break conditions (and weren't already combined them with "and").<br></div><div><br> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
Personally, the "nested side effect" form makes me uncomfortable every time I use it because the side effects on breaking or not breaking the loop don't end up at the same indentation level and not necessarily together. However, I'm gathering from the discussion so far that not too many people are thinking like me about this point, so maybe I should simply adjust my mind-set.<br></blockquote><div><br></div><div>This is why I consider the "search only" form of the loop, where the else clause either sets a default value, or else prevents execution of the code after the loop body (via raise, return, or continue), to be the preferred form: there aren't any meaningful side effects hidden away next to the break statement. If I can't do that, I'm more likely to switch to a classic flag variable that gets checked post-loop execution than I am to push the side effect inside the loop body:<br><br></div><div>    search_result = _not_found = object()<br></div><div>    for item in iterable:<br></div><div>        if condition(item):<br></div><div>            search_result = item<br></div><div>            break<br></div><div>    if search_result is _not_found:<br></div><div>        # Handle the "not found" case<br></div><div>    else:<br></div><div>        # Handle the "found" case<br></div><div>     <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
All that said, this is a very nice abstract view on things! I really learned quite a bit from this, thank you :)<br>
<br>
As always though, reality can be expected to be quite a bit more complicated than theory so I decided to check the stdlib for real uses of break. This is quite a tedious task since break is used in many different ways and I couldn't come up with a good automated way of classifying them. So what I did is just go through stdlib code (in reverse alphabetical order) containing the break keyword and put it into categories manually. I only got up to socket.py before losing my enthusiasm, but here's what I found:<br>
<br>
- overall I looked at 114 code blocks that contain one or more breaks<br></blockquote><div><br></div><div>Thanks for doing that research :)<br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
Of the remaining 19 non-trivial cases<br>
<br>
- 9 are variations of your classical search idiom above, i.e., there's an else clause there and nothing more is needed<br>
<br>
- 6 are variations of your "nested side-effects" form presented above with debatable (see above) benefit from except break<br>
<br>
- 2 do not use an else clause currently, but have multiple breaks that do partly redundant things that could be combined in a single except break clause<br></blockquote><div><br></div><div>Those 8 cases could also be reviewed to see whether a flag variable might be clearer than relying on nested side effects or code repetition.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">

- 1 is an example of breaking out of two loops; from sre_parse._parse_sub:<br>
<br>
[...]<br>
    # check if all items share a common prefix<br>
    while True:<br>
        prefix = None<br>
        for item in items:<br>
            if not item:<br>
                break<br>
            if prefix is None:<br>
                prefix = item[0]<br>
            elif item[0] != prefix:<br>
                break<br>
        else:<br>
            # all subitems start with a common "prefix".<br>
            # move it out of the branch<br>
            for item in items:<br>
                del item[0]<br>
            subpatternappend(prefix)<br>
            continue # check next one<br>
        break<br>
[...]<br></blockquote><div><br></div><div>This is a case where a flag variable may be easier to read than loop state manipulations:<br><br></div><div>    may_have_common_prefix = True<br></div><div>    while may_have_common_prefix:<br></div><div>        prefix = None<br></div><div>        for item in items:<br></div><div>            if not item:<br></div><div>                may_have_common_prefix = False<br></div><div>                break<br>            if prefix is None:<br>
                prefix = item[0]<br>
            elif item[0] != prefix:<br><div>                may_have_common_prefix = False<br></div>
                break<br>
        else:<br>
            # all subitems start with a common "prefix".<br>
            # move it out of the branch<br>
            for item in items:<br>
                del item[0]<br>
            subpatternappend(prefix)<br><br></div><div>Although the whole thing could likely be cleaned up even more via itertools.zip_longest:<br><br></div>    for first_uncommon_idx, aligned_entries in enumerate(itertools.zip_longest(*items)):<br><div>        if not all_true_and_same(aligned_entries):<br></div><div>            break<br></div><div>    else:<br></div><div>        # Everything was common, so clear all entries<br></div><div>        first_uncommon_idx = None<br></div><div>    for item in items:<br></div><div>        del item[:first_uncommon_idx]<br><br></div><div>(Batching the deletes like that may even be slightly faster than deleting common entries one at a time)<br></div><div><br></div><div>Given the following helper function:<br></div><div><br><div>    def all_true_and_same(entries):<br></div><div>        itr = iter(entries)<br></div><div>        try:<br></div><div>            first_entry = next(itr)<br></div><div>        except StopIteration:<br></div><div>            return False<br></div><div>        if not first_entry:<br></div><div>            return False<br></div><div>        for entry in itr:<br></div><div>            if not entry or entry != first_entry:<br></div><div>                return False<br></div><div>        return True<br></div></div> <blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
- finally, 1 is a complicated break dance to achieve sth that clearly would have been easier with except break; from typing.py:<br>
<br>
[...]<br>
    def __subclasscheck__(self, cls):<br>
        if cls is Any:<br>
            return True<br>
        if isinstance(cls, GenericMeta):<br>
            # For a class C(Generic[T]) where T is co-variant,<br>
            # C[X] is a subclass of C[Y] iff X is a subclass of Y.<br>
            origin = self.__origin__<br>
            if origin is not None and origin is cls.__origin__:<br>
                assert len(self.__args__) == len(origin.__parameters__)<br>
                assert len(cls.__args__) == len(origin.__parameters__)<br>
                for p_self, p_cls, p_origin in zip(self.__args__,<br>
                                                   cls.__args__,<br>
                                                   origin.__parameters__):<br>
                    if isinstance(p_origin, TypeVar):<br>
                        if p_origin.__covariant__:<br>
                            # Covariant -- p_cls must be a subclass of p_self.<br>
                            if not issubclass(p_cls, p_self):<br>
                                break<br>
                        elif p_origin.__contravariant__:<br>
                            # Contravariant.  I think it's the opposite. :-)<br>
                            if not issubclass(p_self, p_cls):<br>
                                break<br>
                        else:<br>
                            # Invariant -- p_cls and p_self must equal.<br>
                            if p_self != p_cls:<br>
                                break<br>
                    else:<br>
                        # If the origin's parameter is not a typevar,<br>
                        # insist on invariance.<br>
                        if p_self != p_cls:<br>
                            break<br>
                else:<br>
                    return True<br>
                # If we break out of the loop, the superclass gets a chance.<br>
        if super().__subclasscheck__(cls)<wbr>:<br>
            return True<br>
        if self.__extra__ is None or isinstance(cls, GenericMeta):<br>
            return False<br>
        return issubclass(cls, self.__extra__)<br>
[...]<br></blockquote><div><br></div></div>I think is another case that is asking for the inner loop to be factored out to a named function, not for reasons of re-use, but for reasons of making the code more readable and self-documenting :)<br><br></div><div class="gmail_extra">Cheers,<br></div><div class="gmail_extra">Nick.<br clear="all"></div><div class="gmail_extra"><br>-- <br><div class="gmail_signature">Nick Coghlan   |   <a href="mailto:ncoghlan@gmail.com" target="_blank">ncoghlan@gmail.com</a>   |   Brisbane, Australia</div>
</div></div>