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,whileorforclause,elseclause is entered, only if condition check was reached and was false. - After
try-exceptclauseselseclause is entered, only iftryfinished 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).