635 lines
70 KiB
HTML
635 lines
70 KiB
HTML
|
||
<!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> » </li>
|
||
<li><a href="../pep-0000/">PEP Index</a> » </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 <jelle.zijlstra at gmail.com></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">-></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">-></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">-></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">-></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 user’s 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">-></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">-></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">-></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
|
||
argument’s 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">-></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">-></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"># "Awaitable[int] | (int & Awaitable[Any])"</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">-></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">"Hello "</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">-></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 doesn’t 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">"N"</span><span class="p">,</span> <span class="s2">"E"</span><span class="p">,</span> <span class="s2">"S"</span><span class="p">,</span> <span class="s2">"W"</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">-></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">"N"</span><span class="p">,</span> <span class="s2">"E"</span><span class="p">,</span> <span class="s2">"S"</span><span class="p">,</span> <span class="s2">"W"</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">-></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">"</span><span class="si">{</span><span class="n">x</span><span class="si">}</span><span class="s2"> is a cardinal direction"</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">"</span><span class="si">{</span><span class="n">x</span><span class="si">}</span><span class="s2"> is not a cardinal direction"</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 checker’s 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">-></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">-></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">></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">-></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">-></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">"Hello "</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">-></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">"x"</span> <span class="ow">in</span> <span class="n">x</span>
|
||
<span class="ow">and</span> <span class="s2">"y"</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">"x"</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">"y"</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">-></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">-></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">-></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">-></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">TypeScript’s 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 TypeScript’s 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> |