I’m ready to submit my first PEP proposal. Googled how to go about it, and it seems I should float the idea here first.
Abstract
Make the Exception class a context manager so any builtin Exception can be caught using a “with clause”
Note descendants of BaseException but not Exception will not receive this functionality, and it is rare or unusual to catch these “exit exceptions”.
SystemExit
KeyboardInterrupt
GeneratorExit
Motivation
Pythonic code asks for forgiveness instead of permission.
Consider a function “index” that returns the index of a key in a list/tuple. If the key is not present it returns -1
def index0(indexable, key):
if key in indexable: # Ask permission!
return indexable.index(key)
return -1
The above is unpythonic and inefficient. A linear search through an unsorted list is slow, and this search must be performed twice. Once to determine if key is in indexable, and again to determine its index.
The code below is preferred, but rather verbose and ugly
def index1(indexable, key):
try:
return indexable.index(key)
except ValueError: # Ask forgivness
return -1
Specification
A common design pattern is to use a context manager as an alternative to try/except. Generally the code inside the with block will exit the function if successful and continue after the with block on failure
def index2(indexable, key):
with Catcher(ValueError): # More forgiveness
return indexable.index(key)
return -1
The proposal is to add context manager functionality to all exceptions inheriting from Exception. The Exception class, not the Exception object has the context manager methods.
def index3(indexable, key):
with ValueError:
return indexable.index(key)
return -1
While it would have to be implemented in CPython as Exception is not a Python declared type; the implementation is conceptually as follows:
class ExceptionMeta(type):
"""Metaclass to add context manager functionality to Exception"""
def __enter__(cls):
"""Do nothing and return None to as. Although never expect as clause to be used"""
def __exit__(cls, extype, ex, tb):
"""Return True to swallow exception if ex type matches"""
if isinstance(ex, cls):
return True
class Exception(BaseException, metaclass=ExceptionMeta):
"""Common base class for all non-exit exceptions."""
Since context manager functionality is defined in the metaclass; only Exception classes can be used as context managers. Exception instances as caught in except: statements cannot be used as context managers.
try:
(1, 2).index(3)
except ValueError as ex:
print(ex) # ValueError: tuple.index(x): x not in tuple
with ex: # AttributeError: __enter__
pass