python-peps/pep-0742/index.html

635 lines
70 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>PEP 742 Narrowing types with TypeIs | peps.python.org</title>
<link rel="shortcut icon" href="../_static/py.png">
<link rel="canonical" href="https://peps.python.org/pep-0742/">
<link rel="stylesheet" href="../_static/style.css" type="text/css">
<link rel="stylesheet" href="../_static/mq.css" type="text/css">
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light">
<link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark">
<link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss">
<meta property="og:title" content='PEP 742 Narrowing types with TypeIs | peps.python.org'>
<meta property="og:description" content="This PEP proposes a new special form, TypeIs, to allow annotating functions that can be used to narrow the type of a value, similar to the builtin isinstance(). Unlike the existing typing.TypeGuard special form, TypeIs can narrow the type in both the if...">
<meta property="og:type" content="website">
<meta property="og:url" content="https://peps.python.org/pep-0742/">
<meta property="og:site_name" content="Python Enhancement Proposals (PEPs)">
<meta property="og:image" content="https://peps.python.org/_static/og-image.png">
<meta property="og:image:alt" content="Python PEPs">
<meta property="og:image:width" content="200">
<meta property="og:image:height" content="200">
<meta name="description" content="This PEP proposes a new special form, TypeIs, to allow annotating functions that can be used to narrow the type of a value, similar to the builtin isinstance(). Unlike the existing typing.TypeGuard special form, TypeIs can narrow the type in both the if...">
<meta name="theme-color" content="#3776ab">
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all">
<title>Following system colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"></circle>
<path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path>
</svg>
</symbol>
<symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all">
<title>Selected dark colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path>
</svg>
</symbol>
<symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all">
<title>Selected light colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</symbol>
</svg>
<script>
document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto"
</script>
<section id="pep-page-section">
<header>
<h1>Python Enhancement Proposals</h1>
<ul class="breadcrumbs">
<li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> &raquo; </li>
<li><a href="../pep-0000/">PEP Index</a> &raquo; </li>
<li>PEP 742</li>
</ul>
<button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())">
<svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg>
<span class="visually-hidden">Toggle light / dark / auto colour theme</span>
</button>
</header>
<article>
<section id="pep-content">
<h1 class="page-title">PEP 742 Narrowing types with TypeIs</h1>
<dl class="rfc2822 field-list simple">
<dt class="field-odd">Author<span class="colon">:</span></dt>
<dd class="field-odd">Jelle Zijlstra &lt;jelle.zijlstra&#32;&#97;t&#32;gmail.com&gt;</dd>
<dt class="field-even">Discussions-To<span class="colon">:</span></dt>
<dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/pep-742-narrowing-types-with-typenarrower/45613">Discourse thread</a></dd>
<dt class="field-odd">Status<span class="colon">:</span></dt>
<dd class="field-odd"><abbr title="Accepted and implementation complete, or no longer active">Final</abbr></dd>
<dt class="field-even">Type<span class="colon">:</span></dt>
<dd class="field-even"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd>
<dt class="field-odd">Topic<span class="colon">:</span></dt>
<dd class="field-odd"><a class="reference external" href="../topic/typing/">Typing</a></dd>
<dt class="field-even">Created<span class="colon">:</span></dt>
<dd class="field-even">07-Feb-2024</dd>
<dt class="field-odd">Python-Version<span class="colon">:</span></dt>
<dd class="field-odd">3.13</dd>
<dt class="field-even">Post-History<span class="colon">:</span></dt>
<dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/pep-742-narrowing-types-with-typenarrower/45613" title="Discourse thread">11-Feb-2024</a></dd>
<dt class="field-odd">Replaces<span class="colon">:</span></dt>
<dd class="field-odd"><a class="reference external" href="../pep-0724/">724</a></dd>
<dt class="field-even">Resolution<span class="colon">:</span></dt>
<dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/pep-742-narrowing-types-with-typeis/45613/35">03-Apr-2024</a></dd>
</dl>
<hr class="docutils" />
<section id="contents">
<details><summary>Table of Contents</summary><ul class="simple">
<li><a class="reference internal" href="#abstract">Abstract</a></li>
<li><a class="reference internal" href="#motivation">Motivation</a></li>
<li><a class="reference internal" href="#rationale">Rationale</a></li>
<li><a class="reference internal" href="#specification">Specification</a><ul>
<li><a class="reference internal" href="#type-narrowing-behavior">Type narrowing behavior</a></li>
<li><a class="reference internal" href="#examples">Examples</a></li>
<li><a class="reference internal" href="#subtyping">Subtyping</a></li>
</ul>
</li>
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li>
<li><a class="reference internal" href="#security-implications">Security Implications</a></li>
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a><ul>
<li><a class="reference internal" href="#when-to-use-typeis">When to use <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code></a></li>
<li><a class="reference internal" href="#writing-a-safe-typeis-function">Writing a safe <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> function</a></li>
<li><a class="reference internal" href="#typeis-and-typeguard"><code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> and <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code></a></li>
</ul>
</li>
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
<li><a class="reference internal" href="#change-the-behavior-of-typeguard">Change the behavior of <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code></a></li>
<li><a class="reference internal" href="#do-nothing">Do nothing</a></li>
<li><a class="reference internal" href="#alternative-names">Alternative names</a></li>
</ul>
</li>
<li><a class="reference internal" href="#acknowledgments">Acknowledgments</a></li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
</details></section>
<div class="pep-banner canonical-typing-spec sticky-banner admonition attention">
<p class="admonition-title">Attention</p>
<p>This PEP is a historical document: see <a class="reference external" href="https://typing.readthedocs.io/en/latest/spec/narrowing.html#typeis" title="(in typing)"><span>TypeIs</span></a> and
<a class="reference external" href="https://docs.python.org/3.13/library/typing.html#typing.TypeIs" title="(in Python v3.13)"><code class="xref py py-data docutils literal notranslate"><span class="pre">typing.TypeIs</span></code></a> for up-to-date specs and documentation. Canonical typing specs are maintained at the <a class="reference external" href="https://typing.readthedocs.io/en/latest/spec/">typing specs site</a>; runtime typing behaviour is described in the CPython documentation.</p>
<p class="close-button">×</p>
<p>See the <a class="reference external" href="https://typing.readthedocs.io/en/latest/spec/meta.html">typing specification update process</a> for how to propose changes to the typing spec.</p>
</div>
<section id="abstract">
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
<p>This PEP proposes a new special form, <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code>, to allow annotating functions that can be used
to narrow the type of a value, similar to the builtin <a class="reference external" href="https://docs.python.org/3/library/functions.html#isinstance" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">isinstance()</span></code></a>. Unlike the existing
<a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.TypeGuard" title="(in Python v3.13)"><code class="xref py py-data docutils literal notranslate"><span class="pre">typing.TypeGuard</span></code></a> special form, <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> can narrow the type in both the <code class="docutils literal notranslate"><span class="pre">if</span></code>
and <code class="docutils literal notranslate"><span class="pre">else</span></code> branches of a conditional.</p>
</section>
<section id="motivation">
<h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
<p>Typed Python code often requires users to narrow the type of a variable based on a conditional.
For example, if a function accepts a union of two types, it may use an <a class="reference external" href="https://docs.python.org/3/library/functions.html#isinstance" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">isinstance()</span></code></a> check
to discriminate between the two types. Type checkers commonly support type narrowing based on various
builtin function and operations, but occasionally, it is useful to use a user-defined function to
perform type narrowing.</p>
<p>To support such use cases, <a class="pep reference internal" href="../pep-0647/" title="PEP 647 User-Defined Type Guards">PEP 647</a> introduced the <a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.TypeGuard" title="(in Python v3.13)"><code class="xref py py-data docutils literal notranslate"><span class="pre">typing.TypeGuard</span></code></a> special form, which
allows users to define type guards:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">assert_type</span><span class="p">,</span> <span class="n">TypeGuard</span>
<span class="k">def</span> <span class="nf">is_str</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeGuard</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
<span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="nb">str</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">if</span> <span class="n">is_str</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="n">assert_type</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="nb">str</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">assert_type</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="nb">object</span><span class="p">)</span>
</pre></div>
</div>
<p>Unfortunately, the behavior of <a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.TypeGuard" title="(in Python v3.13)"><code class="xref py py-data docutils literal notranslate"><span class="pre">typing.TypeGuard</span></code></a> has some limitations that make it
less useful for many common use cases, as explained also in the “Motivation” section of <a class="pep reference internal" href="../pep-0724/" title="PEP 724 Stricter Type Guards">PEP 724</a>.
In particular:</p>
<ul class="simple">
<li>Type checkers must use exactly the <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> return type as the narrowed type if the
type guard returns <code class="docutils literal notranslate"><span class="pre">True</span></code>. They cannot use pre-existing knowledge about the type of the
variable.</li>
<li>In the case where the type guard returns <code class="docutils literal notranslate"><span class="pre">False</span></code>, the type checker cannot apply any
additional narrowing.</li>
</ul>
<p>The standard library function <a class="reference external" href="https://docs.python.org/3/library/inspect.html#inspect.isawaitable" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">inspect.isawaitable()</span></code></a> may serve as an example. It
returns whether the argument is an awaitable object, and
<a class="reference external" href="https://github.com/python/typeshed/blob/a4f81a67a07c18dd184dd068c459b02e71bcac22/stdlib/inspect.pyi#L219">typeshed</a>
currently annotates it as:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">isawaitable</span><span class="p">(</span><span class="nb">object</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeGuard</span><span class="p">[</span><span class="n">Awaitable</span><span class="p">[</span><span class="n">Any</span><span class="p">]]:</span> <span class="o">...</span>
</pre></div>
</div>
<p>A user <a class="reference external" href="https://github.com/python/mypy/issues/15520">reported</a> an issue to mypy about
the behavior of this function. They observed the following behavior:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">inspect</span>
<span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Awaitable</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">reveal_type</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">t</span><span class="p">:</span> <span class="n">Awaitable</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">|</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">if</span> <span class="n">inspect</span><span class="o">.</span><span class="n">isawaitable</span><span class="p">(</span><span class="n">t</span><span class="p">):</span>
<span class="n">reveal_type</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="c1"># Awaitable[Any]</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">reveal_type</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="c1"># Awaitable[int] | int</span>
</pre></div>
</div>
<p>This behavior is consistent with <a class="pep reference internal" href="../pep-0647/" title="PEP 647 User-Defined Type Guards">PEP 647</a>, but it did not match the users expectations.
Instead, they would expect the type of <code class="docutils literal notranslate"><span class="pre">t</span></code> to be narrowed to <code class="docutils literal notranslate"><span class="pre">Awaitable[int]</span></code> in the <code class="docutils literal notranslate"><span class="pre">if</span></code>
branch, and to <code class="docutils literal notranslate"><span class="pre">int</span></code> in the <code class="docutils literal notranslate"><span class="pre">else</span></code> branch. This PEP proposes a new construct that does
exactly that.</p>
<p>Other examples of issues that arose out of the current behavior of <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> include:</p>
<ul class="simple">
<li><a class="reference external" href="https://github.com/python/typing/issues/996">Python typing issue</a> (<code class="docutils literal notranslate"><span class="pre">numpy.isscalar</span></code>)</li>
<li><a class="reference external" href="https://github.com/python/typing/issues/1351">Python typing issue</a> (<a class="reference external" href="https://docs.python.org/3/library/dataclasses.html#dataclasses.is_dataclass" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">dataclasses.is_dataclass()</span></code></a>)</li>
<li><a class="reference external" href="https://github.com/microsoft/pyright/issues/3450">Pyright issue</a> (expecting <a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.TypeGuard" title="(in Python v3.13)"><code class="xref py py-data docutils literal notranslate"><span class="pre">typing.TypeGuard</span></code></a> to work like <a class="reference external" href="https://docs.python.org/3/library/functions.html#isinstance" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">isinstance()</span></code></a>)</li>
<li><a class="reference external" href="https://github.com/microsoft/pyright/issues/3466">Pyright issue</a> (expecting narrowing in the <code class="docutils literal notranslate"><span class="pre">else</span></code> branch)</li>
<li><a class="reference external" href="https://github.com/python/mypy/issues/13957">Mypy issue</a> (expecting narrowing in the <code class="docutils literal notranslate"><span class="pre">else</span></code> branch)</li>
<li><a class="reference external" href="https://github.com/python/mypy/issues/14434">Mypy issue</a> (combining multiple TypeGuards)</li>
<li><a class="reference external" href="https://github.com/python/mypy/issues/15305">Mypy issue</a> (expecting narrowing in the <code class="docutils literal notranslate"><span class="pre">else</span></code> branch)</li>
<li><a class="reference external" href="https://github.com/python/mypy/issues/11907">Mypy issue</a> (user-defined function similar to <a class="reference external" href="https://docs.python.org/3/library/inspect.html#inspect.isawaitable" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">inspect.isawaitable()</span></code></a>)</li>
<li><a class="reference external" href="https://github.com/python/typeshed/issues/8009">Typeshed issue</a> (<code class="docutils literal notranslate"><span class="pre">asyncio.iscoroutinefunction</span></code>)</li>
</ul>
</section>
<section id="rationale">
<h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2>
<p>The problems with the current behavior of <a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.TypeGuard" title="(in Python v3.13)"><code class="xref py py-data docutils literal notranslate"><span class="pre">typing.TypeGuard</span></code></a> compel us to improve
the type system to allow a different type narrowing behavior. <a class="pep reference internal" href="../pep-0724/" title="PEP 724 Stricter Type Guards">PEP 724</a> proposed to change
the behavior of the existing <a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.TypeGuard" title="(in Python v3.13)"><code class="xref py py-data docutils literal notranslate"><span class="pre">typing.TypeGuard</span></code></a> construct, but we <a class="reference internal" href="#pep-742-change-typeguard"><span class="std std-ref">believe</span></a>
that the backwards compatibility implications of that change are too severe. Instead, we propose
adding a new special form with the desired semantics.</p>
<p>We acknowledge that this leads to an unfortunate situation where there are two constructs with
a similar purpose and similar semantics. We believe that users are more likely to want the behavior
of <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code>, the new form proposed in this PEP, and therefore we recommend that documentation
emphasize <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> over <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> as a more commonly applicable tool. However, the semantics of
<code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> are occasionally useful, and we do not propose to deprecate or remove it. In the long
run, most users should use <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code>, and <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> should be reserved for rare cases
where its behavior is specifically desired.</p>
</section>
<section id="specification">
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
<p>A new special form, <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code>, is added to the <a class="reference external" href="https://docs.python.org/3/library/typing.html#module-typing" title="(in Python v3.13)"><code class="xref py py-mod docutils literal notranslate"><span class="pre">typing</span></code></a>
module. Its usage, behavior, and runtime implementation are similar to
those of <a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.TypeGuard" title="(in Python v3.13)"><code class="xref py py-data docutils literal notranslate"><span class="pre">typing.TypeGuard</span></code></a>.</p>
<p>It accepts a single
argument and can be used as the return type of a function. A function annotated as returning a
<code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> is called a type narrowing function. Type narrowing functions must return <code class="docutils literal notranslate"><span class="pre">bool</span></code>
values, and the type checker should verify that all return paths return
<code class="docutils literal notranslate"><span class="pre">bool</span></code>.</p>
<p>Type narrowing functions must accept at least one positional argument. The type
narrowing behavior is applied to the first positional argument passed to
the function. The function may accept additional arguments, but they are
not affected by type narrowing. If a type narrowing function is implemented as
an instance method or class method, the first positional argument maps
to the second parameter (after <code class="docutils literal notranslate"><span class="pre">self</span></code> or <code class="docutils literal notranslate"><span class="pre">cls</span></code>).</p>
<section id="type-narrowing-behavior">
<h3><a class="toc-backref" href="#type-narrowing-behavior" role="doc-backlink">Type narrowing behavior</a></h3>
<p>To specify the behavior of <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code>, we use the following terminology:</p>
<ul class="simple">
<li>I = <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> input type</li>
<li>R = <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> return type</li>
<li>A = Type of argument passed to type narrowing function (pre-narrowed)</li>
<li>NP = Narrowed type (positive; used when <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> returned <code class="docutils literal notranslate"><span class="pre">True</span></code>)</li>
<li>NN = Narrowed type (negative; used when <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> returned <code class="docutils literal notranslate"><span class="pre">False</span></code>)</li>
</ul>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">narrower</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">I</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeIs</span><span class="p">[</span><span class="n">R</span><span class="p">]:</span> <span class="o">...</span>
<span class="k">def</span> <span class="nf">func1</span><span class="p">(</span><span class="n">val</span><span class="p">:</span> <span class="n">A</span><span class="p">):</span>
<span class="k">if</span> <span class="n">narrower</span><span class="p">(</span><span class="n">val</span><span class="p">):</span>
<span class="n">assert_type</span><span class="p">(</span><span class="n">val</span><span class="p">,</span> <span class="n">NP</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">assert_type</span><span class="p">(</span><span class="n">val</span><span class="p">,</span> <span class="n">NN</span><span class="p">)</span>
</pre></div>
</div>
<p>The return type <code class="docutils literal notranslate"><span class="pre">R</span></code> must be <a class="reference internal" href="../pep-0483/#pep-483-gradual-typing"><span class="std std-ref">consistent with</span></a> <code class="docutils literal notranslate"><span class="pre">I</span></code>. The type checker should
emit an error if this condition is not met.</p>
<p>Formally, type <em>NP</em> should be narrowed to <span class="formula"><i>A</i><i>R</i></span>,
the intersection of <em>A</em> and <em>R</em>, and type <em>NN</em> should be narrowed to
<span class="formula"><i>A</i>∧¬<i>R</i></span>, the intersection of <em>A</em> and the complement of <em>R</em>.
In practice, the theoretic types for strict type guards cannot be expressed
precisely in the Python type system. Type checkers should fall back on
practical approximations of these types. As a rule of thumb, a type checker
should use the same type narrowing logic and get results that are consistent
with its handling of <a class="reference external" href="https://docs.python.org/3/library/functions.html#isinstance" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">isinstance()</span></code></a>. This guidance allows for changes and
improvements if the type system is extended in the future.</p>
</section>
<section id="examples">
<h3><a class="toc-backref" href="#examples" role="doc-backlink">Examples</a></h3>
<p>Type narrowing is applied in both the positive and negative case:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeIs</span><span class="p">,</span> <span class="n">assert_type</span>
<span class="k">def</span> <span class="nf">is_str</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeIs</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
<span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="nb">str</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">if</span> <span class="n">is_str</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="n">assert_type</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="nb">str</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">assert_type</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span>
</pre></div>
</div>
<p>The final narrowed type may be narrower than <strong>R</strong>, due to the constraints of the
arguments previously-known type:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Awaitable</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">TypeIs</span><span class="p">,</span> <span class="n">assert_type</span>
<span class="kn">import</span> <span class="nn">inspect</span>
<span class="k">def</span> <span class="nf">isawaitable</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeIs</span><span class="p">[</span><span class="n">Awaitable</span><span class="p">[</span><span class="n">Any</span><span class="p">]]:</span>
<span class="k">return</span> <span class="n">inspect</span><span class="o">.</span><span class="n">isawaitable</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Awaitable</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">|</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">if</span> <span class="n">isawaitable</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="c1"># Type checkers may also infer the more precise type</span>
<span class="c1"># &quot;Awaitable[int] | (int &amp; Awaitable[Any])&quot;</span>
<span class="n">assert_type</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">Awaitable</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">assert_type</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span>
</pre></div>
</div>
<p>It is an error to narrow to a type that is not consistent with the input type:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeIs</span>
<span class="k">def</span> <span class="nf">is_str</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeIs</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span> <span class="c1"># Type checker error</span>
<span class="o">...</span>
</pre></div>
</div>
</section>
<section id="subtyping">
<h3><a class="toc-backref" href="#subtyping" role="doc-backlink">Subtyping</a></h3>
<p><code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> is also valid as the return type of a callable, for example
in callback protocols and in the <code class="docutils literal notranslate"><span class="pre">Callable</span></code> special form. In these
contexts, it is treated as a subtype of bool. For example, <code class="docutils literal notranslate"><span class="pre">Callable[...,</span> <span class="pre">TypeIs[int]]</span></code>
is assignable to <code class="docutils literal notranslate"><span class="pre">Callable[...,</span> <span class="pre">bool]</span></code>.</p>
<p>Unlike <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code>, <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> is invariant in its argument type:
<code class="docutils literal notranslate"><span class="pre">TypeIs[B]</span></code> is not a subtype of <code class="docutils literal notranslate"><span class="pre">TypeIs[A]</span></code>,
even if <code class="docutils literal notranslate"><span class="pre">B</span></code> is a subtype of <code class="docutils literal notranslate"><span class="pre">A</span></code>.
To see why, consider the following example:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">takes_narrower</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="nb">str</span><span class="p">,</span> <span class="n">narrower</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[</span><span class="nb">object</span><span class="p">],</span> <span class="n">TypeIs</span><span class="p">[</span><span class="nb">int</span><span class="p">]]):</span>
<span class="k">if</span> <span class="n">narrower</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="c1"># x is an int</span>
<span class="k">else</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;Hello &quot;</span> <span class="o">+</span> <span class="n">x</span><span class="p">)</span> <span class="c1"># x is a str</span>
<span class="k">def</span> <span class="nf">is_bool</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeIs</span><span class="p">[</span><span class="nb">bool</span><span class="p">]:</span>
<span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="nb">bool</span><span class="p">)</span>
<span class="n">takes_narrower</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">is_bool</span><span class="p">)</span> <span class="c1"># Error: is_bool is not a TypeIs[int]</span>
</pre></div>
</div>
<p>(Note that <code class="docutils literal notranslate"><span class="pre">bool</span></code> is a subtype of <code class="docutils literal notranslate"><span class="pre">int</span></code>.)
This code fails at runtime, because the narrower returns <code class="docutils literal notranslate"><span class="pre">False</span></code> (1 is not a <code class="docutils literal notranslate"><span class="pre">bool</span></code>)
and the <code class="docutils literal notranslate"><span class="pre">else</span></code> branch is taken in <code class="docutils literal notranslate"><span class="pre">takes_narrower()</span></code>.
If the call <code class="docutils literal notranslate"><span class="pre">takes_narrower(1,</span> <span class="pre">is_bool)</span></code> was allowed, type checkers would fail to
detect this error.</p>
</section>
</section>
<section id="backwards-compatibility">
<h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2>
<p>As this PEP only proposes a new special form, there are no implications on
backwards compatibility.</p>
</section>
<section id="security-implications">
<h2><a class="toc-backref" href="#security-implications" role="doc-backlink">Security Implications</a></h2>
<p>None known.</p>
</section>
<section id="how-to-teach-this">
<h2><a class="toc-backref" href="#how-to-teach-this" role="doc-backlink">How to Teach This</a></h2>
<p>Introductions to typing should cover <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> when discussing how to narrow types,
along with discussion of other narrowing constructs such as <a class="reference external" href="https://docs.python.org/3/library/functions.html#isinstance" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">isinstance()</span></code></a>. The
documentation should emphasize <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> over <a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.TypeGuard" title="(in Python v3.13)"><code class="xref py py-data docutils literal notranslate"><span class="pre">typing.TypeGuard</span></code></a>; while the
latter is not being deprecated and its behavior is occasionally useful, we expect that the
behavior of <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> is usually more intuitive, and most users should reach for
<code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> first. The rest of this section contains some example content that could
be used in introductory user-facing documentation.</p>
<section id="when-to-use-typeis">
<h3><a class="toc-backref" href="#when-to-use-typeis" role="doc-backlink">When to use <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code></a></h3>
<p>Python code often uses functions like <code class="docutils literal notranslate"><span class="pre">isinstance()</span></code> to distinguish between
different possible types of a value. Type checkers understand <code class="docutils literal notranslate"><span class="pre">isinstance()</span></code>
and various other checks and use them to narrow the type of a variable. However,
sometimes you want to reuse a more complicated check in multiple places, or
you use a check that the type checker doesnt understand. In these cases, you
can define a <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> function to perform the check and allow type checkers
to use it to narrow the type of a variable.</p>
<p>A <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> function takes a single argument and is annotated as returning
<code class="docutils literal notranslate"><span class="pre">TypeIs[T]</span></code>, where <code class="docutils literal notranslate"><span class="pre">T</span></code> is the type that you want to narrow to. The function
must return <code class="docutils literal notranslate"><span class="pre">True</span></code> if the argument is of type <code class="docutils literal notranslate"><span class="pre">T</span></code>, and <code class="docutils literal notranslate"><span class="pre">False</span></code> otherwise.
The function can then be used in <code class="docutils literal notranslate"><span class="pre">if</span></code> checks, just like you would use <code class="docutils literal notranslate"><span class="pre">isinstance()</span></code>.
For example:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeIs</span><span class="p">,</span> <span class="n">Literal</span>
<span class="nb">type</span> <span class="n">Direction</span> <span class="o">=</span> <span class="n">Literal</span><span class="p">[</span><span class="s2">&quot;N&quot;</span><span class="p">,</span> <span class="s2">&quot;E&quot;</span><span class="p">,</span> <span class="s2">&quot;S&quot;</span><span class="p">,</span> <span class="s2">&quot;W&quot;</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">is_direction</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeIs</span><span class="p">[</span><span class="n">Direction</span><span class="p">]:</span>
<span class="k">return</span> <span class="n">x</span> <span class="ow">in</span> <span class="p">{</span><span class="s2">&quot;N&quot;</span><span class="p">,</span> <span class="s2">&quot;E&quot;</span><span class="p">,</span> <span class="s2">&quot;S&quot;</span><span class="p">,</span> <span class="s2">&quot;W&quot;</span><span class="p">}</span>
<span class="k">def</span> <span class="nf">maybe_direction</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">if</span> <span class="n">is_direction</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">x</span><span class="si">}</span><span class="s2"> is a cardinal direction&quot;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">x</span><span class="si">}</span><span class="s2"> is not a cardinal direction&quot;</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="writing-a-safe-typeis-function">
<h3><a class="toc-backref" href="#writing-a-safe-typeis-function" role="doc-backlink">Writing a safe <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> function</a></h3>
<p>A <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> function allows you to override your type checkers type narrowing
behavior. This is a powerful tool, but it can be dangerous because an incorrectly
written <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> function can lead to unsound type checking, and type checkers
cannot detect such errors.</p>
<p>For a function returning <code class="docutils literal notranslate"><span class="pre">TypeIs[T]</span></code> to be safe, it must return <code class="docutils literal notranslate"><span class="pre">True</span></code> if and only if
the argument is compatible with type <code class="docutils literal notranslate"><span class="pre">T</span></code>, and <code class="docutils literal notranslate"><span class="pre">False</span></code> otherwise. If this condition is
not met, the type checker may infer incorrect types.</p>
<p>Below are some examples of correct and incorrect <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> functions:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeIs</span>
<span class="c1"># Correct</span>
<span class="k">def</span> <span class="nf">good_typeis</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeIs</span><span class="p">[</span><span class="nb">int</span><span class="p">]:</span>
<span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span>
<span class="c1"># Incorrect: does not return True for all ints</span>
<span class="k">def</span> <span class="nf">bad_typeis1</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeIs</span><span class="p">[</span><span class="nb">int</span><span class="p">]:</span>
<span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span> <span class="ow">and</span> <span class="n">x</span> <span class="o">&gt;</span> <span class="mi">0</span>
<span class="c1"># Incorrect: returns True for some non-ints</span>
<span class="k">def</span> <span class="nf">bad_typeis2</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeIs</span><span class="p">[</span><span class="nb">int</span><span class="p">]:</span>
<span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="p">(</span><span class="nb">int</span><span class="p">,</span> <span class="nb">float</span><span class="p">))</span>
</pre></div>
</div>
<p>This function demonstrates some errors that can occur when using a poorly written
<code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> function. These errors are not detected by type checkers:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">caller</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="nb">str</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="nb">float</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">if</span> <span class="n">bad_typeis1</span><span class="p">(</span><span class="n">x</span><span class="p">):</span> <span class="c1"># narrowed to int</span>
<span class="nb">print</span><span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span> <span class="c1"># narrowed to str (incorrectly)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;Hello &quot;</span> <span class="o">+</span> <span class="n">x</span><span class="p">)</span> <span class="c1"># runtime error if x is a negative int</span>
<span class="k">if</span> <span class="n">bad_typeis2</span><span class="p">(</span><span class="n">y</span><span class="p">):</span> <span class="c1"># narrowed to int</span>
<span class="c1"># Because of the incorrect TypeIs, this branch is taken at runtime if</span>
<span class="c1"># y is a float.</span>
<span class="nb">print</span><span class="p">(</span><span class="n">y</span><span class="o">.</span><span class="n">bit_count</span><span class="p">())</span> <span class="c1"># runtime error: this method exists only on int, not float</span>
<span class="k">else</span><span class="p">:</span> <span class="c1"># narrowed to float (though never executed at runtime)</span>
<span class="k">pass</span>
</pre></div>
</div>
<p>Here is an example of a correct <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> function for a more complicated type:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypedDict</span><span class="p">,</span> <span class="n">TypeIs</span>
<span class="k">class</span> <span class="nc">Point</span><span class="p">(</span><span class="n">TypedDict</span><span class="p">):</span>
<span class="n">x</span><span class="p">:</span> <span class="nb">int</span>
<span class="n">y</span><span class="p">:</span> <span class="nb">int</span>
<span class="k">def</span> <span class="nf">is_point</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeIs</span><span class="p">[</span><span class="n">Point</span><span class="p">]:</span>
<span class="k">return</span> <span class="p">(</span>
<span class="nb">isinstance</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="nb">dict</span><span class="p">)</span>
<span class="ow">and</span> <span class="nb">all</span><span class="p">(</span><span class="nb">isinstance</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="nb">str</span><span class="p">)</span> <span class="k">for</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">x</span><span class="p">)</span>
<span class="ow">and</span> <span class="s2">&quot;x&quot;</span> <span class="ow">in</span> <span class="n">x</span>
<span class="ow">and</span> <span class="s2">&quot;y&quot;</span> <span class="ow">in</span> <span class="n">x</span>
<span class="ow">and</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">x</span><span class="p">[</span><span class="s2">&quot;x&quot;</span><span class="p">],</span> <span class="nb">int</span><span class="p">)</span>
<span class="ow">and</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">x</span><span class="p">[</span><span class="s2">&quot;y&quot;</span><span class="p">],</span> <span class="nb">int</span><span class="p">)</span>
<span class="p">)</span>
</pre></div>
</div>
</section>
<section id="typeis-and-typeguard">
<h3><a class="toc-backref" href="#typeis-and-typeguard" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> and <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code></a></h3>
<p><code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> and <a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.TypeGuard" title="(in Python v3.13)"><code class="xref py py-data docutils literal notranslate"><span class="pre">typing.TypeGuard</span></code></a> are both tools for narrowing the type of a variable
based on a user-defined function. Both can be used to annotate functions that take an
argument and return a boolean depending on whether the input argument is compatible with
the narrowed type. These function can then be used in <code class="docutils literal notranslate"><span class="pre">if</span></code> checks to narrow the type
of a variable.</p>
<p><code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> usually has the most intuitive behavior, but it
introduces more restrictions. <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> is the right tool to use if:</p>
<ul class="simple">
<li>You want to narrow to a type that is not compatible with the input type, for example
from <code class="docutils literal notranslate"><span class="pre">list[object]</span></code> to <code class="docutils literal notranslate"><span class="pre">list[int]</span></code>. <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> only allows narrowing between
compatible types.</li>
<li>Your function does not return <code class="docutils literal notranslate"><span class="pre">True</span></code> for all input values that are compatible with
the narrowed type. For example, you could have a <code class="docutils literal notranslate"><span class="pre">TypeGuard[int]</span></code> that returns <code class="docutils literal notranslate"><span class="pre">True</span></code>
only for positive integers.</li>
</ul>
<p><code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> and <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> differ in the following ways:</p>
<ul class="simple">
<li><code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> requires the narrowed type to be a subtype of the input type, while
<code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> does not.</li>
<li>When a <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> function returns <code class="docutils literal notranslate"><span class="pre">True</span></code>, type checkers narrow the type of the
variable to exactly the <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> type. When a <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> function returns <code class="docutils literal notranslate"><span class="pre">True</span></code>,
type checkers can infer a more precise type combining the previously known type of the
variable with the <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> type. (Technically, this is known as an intersection type.)</li>
<li>When a <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> function returns <code class="docutils literal notranslate"><span class="pre">False</span></code>, type checkers cannot narrow the type of
the variable at all. When a <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> function returns <code class="docutils literal notranslate"><span class="pre">False</span></code>, type checkers can narrow
the type of the variable to exclude the <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> type.</li>
</ul>
<p>This behavior can be seen in the following example:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeGuard</span><span class="p">,</span> <span class="n">TypeIs</span><span class="p">,</span> <span class="n">reveal_type</span><span class="p">,</span> <span class="n">final</span>
<span class="k">class</span> <span class="nc">Base</span><span class="p">:</span> <span class="o">...</span>
<span class="k">class</span> <span class="nc">Child</span><span class="p">(</span><span class="n">Base</span><span class="p">):</span> <span class="o">...</span>
<span class="nd">@final</span>
<span class="k">class</span> <span class="nc">Unrelated</span><span class="p">:</span> <span class="o">...</span>
<span class="k">def</span> <span class="nf">is_base_typeguard</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeGuard</span><span class="p">[</span><span class="n">Base</span><span class="p">]:</span>
<span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">Base</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">is_base_typeis</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeIs</span><span class="p">[</span><span class="n">Base</span><span class="p">]:</span>
<span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">Base</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">use_typeguard</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Child</span> <span class="o">|</span> <span class="n">Unrelated</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">if</span> <span class="n">is_base_typeguard</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="n">reveal_type</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="c1"># Base</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">reveal_type</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="c1"># Child | Unrelated</span>
<span class="k">def</span> <span class="nf">use_typeis</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Child</span> <span class="o">|</span> <span class="n">Unrelated</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">if</span> <span class="n">is_base_typeis</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="n">reveal_type</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="c1"># Child</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">reveal_type</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="c1"># Unrelated</span>
</pre></div>
</div>
</section>
</section>
<section id="reference-implementation">
<h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2>
<p>The <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> special form <a class="reference external" href="https://github.com/python/typing_extensions/pull/330">has been implemented</a>
in the <code class="docutils literal notranslate"><span class="pre">typing_extensions</span></code> module and will be released in typing_extensions 4.10.0.</p>
<p>Implementations are available for several type checkers:</p>
<ul class="simple">
<li>Mypy: <a class="reference external" href="https://github.com/python/mypy/pull/16898">pull request open</a></li>
<li>Pyanalyze: <a class="reference external" href="https://github.com/quora/pyanalyze/pull/718">pull request</a></li>
<li>Pyright: <a class="reference external" href="https://github.com/microsoft/pyright/releases/tag/1.1.351">added in version 1.1.351</a></li>
</ul>
</section>
<section id="rejected-ideas">
<h2><a class="toc-backref" href="#rejected-ideas" role="doc-backlink">Rejected Ideas</a></h2>
<section id="change-the-behavior-of-typeguard">
<span id="pep-742-change-typeguard"></span><h3><a class="toc-backref" href="#change-the-behavior-of-typeguard" role="doc-backlink">Change the behavior of <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code></a></h3>
<p><a class="pep reference internal" href="../pep-0724/" title="PEP 724 Stricter Type Guards">PEP 724</a> previously proposed changing the specified behavior of <a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.TypeGuard" title="(in Python v3.13)"><code class="xref py py-data docutils literal notranslate"><span class="pre">typing.TypeGuard</span></code></a> so
that if the return type of the guard is consistent with the input type, the behavior proposed
here for <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> would apply. This proposal has some important advantages: because it
does not require any runtime changes, it requires changes only in type checkers, making it easier
for users to take advantage of the new, usually more intuitive behavior.</p>
<p>However, this approach has some major problems. Users who have written <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> functions
expecting the existing semantics specified in <a class="pep reference internal" href="../pep-0647/" title="PEP 647 User-Defined Type Guards">PEP 647</a> would see subtle and potentially breaking
changes in how type checkers interpret their code. The split behavior of <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code>, where it
works one way if the return type is consistent with the input type and another way if it is not,
could be confusing for users. The Typing Council was unable to come to an agreement in favor of
<a class="pep reference internal" href="../pep-0724/" title="PEP 724 Stricter Type Guards">PEP 724</a>; as a result, we are proposing this alternative PEP.</p>
</section>
<section id="do-nothing">
<h3><a class="toc-backref" href="#do-nothing" role="doc-backlink">Do nothing</a></h3>
<p>Both this PEP and the alternative proposed in <a class="pep reference internal" href="../pep-0724/" title="PEP 724 Stricter Type Guards">PEP 724</a> have shortcomings. The latter are
discussed above. As for this PEP, it introduces two special forms with very similar semantics,
and it potentially creates a long migration path for users currently using <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code>
who would be better off with different narrowing semantics.</p>
<p>One way forward, then, is to do nothing and live with the current limitations of the type system.
However, we believe that the limitations of the current <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code>, as outlined in the “Motivation”
section, are significant enough that it is worthwhile to change the type system to address them.
If we do not make any change, users will continue to encounter the same unintuitive behaviors from
<code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code>, and the type system will be unable to properly represent common type narrowing functions
like <code class="docutils literal notranslate"><span class="pre">inspect.isawaitable</span></code>.</p>
</section>
<section id="alternative-names">
<h3><a class="toc-backref" href="#alternative-names" role="doc-backlink">Alternative names</a></h3>
<p>This PEP currently proposes the name <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code>, emphasizing that the special form <code class="docutils literal notranslate"><span class="pre">TypeIs[T]</span></code>
returns whether the argument is of type <code class="docutils literal notranslate"><span class="pre">T</span></code>, and mirroring
<a class="reference external" href="https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates">TypeScripts syntax</a>.
Other names were considered, including in an earlier version of this PEP.</p>
<p>Options include:</p>
<ul class="simple">
<li><code class="docutils literal notranslate"><span class="pre">IsInstance</span></code> (<a class="reference external" href="https://discuss.python.org/t/pep-724-stricter-type-guards/34124/60">post by Paul Moore</a>):
emphasizes that the new construct behaves similarly to the builtin <a class="reference external" href="https://docs.python.org/3/library/functions.html#isinstance" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">isinstance()</span></code></a>.</li>
<li><code class="docutils literal notranslate"><span class="pre">Narrowed</span></code> or <code class="docutils literal notranslate"><span class="pre">NarrowedTo</span></code>: shorter than <code class="docutils literal notranslate"><span class="pre">TypeNarrower</span></code> but keeps the connection to “type narrowing”
(suggested by Eric Traut).</li>
<li><code class="docutils literal notranslate"><span class="pre">Predicate</span></code> or <code class="docutils literal notranslate"><span class="pre">TypePredicate</span></code>: mirrors TypeScripts name for the feature, “type predicates”.</li>
<li><code class="docutils literal notranslate"><span class="pre">StrictTypeGuard</span></code> (earlier drafts of <a class="pep reference internal" href="../pep-0724/" title="PEP 724 Stricter Type Guards">PEP 724</a>): emphasizes that the new construct performs a stricter
version of type narrowing than <a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.TypeGuard" title="(in Python v3.13)"><code class="xref py py-data docutils literal notranslate"><span class="pre">typing.TypeGuard</span></code></a>.</li>
<li><code class="docutils literal notranslate"><span class="pre">TypeCheck</span></code> (<a class="reference external" href="https://discuss.python.org/t/pep-724-stricter-type-guards/34124/59">post by Nicolas Tessore</a>):
emphasizes the binary nature of the check.</li>
<li><code class="docutils literal notranslate"><span class="pre">TypeNarrower</span></code>: emphasizes that the function narrows its argument type. Used in an earlier version of this PEP.</li>
</ul>
</section>
</section>
<section id="acknowledgments">
<h2><a class="toc-backref" href="#acknowledgments" role="doc-backlink">Acknowledgments</a></h2>
<p>Much of the motivation and specification for this PEP derives from <a class="pep reference internal" href="../pep-0724/" title="PEP 724 Stricter Type Guards">PEP 724</a>. While
this PEP proposes a different solution for the problem at hand, the authors of <a class="pep reference internal" href="../pep-0724/" title="PEP 724 Stricter Type Guards">PEP 724</a>, Eric Traut, Rich
Chiodo, and Erik De Bonte, made a strong case for their proposal and this proposal
would not have been possible without their work.</p>
</section>
<section id="copyright">
<h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2>
<p>This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.</p>
</section>
</section>
<hr class="docutils" />
<p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0742.rst">https://github.com/python/peps/blob/main/peps/pep-0742.rst</a></p>
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0742.rst">2024-10-17 12:49:39 GMT</a></p>
</article>
<nav id="pep-sidebar">
<h2>Contents</h2>
<ul>
<li><a class="reference internal" href="#abstract">Abstract</a></li>
<li><a class="reference internal" href="#motivation">Motivation</a></li>
<li><a class="reference internal" href="#rationale">Rationale</a></li>
<li><a class="reference internal" href="#specification">Specification</a><ul>
<li><a class="reference internal" href="#type-narrowing-behavior">Type narrowing behavior</a></li>
<li><a class="reference internal" href="#examples">Examples</a></li>
<li><a class="reference internal" href="#subtyping">Subtyping</a></li>
</ul>
</li>
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li>
<li><a class="reference internal" href="#security-implications">Security Implications</a></li>
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a><ul>
<li><a class="reference internal" href="#when-to-use-typeis">When to use <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code></a></li>
<li><a class="reference internal" href="#writing-a-safe-typeis-function">Writing a safe <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> function</a></li>
<li><a class="reference internal" href="#typeis-and-typeguard"><code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> and <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code></a></li>
</ul>
</li>
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
<li><a class="reference internal" href="#change-the-behavior-of-typeguard">Change the behavior of <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code></a></li>
<li><a class="reference internal" href="#do-nothing">Do nothing</a></li>
<li><a class="reference internal" href="#alternative-names">Alternative names</a></li>
</ul>
</li>
<li><a class="reference internal" href="#acknowledgments">Acknowledgments</a></li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
<br>
<a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0742.rst">Page Source (GitHub)</a>
</nav>
</section>
<script src="../_static/colour_scheme.js"></script>
<script src="../_static/wrap_tables.js"></script>
<script src="../_static/sticky_banner.js"></script>
</body>
</html>