463 lines
40 KiB
HTML
463 lines
40 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 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> » </li>
|
||
<li><a href="../pep-0000/">PEP Index</a> » </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 <erictr at microsoft.com></dd>
|
||
<dt class="field-even">Sponsor<span class="colon">:</span></dt>
|
||
<dd class="field-even">Guido van Rossum <guido at python.org></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@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@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 program’s 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"># "is None" 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"># "isinstance" 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">-></span> <span class="nb">bool</span><span class="p">:</span>
|
||
<span class="w"> </span><span class="sd">"""Determines whether all objects in the list are strings"""</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">" "</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">-></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">"""Determines whether all objects in the list are strings"""</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">-></span> <span class="s2">"TypeGuard[Person]"</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">"name"</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">"age"</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">"Age: </span><span class="si">{</span><span class="n">val</span><span class="p">[</span><span class="s1">'age'</span><span class="p">]</span><span class="si">}</span><span class="s2">"</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">"Not a person!"</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">"_T"</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">-></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">-></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">"_T"</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">-></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, it’s 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">-></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">-></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">-></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>TypeScript’s 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> |