Skip to content
Merged
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,8 @@ peps/pep-0777.rst @warsaw
# ...
peps/pep-0779.rst @Yhg1s @colesbury @mpage
# ...
peps/pep-0781.rst @methane
# ...
peps/pep-0789.rst @njsmith
# ...
peps/pep-0801.rst @warsaw
Expand Down
116 changes: 116 additions & 0 deletions peps/pep-0781.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
PEP: 781
Title: Adding __type_checking__ constant
Comment thread
methane marked this conversation as resolved.
Outdated
Author: Inada Naoki <songofacandy@gmail.com>
Discussions-To: https://discuss.python.org/t/85728
Status: Draft
Type: Standards Track
Topic: Typing
Created: 24-Mar-2025
Python-Version: 3.14
Comment thread
methane marked this conversation as resolved.
Post-History: `11-Jan-2025 <https://discuss.python.org/t/76766>`__,
`24-Mar-2025 <https://discuss.python.org/t/85728>`__,


Abstract
========

This PEP proposes adding a new keyword, :data:`!__type_checking__`, to improve
the experience of writing Python code with type annotations. It is evaluated
as ``True`` when the code is being analyzed by a static type checker, and as
``False`` during normal runtime execution. Unlike :data:`typing.TYPE_CHECKING`,
which this keyword replaces, it does not require an import statement, and
creates the opportunity for compiler optimizations, such as smaller bytecode.


Motivation
==========

Type annotations were defined for Python by :pep:`484`, and have enjoyed
widespread adoption. A challenge with fully-annotated code is that many
more imports are required in order to bring the relevant name into scope,
potentially causing import cycles without careful design. This has been
recognized by :pep:`563` and later :pep:`649`, which introduce two different
mechanisms for deferred evaluation of type annotations. As PEP 563 notes,
"type hints are ... not computationally free". The :data:`typing.TYPE_CHECKING`
constant was thus introduced__, initially to aid in breaking cyclic imports.

__ https://github.com/python/typing/issues/230

In situations where startup time is critical, such as command-line interfaces,
applications, or core libraries, programmers may place all import statements
not required for runtime execution within a 'TYPE_CHECKING block', or even
defer certain imports to within functions. The ``typing`` module itself though
can take as much as 10ms to import, longer than Python takes to initialize.
The time taken to import the ``typing`` module clearly cannot be ignored.

To avoid importing ``TYPE_CHECKING`` from ``typing``, developers currently
define a module-level variable such as ``TYPE_CHECKING = False`` or use code
like ``if False: # TYPE_CHECKING``. Providing a standard method will allow
many tools to implement the same behavior consistently. It will also allow
third-party tools in the ecosystem to standardize on a single behavior
with guaranteed semantics, as for example some static type checkers currently
do not permit local constants, only recognizing ``typing.TYPE_CHECKING``.

Furthermore, this allows compilers to eliminate type-checking-only code at
compile time, as the constant is guaranteed to be ``False`` and cannot be
reassigned. This reduces bytecode size and memory usage, and would help with
writing type-annotated code for memory-constrained environments such as WASM
or MicroPython.


Rationale
=========

Using Keyword
-------------

Avoiding the addition of a new :ref:`keyword <python:keywords>`
(like :data:`__debug__`) would be attractive because more keywords means
a more complex language.

However, adding a constant without a keyword (like :data:`__debug__`) requires
special handling in both the compiler and runtime.

By implementing ``__type_checking__`` the same way as ``False``, we avoid the
need for the special handling.
Therefore, making it a keyword is the simpler approach.
Comment thread
AA-Turner marked this conversation as resolved.
Outdated


Specification
=============

``__type_checking__`` is a :ref:`keyword <python:keywords>` and its value is
``False``.
It can be used in the same way as ``False``, except it cannot be used as
a matching pattern.

Static type checkers must treat ``__type_checking__`` as ``True``,
similar to :data:`typing.TYPE_CHECKING`.

If this PEP is accepted, ``__type_checking__`` will be the preferred approach,
instead of ``typing.TYPE_CHECKING``. However, ``typing.TYPE_CHECKING`` will not
be deprecated.
Instead, it will be implemented as ``TYPE_CHECKING = __type_checking__``,
allowing future type checkers to focus on only handling ``__type_checking__``.


How to Teach This
=================

* Use ``__type_checking__`` for skipping type-checking code at runtime.
* Use :data:`typing.TYPE_CHECKING` to support Python versions before 3.14.
* Workarounds like ``TYPE_CHECKING = False`` or ``if False: # TYPE_CHECKING``
are not recommended since Python 3.14.


Reference Implementation
========================

* `python/cpython#131641 <https://github.com/python/cpython/pull/131641>`__


Copyright
=========

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.