PEP: XXX
Title: Exception-catching expressions
Version: $Revision$
Last-Modified: $Date$
Author: Chris Angelico
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 15-Feb-2014
Python-Version: 3.5
Post-History: 16-Feb-2014
Abstract
========
Just as PEP 308 introduced a means of value-based conditions in an
expression, this system allows exception-based conditions to be used as part
of an expression.
Motivation
==========
A number of functions and methods have parameters which will cause them to
return a specified value instead of raising an exception. The current system
is ad-hoc and inconsistent, and requires that each function be individually
written to have this functionality; not all support this.
* dict.get(key, default) - second positional argument in place of KeyError
* next(iter, default) - second positional argument in place of StopIteration
* list.pop() - no way to return a default
(TODO: Get more examples. I know there are some but I can't think of any now.)
Rationale
=========
The current system requires that a function author predict the need for a
default, and implement support for it. If this is not done, a full try/except
block is needed.
Note that the specific syntax is open to three metric tons of bike-shedding.
The proposal may well be rejected, but even then is not useless; it can be
maintained as a collection of failed syntaxes for expression exceptions.
Proposal
========
Just as the 'or' operator and the three part 'if-else' expression give short
circuiting methods of catching a falsy value and replacing it, this syntax
gives a short-circuiting method of catching an exception and replacing it.
This currently works::
lst = [1, 2, None, 3]
value = lst[2] or "No value"
The proposal adds this::
lst = [1, 2]
value = lst[2] except IndexError: "No value"
The exception object can be captured just as in a normal try/except block::
# Return the next yielded or returned value from a generator
value = next(it) except StopIteration as e: e.args[0]
This is effectively equivalent to::
try:
_ = next(it)
except StopIteration as e:
_ = e.args[0]
value = _
This ternary operator would be between lambda and if/else in precedence.
Chaining
--------
Multiple 'except' keywords can be used, and they will all catch exceptions
raised in the original expression (only)::
value = (expr
except Exception1 [as e]: default1
except Exception2 [as e]: default2
# ... except ExceptionN [as e]: defaultN
)
This is not the same as either parenthesized form::
value = (("Yield: "+next(it) except StopIteration as e: "End: "+e.args[0])
except TypeError: "Error: Non-string returned or raised")
value = (next(it) except StopIteration as e:
(e.args[0] except IndexError: None))
The first form will catch an exception raised in either the original
expression or in the default expression; the second form will catch ONLY one
raised by the default expression. All three effects have their uses.
Alternative Proposals
=====================
Discussion on python-ideas brought up the following syntax suggestions::
value = expr except default if Exception [as e]
value = expr except default for Exception [as e]
value = expr except default from Exception [as e]
value = expr except Exception [as e] return default
value = expr except (Exception [as e]: default)
value = expr except Exception [as e] try default
value = expr except Exception [as e] continue with default
value = default except Exception [as e] else expr
value = try expr except Exception [as e]: default
value = expr except Exception [as e] pass default
In all cases, default is an expression which will not be evaluated unless
an exception is raised; if 'as' is used, this expression may refer to the
exception object.
It has also been suggested that a new keyword be created, rather than reusing
an existing one. Such proposals fall into the same structure as the last
form, but with a different keyword in place of 'pass'. Suggestions include
'then', 'when', and 'use'.
Open Issues
===========
finally clause
--------------
Should the except expression be able to have a finally clause? No form of the
proposal so far has included finally.
Commas between multiple except clauses
--------------------------------------
Where there are multiple except clauses, should they be separated by commas?
It may be easier for the parser, that way::
value = (expr
except Exception1 [as e]: default1,
except Exception2 [as e]: default2,
# ... except ExceptionN [as e]: defaultN,
)
with an optional comma after the last, as per tuple rules. Downside: Omitting
the comma would be syntactically valid, and would have almost identical
semantics, but would nest the entire preceding expression in its exception
catching rig - a matching exception raised in the default clause would be
caught by the subsequent except clause. As this difference is so subtle, it
runs the risk of being a major bug magnet.
Copyright
=========
This document has been placed in the public domain.
..
Local Variables:
mode: indented-text
indent-tabs-mode: nil
sentence-end-double-space: t
fill-column: 70
coding: utf-8