diff --git a/pep-0548.rst b/pep-0548.rst new file mode 100644 index 000000000..14a9f69ff --- /dev/null +++ b/pep-0548.rst @@ -0,0 +1,267 @@ +PEP: ??? +Title: More Flexible Loop Control +Version: $Revision$ +Last-Modified: $Date$ +Author: R. David Murray +Status: Draft +Type: Standards Track +Content-Type: text/x-rst +Created: 05-Sep-2017 +Python-Version: 3.7 +Post-History: + + +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 : whlie1_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 + + 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.