1. Proposal
Currently, each compound statement has a subset of clauses, but neither of them has a full set (union).
I think by now, all clauses have proven to be useful python constructs and it might be worthwhile considering sharing the full set among all of the compound statements.
<MAIN STATEMENT CLAUSE(S)>
elif cond2:# *
expr3()
else: # ?
expr4()
finally: # ?
expr5()
# where <MAIN STATEMENT CLAUSE(S)> are one of the following:
# 1. Conditional
if cond1:
expr1()
# 2.
try: Try-except
expr1()
except exc:# * (if ommitted, only single `finally` is allowed)
expr2()
# 3. While statement
while cond:
expr1()
# 4. Compound iterative
for target in iter:
expr1()
2. Use Cases
Requests from users:
CPython code base:
'try\:.*else\:\s*if.*' --nlines=10 | wc -l
1.1 946 / 11 ~ 86
1.2 With site-packages: 4774 / 11 ~ 434
1.3 inspect.py: 99 / 11 ~ 9czg ''try\:\s*if .*\:.*finally.*'' . --nlines=10 | wc -l
2.1 649 / 11 ~ 59
2.2 With site-packages: 2179 / 11 ~ 198czg 'try\:\s*for .*\:.*\:.*finally.*' . -cG --nlines=10 | wc -l
3.1 253 / 11 ~ 23
3.2 With site-packages: 625 / 11 ~ 57
It most likely has false positives, but overestimation in general is not very likely. I looked into inspect
and all cases are valid. Also, this only tests for 4 cases and only registers hits that span maximum 10 lines.
For CPython repo (compared to things I have looked at before) these are quite big numbers.
One example of use case research of CPython vs Github: \.count\(.*\).*[><=]
- cpython: 38
- github: 432K
- Which is ~ 1 / 11000.
Multiline search on Github is not provided (at least as far as I know), so it is only a guess that the ratio could be similar.
A concrete case from each of the searches:
# asyncio/unix_events.py:548:0
try:
data = os.read(self._fileno, self.max_size)
except (BlockingIOError, InterruptedError):
pass
except OSError as exc:
self._fatal_error(exc, 'Fatal read error on pipe transport')
else:
if data:
self._protocol.data_received(data)
else:
if self._loop.get_debug():
logger.info("%r was closed by peer", self)
...
-->
try:
data = os.read(self._fileno, self.max_size)
except (BlockingIOError, InterruptedError):
pass
except OSError as exc:
self._fatal_error(exc, 'Fatal read error on pipe transport')
elif data:
self._protocol.data_received(data)
elif self._loop.get_debug():
logger.info("%r was closed by peer", self)
# logging/handlers.py:1406:0
try:
if self.flushOnClose:
self.flush()
finally:
with self.lock:
self.target = None
BufferingHandler.close(self)
-->
if self.flushOnClose:
self.flush()
finally:
with self.lock:
self.target = None
BufferingHandler.close(self)
# asyncio/proactor_events.py:748
try:
while True:
blocksize = min(end_pos - offset, blocksize)
if blocksize <= 0:
return total_sent
await self._proactor.sendfile(sock, file, offset, blocksize)
offset += blocksize
total_sent += blocksize
finally:
if total_sent > 0:
file.seek(offset)
-->
while True:
blocksize = min(end_pos - offset, blocksize)
if blocksize <= 0:
return total_sent
await self._proactor.sendfile(sock, file, offset, blocksize)
offset += blocksize
total_sent += blocksize
finally:
if total_sent > 0:
file.seek(offset)
3. Benefits
- Learning - once one statement is learned, only 1 clause is left to learn for another one.
try-except
would have either 2+ clauses or special case oftry-finally
(as it is now). It might provide a bit steeper learning curve at the start, but for the longer term benefit. - More readable code. It would provide code simplifications and would eliminate a portion of nested compound statements.
- General consistency, thus better code. E.g. frameworks that build compound statement ASTs could re-use code and simplify the logic.
It is backwards compatible (at least it seems so).
Would be very interested to get some feedback for this