Skip to content

Commit 2535760

Browse files
authored
Added typing spec chapter focused on exception behavior (#1718)
* Added typing spec chapter focused on exception behavior — in particular, context managers and whether they suppress exceptions. * Incorporated PR feedback. * Incorporated feedback from Guido.
1 parent 226b528 commit 2535760

File tree

2 files changed

+64
-0
lines changed

2 files changed

+64
-0
lines changed

docs/spec/exceptions.rst

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
Exceptions
2+
==========
3+
4+
Some type checking behaviors, such as type narrowing and reachability analysis,
5+
require a type checker to understand code flow. Code flow normally proceeds
6+
from one statement to the next, but some statements such as ``for``, ``while``
7+
and ``return`` can change the code flow. Similarly, ``try``/``except``/``finally``
8+
statements affect code flow and therefore can affect type evaluation. For example::
9+
10+
x = None
11+
try:
12+
some_function()
13+
x = 1
14+
except NotImplementedError:
15+
pass
16+
17+
# The type of `x` at this point could be None if `some_function` raises
18+
# an exception or `Literal[1]` if it doesn't, so a type checker may
19+
# choose to narrow its type based on this analysis.
20+
reveal_type(x) # Literal[1] | None
21+
22+
23+
Context Managers
24+
----------------
25+
26+
A context manager may optionally "suppress" exceptions by returning ``True``
27+
(or some other truthy value) from its ``__exit__`` method. When such a context
28+
manager is used, any exceptions that are raised and otherwise uncaught within
29+
the ``with`` block are caught by the context manager, and control continues
30+
immediately after the ``with`` block. If a context manager does not suppress
31+
exceptions (as is typically the case), any exceptions that are raised and
32+
otherwise uncaught within the ``with`` block propagate beyond the ``with``
33+
block.
34+
35+
Type checkers that employ code flow analysis must be able to distinguish
36+
between these two cases. This is done by examining the return type
37+
annotation of the ``__exit__`` method of the context manager.
38+
39+
If the return type of the ``__exit__`` method is specifically ``bool`` or
40+
``Literal[True]``, a type checker should assume that exceptions *can be*
41+
suppressed. For any other return type, a type checker should assume that
42+
exceptions *are not* suppressed. Examples include: ``Any``, ``Literal[False]``,
43+
``None``, and ``bool | None``.
44+
45+
This convention was chosen because most context managers do not suppress
46+
exceptions, and it is common for their ``__exit__`` method to be annotated as
47+
returning ``bool | None``. Context managers that suppress exceptions are
48+
relatively rare, so they are considered a special case.
49+
50+
For example, the following context manager suppresses exceptions::
51+
52+
class Suppress:
53+
def __enter__(self) -> None:
54+
pass
55+
56+
def __exit__(self, exc_type, exc_value, traceback) -> bool:
57+
return True
58+
59+
with Suppress():
60+
raise ValueError("This exception is suppressed")
61+
62+
# The exception is suppressed, so this line is reachable.
63+
print("Code is reachable")

docs/spec/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Specification for the Python type system
1919
callables
2020
constructors
2121
overload
22+
exceptions
2223
dataclasses
2324
typeddict
2425
tuples

0 commit comments

Comments
 (0)