PEP: 548 Title: More Flexible Loop Control Version: $Revision$ Last-Modified: $Date$ Author: R David Murray Status: Rejected Type: Standards Track Content-Type: text/x-rst Created: 05-Sep-2017 Python-Version: 3.7 Post-History: 05-Aug-2017 Rejection Note ============== Rejection by Guido: https://mail.python.org/pipermail/python-dev/2017-September/149232.html Abstract ======== This PEP proposes enhancing the ``break`` and ``continue`` statements with an optional boolean expression that controls whether or not they execute. This allows the flow of control in loops to be expressed more clearly and compactly. Motivation ========== Quoting from the rejected :pep:`315`: It is often necessary for some code to be executed before each evaluation of the while loop condition. This code is often duplicated outside the loop, as setup code that executes once before entering the loop:: while : That PEP was rejected because no syntax was found that was superior to the following form:: while True: if not : break This PEP proposes a superior form, one that also has application to for loops. It is superior because it makes the flow of control in loops more explicit, while preserving Python's indentation aesthetic. Syntax ====== The syntax of the break and continue statements are extended as follows:: break_stmt : "break" ["if" expression] continue_stmt : "continue" ["if" expression] In addition, the syntax of the while statement is modified as follows:: while_stmt : while1_stmt|while2_stmt while1_stmt : "while" expression ":" suite ["else" ":" suite] while2_stmt : "while" ":" suite Semantics ========= A ``break if`` or ``continue if`` is executed if and only if ``expression`` evaluates to true. A `while` statement with no expression loops until a break or return is executed (or an error is raised), as if it were a `'while True`` statement. Given that the loop can never terminate except in a way that would not cause an ``else`` suite to execute, no ``else`` suite is allowed in the expressionless form. If practical, it should also be an error if the body of an expressionless ``while`` does not contain at least one ``break`` or ``return`` statement. Justification and Examples ========================== The previous "best possible" form:: while True: if not : break could be formatted as:: while True: if not : break This is superficially almost identical to the form proposed by this PEP:: while: break if not The significant difference here is that the loop flow control keyword appears *first* in the line of code. This makes it easier to comprehend the flow of control in the loop at a glance, especially when reading colorized code. For example, this is a common code pattern, taken in this case from the tarfile module:: while True: buf = self._read(self.bufsize) if not buf: break t.append(buf) Reading this, we either see the break and possibly need to think about where the while is that it applies to, since the break is indented under the if, and then track backward to read the condition that triggers it; or, we read the condition and only afterward discover that this condition changes the flow of the loop. With the new syntax this becomes:: while: buf = self._read(self.bufsize) break if not buf t.append(buf) Reading this we first see the``break``, which obviously applies to the while since it is at the same level of indentation as the loop body, and then we read the condition that causes the flow of control to change. Further, consider a more complex example from sre_parse:: while True: c = self.next self.__next() if c is None: if not result: raise self.error("missing group name") raise self.error("missing %s, unterminated name" % terminator, len(result)) if c == terminator: if not result: raise self.error("missing group name", 1) break result += c return result This is the natural way to write this code given current Python loop control syntax. However, given ``break if``, it would be more natural to write this as follows:: while: c = self.next self.__next() break if c is None or c == terminator result += c if not result: raise self.error("missing group name") elif c is None: raise self.error("missing %s, unterminated name" % terminator, len(result)) return result This form moves the error handling out of the loop body, leaving the loop logic much more understandable. While it would certainly be possible to write the code this way using the current syntax, the proposed syntax makes it more natural to write it in the clearer form. The proposed syntax also provides a natural, Pythonic spelling of the classic ``repeat ... until `` construct found in other languages, and for which no good syntax has previously been found for Python:: while: ... break if The tarfile module, for example, has a couple of "read until" loops like the following:: while True: s = self.__read(1) if not s or s == NUL: break With the new syntax this would read more clearly:: while: s = self.__read(1) break if not s or s == NUL The case for extending this syntax to ``continue`` is less strong, but buttressed by the value of consistency. It is much more common for a ``continue`` statement to be at the end of a multiline if suite, such as this example from zipfile :: while True: try: self.fp = io.open(file, filemode) except OSError: if filemode in modeDict: filemode = modeDict[filemode] continue raise break The only opportunity for improvement the new syntax would offer for this loop would be the omission of the ``True`` token. On the other hand, consider this example from uuid.py:: for i in range(adapters.length): ncb.Reset() ncb.Command = netbios.NCBRESET ncb.Lana_num = ord(adapters.lana[i]) if win32wnet.Netbios(ncb) != 0: continue ncb.Reset() ncb.Command = netbios.NCBASTAT ncb.Lana_num = ord(adapters.lana[i]) ncb.Callname = '*'.ljust(16) ncb.Buffer = status = netbios.ADAPTER_STATUS() if win32wnet.Netbios(ncb) != 0: continue status._unpack() bytes = status.adapter_address[:6] if len(bytes) != 6: continue return int.from_bytes(bytes, 'big') This becomes:: for i in range(adapters.length): ncb.Reset() ncb.Command = netbios.NCBRESET ncb.Lana_num = ord(adapters.lana[i]) continue if win32wnet.Netbios(ncb) != 0 ncb.Reset() ncb.Command = netbios.NCBASTAT ncb.Lana_num = ord(adapters.lana[i]) ncb.Callname = '*'.ljust(16) ncb.Buffer = status = netbios.ADAPTER_STATUS() continue if win32wnet.Netbios(ncb) != 0 status._unpack() bytes = status.adapter_address[:6] continue if len(bytes) != 6 return int.from_bytes(bytes, 'big') This example indicates that there are non-trivial use cases where ``continue if`` also improves the readability of the loop code. It is probably significant to note that all of the examples selected for this PEP were found by grepping the standard library for ``while True`` and ``continue``, and the relevant examples were found in the first four modules inspected. Copyright ========= This document is placed in the public domain.