The issue
I will start with from where I got this idea. During discussion about elif
in compound statements some of the points were about how keyword else
is often unintuitive. Here are some summaries of understanding the keyword:.
I always understood it differently. Maybe, because I have learned to read assembly before attempting to learn Python, I have never seen any issue with else
clause after loops. For me, there are only 2 cases:.
- After
if
,while
orfor
clause,else
clause is entered, only if condition check was reached and was false. - After
try-except
clauseselse
clause is entered, only iftry
finished with no exception.
Loops
Reasoning for loops is simple. The for
loop is a fancy while
loop, so let’s focus on while
loop with else
clause. Look at the example:.
while loop_condition:
...
if break_condition: break;
...
else:
...
while
loop can be broken into goto
jumps (in C):.
loop_start: if (loop_condition) {
/*...*/
if (break_condition) goto loop_end;
/*...*/
goto loop_start;
} else {
/*...*/
} loop_end:
Translating more (into assembly) would also break the else
block into jumps, but (in short) that’s how I understand loops with else
clause.
Try statement
Imagine, U don’t know what else
in try statement mean, but U know everything else about try statement and about else
keyword in different statements. How would U understand else
inside try statement, just by looking at it? Well, in all other cases there exist a clear (on code read) condition to enter the block before else
clause:.
if condition: ...
else: ...
while condition: ...
else: ...
for element in collection: ...
else: ...
So, let’s look at try statement:.
try: # No condition.
...
except type1: # If any exception was raised,
# checks if raised exception is of type ``type1``.
...
except type2: # If previous condition was checked and was false,
# checks if raised exception is of type ``type2``.
...
else: # ...?
...
finally: # No condition.
...
Logically, else
should do what it always does. Perform actions in its body (“suite”), if previous condition was checked and was false. In other words, exception was raised and didn’t match any of the types in all except
checks, or just in the one before else
. The complete opposite of what it actually does.
Ok, I kind of cheated. I didn’t include the default except
, but note, it still doesn’t make any sense:.
try: # No condition.
...
except type1: # If any exception was raised,
# checks if raised exception is of type ``type1``.
...
except type2: # If previous condition was checked and was false,
# checks if raised exception is of type ``type2``.
...
except: # If previous condition was checked and was false.
...
else: # ...? There is no previous condition.
...
finally: # No condition.
...
The only occasion, where else
in try statement makes any sense, is when there is no typed except
check:.
try: # No condition.
...
except: # If any exception was raised.
...
else: # "If previous condition was checked and was false"?
...
finally: # No condition.
...
(Pedantic) note, default except
doesn’t perform any checks, it is just jumped into.
Examples of possible replacements
noexcept
try: ...
except type1: # If any exception was raised,
# checks if raised exception is of type ``type1``.
...
noexcept: # If no exception was raised.
...
Clear, but possible name collision.
not except
try: ...
except type1: # If any exception was raised,
# checks if raised exception is of type ``type1``.
...
not except: # Negation of ``except``.
Less clear, but still better than else
.
EBNF in docs
try_stmt ::= try1_stmt | try2_stmt | try3_stmt
try1_stmt ::= "try" ":" suite
("except" [expression ["as" identifier]] ":" suite)+
["else" ":" suite]
["finally" ":" suite]
try2_stmt ::= "try" ":" suite
("except" "*" expression ["as" identifier] ":" suite)+
["else" ":" suite]
["finally" ":" suite]
try3_stmt ::= "try" ":" suite
"finally" ":" suite
Current EBNF contains an error, the following code isn’t a valid try statement:.
try: ...
except: ...
except Exception: ...
I fixed it, named all clauses (to avoid duplication), and unnamed try variants (naming by numbers, rely?).
try_stmt ::= "try" ":" suite
( try_exc+ [try_dflt] [try_else] [try_fin]
| try_dflt [try_else] [try_fin]
| try_grup+ [try_else] [try_fin]
| try_fin )
try_dflt ::= "except" ":" suite
try_exc ::= "except" expression ["as" identifier] ":" suite
try_grup ::= "except" "*" expression ["as" identifier] ":" suite
try_else ::= "else" ":" suite
try_fin ::= "finally" ":" suite
Additional thoughts
I don’t know when typed except
was added, but according to docs archive, it existed at least in Python 1.4 (the oldest archive).
Default except
with else
clause is used quite often, GitHub search shows more than 101k files (I searched by regex with false negatives).