281 lines
8.0 KiB
ReStructuredText
281 lines
8.0 KiB
ReStructuredText
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::
|
|
|
|
<setup code>
|
|
while <condition>:
|
|
<loop body>
|
|
<setup code>
|
|
|
|
That PEP was rejected because no syntax was found that was superior
|
|
to the following form::
|
|
|
|
while True:
|
|
<setup code>
|
|
if not <condition>:
|
|
break
|
|
<loop body>
|
|
|
|
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:
|
|
<setup code>
|
|
if not <condition>:
|
|
break
|
|
<loop body>
|
|
|
|
could be formatted as::
|
|
|
|
while True:
|
|
<setup code>
|
|
if not <condition>: break
|
|
<loop body>
|
|
|
|
This is superficially almost identical to the form proposed by this
|
|
PEP::
|
|
|
|
while:
|
|
<setup code>
|
|
break if not <condition>
|
|
<loop body>
|
|
|
|
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 <expression>`` construct found in
|
|
other languages, and for which no good syntax has previously been
|
|
found for Python::
|
|
|
|
while:
|
|
...
|
|
break if <expression>
|
|
|
|
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.
|