python-peps/pep-0647/index.html

463 lines
40 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 647 User-Defined Type Guards | peps.python.org</title>
<link rel="shortcut icon" href="../_static/py.png">
<link rel="canonical" href="https://peps.python.org/pep-0647/">
<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 647 User-Defined Type Guards | peps.python.org'>
<meta property="og:description" content="This PEP specifies a way for programs to influence conditional type narrowing employed by a type checker based on runtime checks.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://peps.python.org/pep-0647/">
<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 specifies a way for programs to influence conditional type narrowing employed by a type checker based on runtime checks.">
<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 647</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 647 User-Defined Type Guards</h1>
<dl class="rfc2822 field-list simple">
<dt class="field-odd">Author<span class="colon">:</span></dt>
<dd class="field-odd">Eric Traut &lt;erictr at microsoft.com&gt;</dd>
<dt class="field-even">Sponsor<span class="colon">:</span></dt>
<dd class="field-even">Guido van Rossum &lt;guido&#32;&#97;t&#32;python.org&gt;</dd>
<dt class="field-odd">Discussions-To<span class="colon">:</span></dt>
<dd class="field-odd"><a class="reference external" href="https://mail.python.org/archives/list/typing-sig&#64;python.org/">Typing-SIG list</a></dd>
<dt class="field-even">Status<span class="colon">:</span></dt>
<dd class="field-even"><abbr title="Accepted and implementation complete, or no longer active">Final</abbr></dd>
<dt class="field-odd">Type<span class="colon">:</span></dt>
<dd class="field-odd"><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-even">Topic<span class="colon">:</span></dt>
<dd class="field-even"><a class="reference external" href="../topic/typing/">Typing</a></dd>
<dt class="field-odd">Created<span class="colon">:</span></dt>
<dd class="field-odd">07-Oct-2020</dd>
<dt class="field-even">Python-Version<span class="colon">:</span></dt>
<dd class="field-even">3.10</dd>
<dt class="field-odd">Post-History<span class="colon">:</span></dt>
<dd class="field-odd">28-Dec-2020, 09-Apr-2021</dd>
<dt class="field-even">Resolution<span class="colon">:</span></dt>
<dd class="field-even"><a class="reference external" href="https://mail.python.org/archives/list/python-dev&#64;python.org/thread/2ME6F6YUVKHOQYKSHTVQQU5WD4CVAZU4/">Python-Dev thread</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="#specification">Specification</a><ul>
<li><a class="reference internal" href="#typeguard-type">TypeGuard Type</a></li>
</ul>
</li>
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></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="#decorator-syntax">Decorator Syntax</a></li>
<li><a class="reference internal" href="#enforcing-strict-narrowing">Enforcing Strict Narrowing</a></li>
<li><a class="reference internal" href="#conditionally-applying-typeguard-type">Conditionally Applying TypeGuard Type</a></li>
<li><a class="reference internal" href="#narrowing-of-arbitrary-parameters">Narrowing of Arbitrary Parameters</a></li>
<li><a class="reference internal" href="#narrowing-of-implicit-self-and-cls-parameters">Narrowing of Implicit “self” and “cls” Parameters</a></li>
</ul>
</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#typeguard" title="(in typing)"><span>TypeGuard</span></a> 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> 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 specifies a way for programs to influence conditional type narrowing
employed by a type checker based on runtime checks.</p>
</section>
<section id="motivation">
<h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
<p>Static type checkers commonly employ a technique called “type narrowing” to
determine a more precise type of an expression within a programs code flow.
When type narrowing is applied within a block of code based on a conditional
code flow statement (such as <code class="docutils literal notranslate"><span class="pre">if</span></code> and <code class="docutils literal notranslate"><span class="pre">while</span></code> statements), the conditional
expression is sometimes referred to as a “type guard”. Python type checkers
typically support various forms of type guards expressions.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="n">val</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]):</span>
<span class="c1"># &quot;is None&quot; type guard</span>
<span class="k">if</span> <span class="n">val</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
<span class="c1"># Type of val is narrowed to str</span>
<span class="o">...</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># Type of val is narrowed to None</span>
<span class="o">...</span>
<span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="n">val</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]):</span>
<span class="c1"># Truthy type guard</span>
<span class="k">if</span> <span class="n">val</span><span class="p">:</span>
<span class="c1"># Type of val is narrowed to str</span>
<span class="o">...</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># Type of val remains Optional[str]</span>
<span class="o">...</span>
<span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="n">val</span><span class="p">:</span> <span class="n">Union</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">float</span><span class="p">]):</span>
<span class="c1"># &quot;isinstance&quot; type guard</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">val</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
<span class="c1"># Type of val is narrowed to str</span>
<span class="o">...</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># Type of val is narrowed to float</span>
<span class="o">...</span>
<span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="n">val</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">]):</span>
<span class="c1"># Comparison type guard</span>
<span class="k">if</span> <span class="n">val</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
<span class="c1"># Type of val is narrowed to Literal[1]</span>
<span class="o">...</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># Type of val is narrowed to Literal[2]</span>
<span class="o">...</span>
</pre></div>
</div>
<p>There are cases where type narrowing cannot be applied based on static
information only. Consider the following example:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">is_str_list</span><span class="p">(</span><span class="n">val</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">object</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;Determines whether all objects in the list are strings&quot;&quot;&quot;</span>
<span class="k">return</span> <span class="nb">all</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">str</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">val</span><span class="p">)</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">List</span><span class="p">[</span><span class="nb">object</span><span class="p">]):</span>
<span class="k">if</span> <span class="n">is_str_list</span><span class="p">(</span><span class="n">val</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot; &quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">val</span><span class="p">))</span> <span class="c1"># Error: invalid type</span>
</pre></div>
</div>
<p>This code is correct, but a type checker will report a type error because
the value <code class="docutils literal notranslate"><span class="pre">val</span></code> passed to the <code class="docutils literal notranslate"><span class="pre">join</span></code> method is understood to be of type
<code class="docutils literal notranslate"><span class="pre">List[object]</span></code>. The type checker does not have enough information to
statically verify that the type of <code class="docutils literal notranslate"><span class="pre">val</span></code> is <code class="docutils literal notranslate"><span class="pre">List[str]</span></code> at this point.</p>
<p>This PEP introduces a way for a function like <code class="docutils literal notranslate"><span class="pre">is_str_list</span></code> to be defined as
a “user-defined type guard”. This allows code to extend the type guards that
are supported by type checkers.</p>
<p>Using this new mechanism, the <code class="docutils literal notranslate"><span class="pre">is_str_list</span></code> function in the above example
would be modified slightly. Its return type would be changed from <code class="docutils literal notranslate"><span class="pre">bool</span></code>
to <code class="docutils literal notranslate"><span class="pre">TypeGuard[List[str]]</span></code>. This promises not merely that the return value
is boolean, but that a true indicates the input to the function was of the
specified 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">TypeGuard</span>
<span class="k">def</span> <span class="nf">is_str_list</span><span class="p">(</span><span class="n">val</span><span class="p">:</span> <span class="n">List</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">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]]:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;Determines whether all objects in the list are strings&quot;&quot;&quot;</span>
<span class="k">return</span> <span class="nb">all</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">str</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">val</span><span class="p">)</span>
</pre></div>
</div>
<p>User-defined type guards can also be used to determine whether a dictionary
conforms to the type requirements of a TypedDict.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Person</span><span class="p">(</span><span class="n">TypedDict</span><span class="p">):</span>
<span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">age</span><span class="p">:</span> <span class="nb">int</span>
<span class="k">def</span> <span class="nf">is_person</span><span class="p">(</span><span class="n">val</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&quot;TypeGuard[Person]&quot;</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">val</span><span class="p">[</span><span class="s2">&quot;name&quot;</span><span class="p">],</span> <span class="nb">str</span><span class="p">)</span> <span class="ow">and</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">val</span><span class="p">[</span><span class="s2">&quot;age&quot;</span><span class="p">],</span> <span class="nb">int</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">False</span>
<span class="k">def</span> <span class="nf">print_age</span><span class="p">(</span><span class="n">val</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
<span class="k">if</span> <span class="n">is_person</span><span class="p">(</span><span class="n">val</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Age: </span><span class="si">{</span><span class="n">val</span><span class="p">[</span><span class="s1">&#39;age&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&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="s2">&quot;Not a person!&quot;</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="specification">
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
<section id="typeguard-type">
<h3><a class="toc-backref" href="#typeguard-type" role="doc-backlink">TypeGuard Type</a></h3>
<p>This PEP introduces the symbol <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> exported from the <code class="docutils literal notranslate"><span class="pre">typing</span></code>
module. <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> is a special form that accepts a single type argument.
It is used to annotate the return type of a user-defined type guard function.
Return statements within a type guard function should return bool values,
and type checkers should verify that all return paths return a bool.</p>
<p>In all other respects, TypeGuard is a distinct type from bool. It is not a
subtype of bool. Therefore, <code class="docutils literal notranslate"><span class="pre">Callable[...,</span> <span class="pre">TypeGuard[int]]</span></code> is not assignable
to <code class="docutils literal notranslate"><span class="pre">Callable[...,</span> <span class="pre">bool]</span></code>.</p>
<p>When <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> is used to annotate the return type of a function or
method that accepts at least one parameter, that function or method is
treated by type checkers as a user-defined type guard. The type argument
provided for <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> indicates the type that has been validated by
the function.</p>
<p>User-defined type guards can be generic functions, as shown in this example:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">_T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&quot;_T&quot;</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">is_two_element_tuple</span><span class="p">(</span><span class="n">val</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">[</span><span class="n">_T</span><span class="p">,</span> <span class="o">...</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">TypeGuard</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="n">_T</span><span class="p">,</span> <span class="n">_T</span><span class="p">]]:</span>
<span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
<span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="n">names</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="o">...</span><span class="p">]):</span>
<span class="k">if</span> <span class="n">is_two_element_tuple</span><span class="p">(</span><span class="n">names</span><span class="p">):</span>
<span class="n">reveal_type</span><span class="p">(</span><span class="n">names</span><span class="p">)</span> <span class="c1"># Tuple[str, str]</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">reveal_type</span><span class="p">(</span><span class="n">names</span><span class="p">)</span> <span class="c1"># Tuple[str, ...]</span>
</pre></div>
</div>
<p>Type checkers should assume that type narrowing should be applied to the
expression that is passed as the first positional argument to a user-defined
type guard. If the type guard function accepts more than one argument, no
type narrowing is applied to those additional argument expressions.</p>
<p>If a type guard function is implemented as an instance method or class method,
the first positional argument maps to the second parameter (after “self” or
“cls”).</p>
<p>Here are some examples of user-defined type guard functions that accept more
than one argument:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">is_str_list</span><span class="p">(</span><span class="n">val</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">object</span><span class="p">],</span> <span class="n">allow_empty</span><span class="p">:</span> <span class="nb">bool</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TypeGuard</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]]:</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="k">return</span> <span class="n">allow_empty</span>
<span class="k">return</span> <span class="nb">all</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">str</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">val</span><span class="p">)</span>
<span class="n">_T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&quot;_T&quot;</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">is_set_of</span><span class="p">(</span><span class="n">val</span><span class="p">:</span> <span class="n">Set</span><span class="p">[</span><span class="n">Any</span><span class="p">],</span> <span class="nb">type</span><span class="p">:</span> <span class="n">Type</span><span class="p">[</span><span class="n">_T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">TypeGuard</span><span class="p">[</span><span class="n">Set</span><span class="p">[</span><span class="n">_T</span><span class="p">]]:</span>
<span class="k">return</span> <span class="nb">all</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">type</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">val</span><span class="p">)</span>
</pre></div>
</div>
<p>The return type of a user-defined type guard function will normally refer to
a type that is strictly “narrower” than the type of the first argument (that
is, its a more specific type that can be assigned to the more general type).
However, it is not required that the return type be strictly narrower. This
allows for cases like the example above where <code class="docutils literal notranslate"><span class="pre">List[str]</span></code> is not assignable
to <code class="docutils literal notranslate"><span class="pre">List[object]</span></code>.</p>
<p>When a conditional statement includes a call to a user-defined type guard
function, and that function returns true, the expression passed as the first
positional argument to the type guard function should be assumed by a static
type checker to take on the type specified in the TypeGuard return type,
unless and until it is further narrowed within the conditional code block.</p>
<p>Some built-in type guards provide narrowing for both positive and negative
tests (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> clauses). For example, consider the
type guard for an expression of the form <code class="docutils literal notranslate"><span class="pre">x</span> <span class="pre">is</span> <span class="pre">None</span></code>. If <code class="docutils literal notranslate"><span class="pre">x</span></code> has a type that
is a union of None and some other type, it will be narrowed to <code class="docutils literal notranslate"><span class="pre">None</span></code> in the
positive case and the other type in the negative case. User-defined type
guards apply narrowing only in the positive case (the <code class="docutils literal notranslate"><span class="pre">if</span></code> clause). The type
is not narrowed in the negative case.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">OneOrTwoStrs</span> <span class="o">=</span> <span class="n">Union</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]]</span>
<span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="n">val</span><span class="p">:</span> <span class="n">OneOrTwoStrs</span><span class="p">):</span>
<span class="k">if</span> <span class="n">is_two_element_tuple</span><span class="p">(</span><span class="n">val</span><span class="p">):</span>
<span class="n">reveal_type</span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="c1"># Tuple[str, str]</span>
<span class="o">...</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">reveal_type</span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="c1"># OneOrTwoStrs</span>
<span class="o">...</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">is_two_element_tuple</span><span class="p">(</span><span class="n">val</span><span class="p">):</span>
<span class="n">reveal_type</span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="c1"># OneOrTwoStrs</span>
<span class="o">...</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">reveal_type</span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="c1"># Tuple[str, str]</span>
<span class="o">...</span>
</pre></div>
</div>
</section>
</section>
<section id="backwards-compatibility">
<h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2>
<p>Existing code that does not use this new functionality will be unaffected.</p>
<p>Notably, code which uses annotations in a manner incompatible with the
stdlib typing library should simply not import TypeGuard.</p>
</section>
<section id="reference-implementation">
<h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2>
<p>The Pyright type checker supports the behavior described in this PEP.</p>
</section>
<section id="rejected-ideas">
<h2><a class="toc-backref" href="#rejected-ideas" role="doc-backlink">Rejected Ideas</a></h2>
<section id="decorator-syntax">
<h3><a class="toc-backref" href="#decorator-syntax" role="doc-backlink">Decorator Syntax</a></h3>
<p>The use of a decorator was considered for defining type guards.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@type_guard</span><span class="p">(</span><span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">is_str_list</span><span class="p">(</span><span class="n">val</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">object</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span> <span class="o">...</span>
</pre></div>
</div>
<p>The decorator approach is inferior because it requires runtime evaluation of
the type, precluding forward references. The proposed approach was also deemed
to be easier to understand and simpler to implement.</p>
</section>
<section id="enforcing-strict-narrowing">
<h3><a class="toc-backref" href="#enforcing-strict-narrowing" role="doc-backlink">Enforcing Strict Narrowing</a></h3>
<p>Strict type narrowing enforcement (requiring that the type specified
in the TypeGuard type argument is a narrower form of the type specified
for the first parameter) was considered, but this eliminates valuable
use cases for this functionality. For instance, the <code class="docutils literal notranslate"><span class="pre">is_str_list</span></code> example
above would be considered invalid because <code class="docutils literal notranslate"><span class="pre">List[str]</span></code> is not a subtype of
<code class="docutils literal notranslate"><span class="pre">List[object]</span></code> because of invariance rules.</p>
<p>One variation that was considered was to require a strict narrowing requirement
by default but allow the type guard function to specify some flag to
indicate that it is not following this requirement. This was rejected because
it was deemed cumbersome and unnecessary.</p>
<p>Another consideration was to define some less-strict check that ensures that
there is some overlap between the value type and the narrowed type specified
in the TypeGuard. The problem with this proposal is that the rules for type
compatibility are already very complex when considering unions, protocols,
type variables, generics, etc. Defining a variant of these rules that relaxes
some of these constraints just for the purpose of this feature would require
that we articulate all of the subtle ways in which the rules differ and under
what specific circumstances the constrains are relaxed. For this reason,
it was decided to omit all checks.</p>
<p>It was noted that without enforcing strict narrowing, it would be possible to
break type safety. A poorly-written type guard function could produce unsafe or
even nonsensical results. For example:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nb">int</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="kc">True</span>
</pre></div>
</div>
<p>However, there are many ways a determined or uninformed developer can subvert
type safety most commonly by using <code class="docutils literal notranslate"><span class="pre">cast</span></code> or <code class="docutils literal notranslate"><span class="pre">Any</span></code>. If a Python
developer takes the time to learn about and implement user-defined
type guards within their code, it is safe to assume that they are interested
in type safety and will not write their type guard functions in a way that will
undermine type safety or produce nonsensical results.</p>
</section>
<section id="conditionally-applying-typeguard-type">
<h3><a class="toc-backref" href="#conditionally-applying-typeguard-type" role="doc-backlink">Conditionally Applying TypeGuard Type</a></h3>
<p>It was suggested that the expression passed as the first argument to a type
guard function should retain its existing type if the type of the expression was
a proper subtype of the type specified in the TypeGuard return type.
For example, if the type guard function is <code class="docutils literal notranslate"><span class="pre">def</span> <span class="pre">f(value:</span> <span class="pre">object)</span> <span class="pre">-&gt;</span>
<span class="pre">TypeGuard[float]</span></code> and the expression passed to this function is of type
<code class="docutils literal notranslate"><span class="pre">int</span></code>, it would retain the <code class="docutils literal notranslate"><span class="pre">int</span></code> type rather than take on the
<code class="docutils literal notranslate"><span class="pre">float</span></code> type indicated by the TypeGuard return type. This proposal was
rejected because it added complexity, inconsistency, and opened up additional
questions about the proper behavior if the type of the expression was of
composite types like unions or type variables with multiple constraints. It was
decided that the added complexity and inconsistency was not justified given
that it would provide little or no added value.</p>
</section>
<section id="narrowing-of-arbitrary-parameters">
<h3><a class="toc-backref" href="#narrowing-of-arbitrary-parameters" role="doc-backlink">Narrowing of Arbitrary Parameters</a></h3>
<p>TypeScripts formulation of user-defined type guards allows for any input
parameter to be used as the value tested for narrowing. The TypeScript language
authors could not recall any real-world examples in TypeScript where the
parameter being tested was not the first parameter. For this reason, it was
decided unnecessary to burden the Python implementation of user-defined type
guards with additional complexity to support a contrived use case. If such
use cases are identified in the future, there are ways the TypeGuard mechanism
could be extended. This could involve the use of keyword indexing, as proposed
in <a class="pep reference internal" href="../pep-0637/" title="PEP 637 Support for indexing with keyword arguments">PEP 637</a>.</p>
</section>
<section id="narrowing-of-implicit-self-and-cls-parameters">
<h3><a class="toc-backref" href="#narrowing-of-implicit-self-and-cls-parameters" role="doc-backlink">Narrowing of Implicit “self” and “cls” Parameters</a></h3>
<p>The proposal states that the first positional argument is assumed to be the
value that is tested for narrowing. If the type guard function is implemented
as an instance or class method, an implicit <code class="docutils literal notranslate"><span class="pre">self</span></code> or <code class="docutils literal notranslate"><span class="pre">cls</span></code> argument will
also be passed to the function. A concern was raised that there may be
cases where it is desired to apply the narrowing logic on <code class="docutils literal notranslate"><span class="pre">self</span></code> and <code class="docutils literal notranslate"><span class="pre">cls</span></code>.
This is an unusual use case, and accommodating it would significantly
complicate the implementation of user-defined type guards. It was therefore
decided that no special provision would be made for it. If narrowing
of <code class="docutils literal notranslate"><span class="pre">self</span></code> or <code class="docutils literal notranslate"><span class="pre">cls</span></code> is required, the value can be passed as an explicit
argument to a type guard function.</p>
</section>
</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-0647.rst">https://github.com/python/peps/blob/main/peps/pep-0647.rst</a></p>
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0647.rst">2024-06-11 22:12:09 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="#specification">Specification</a><ul>
<li><a class="reference internal" href="#typeguard-type">TypeGuard Type</a></li>
</ul>
</li>
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></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="#decorator-syntax">Decorator Syntax</a></li>
<li><a class="reference internal" href="#enforcing-strict-narrowing">Enforcing Strict Narrowing</a></li>
<li><a class="reference internal" href="#conditionally-applying-typeguard-type">Conditionally Applying TypeGuard Type</a></li>
<li><a class="reference internal" href="#narrowing-of-arbitrary-parameters">Narrowing of Arbitrary Parameters</a></li>
<li><a class="reference internal" href="#narrowing-of-implicit-self-and-cls-parameters">Narrowing of Implicit “self” and “cls” Parameters</a></li>
</ul>
</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-0647.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>