python-peps/pep-0747/index.html

719 lines
78 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 747 Annotating Type Forms | peps.python.org</title>
<link rel="shortcut icon" href="../_static/py.png">
<link rel="canonical" href="https://peps.python.org/pep-0747/">
<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 747 Annotating Type Forms | peps.python.org'>
<meta property="og:description" content="Type expressions provide a standardized way to specify types in the Python type system. When a type expression is evaluated at runtime, the resulting type form object encodes the information supplied in the type expression. This enables a variety of use...">
<meta property="og:type" content="website">
<meta property="og:url" content="https://peps.python.org/pep-0747/">
<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="Type expressions provide a standardized way to specify types in the Python type system. When a type expression is evaluated at runtime, the resulting type form object encodes the information supplied in the type expression. This enables a variety of use...">
<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 747</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 747 Annotating Type Forms</h1>
<dl class="rfc2822 field-list simple">
<dt class="field-odd">Author<span class="colon">:</span></dt>
<dd class="field-odd">David Foster &lt;david at dafoster.net&gt;, Eric Traut &lt;erictr at microsoft.com&gt;</dd>
<dt class="field-even">Sponsor<span class="colon">:</span></dt>
<dd class="field-even">Jelle Zijlstra &lt;jelle.zijlstra at gmail.com&gt;</dd>
<dt class="field-odd">Discussions-To<span class="colon">:</span></dt>
<dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/pep-747-typeexpr-type-hint-for-a-type-expression/55984">Discourse thread</a></dd>
<dt class="field-even">Status<span class="colon">:</span></dt>
<dd class="field-even"><abbr title="Proposal under active discussion and revision">Draft</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">27-May-2024</dd>
<dt class="field-even">Python-Version<span class="colon">:</span></dt>
<dd class="field-even">3.14</dd>
<dt class="field-odd">Post-History<span class="colon">:</span></dt>
<dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/typeform-spelling-for-a-type-annotation-object-at-runtime/51435" title="Discourse thread">19-Apr-2024</a>, <a class="reference external" href="https://discuss.python.org/t/typeform-spelling-for-a-type-annotation-object-at-runtime/51435/7/" title="Discourse message">04-May-2024</a>, <a class="reference external" href="https://discuss.python.org/t/pep-747-typeexpr-type-hint-for-a-type-expression/55984" title="Discourse thread">17-Jun-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><ul>
<li><a class="reference internal" href="#why-not-type-c">Why not <code class="docutils literal notranslate"><span class="pre">type[C]</span></code>?</a></li>
<li><a class="reference internal" href="#typeform-use-cases">TypeForm use cases</a></li>
</ul>
</li>
<li><a class="reference internal" href="#specification">Specification</a><ul>
<li><a class="reference internal" href="#implicit-typeform-evaluation">Implicit <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> Evaluation</a></li>
<li><a class="reference internal" href="#explicit-typeform-evaluation">Explicit <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> Evaluation</a></li>
<li><a class="reference internal" href="#assignability">Assignability</a></li>
</ul>
</li>
<li><a class="reference internal" href="#backward-compatibility">Backward Compatibility</a></li>
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li>
<li><a class="reference internal" href="#advanced-examples">Advanced Examples</a><ul>
<li><a class="reference internal" href="#introspecting-type-form-objects">Introspecting type form objects</a></li>
<li><a class="reference internal" href="#combining-with-a-type-variable">Combining with a type variable</a></li>
<li><a class="reference internal" href="#combining-with-type">Combining with <code class="docutils literal notranslate"><span class="pre">type</span></code></a></li>
<li><a class="reference internal" href="#combining-with-typeis-and-typeguard">Combining with <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>
<li><a class="reference internal" href="#challenges-when-accepting-all-typeforms">Challenges When Accepting All TypeForms</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="#alternative-names">Alternative names</a></li>
<li><a class="reference internal" href="#widen-type-c-to-support-all-type-expressions">Widen <code class="docutils literal notranslate"><span class="pre">type[C]</span></code> to support all type expressions</a></li>
<li><a class="reference internal" href="#accept-arbitrary-annotation-expressions">Accept arbitrary annotation expressions</a></li>
<li><a class="reference internal" href="#pattern-matching-on-type-forms">Pattern matching on type forms</a></li>
</ul>
</li>
<li><a class="reference internal" href="#footnotes">Footnotes</a></li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
</details></section>
<section id="abstract">
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
<p><a class="reference external" href="https://typing.readthedocs.io/en/latest/spec/annotations.html#type-expression" title="(in typing)"><span class="xref std std-ref">Type expressions</span></a> provide a standardized way
to specify types in the Python type system. When a type expression is
evaluated at runtime, the resulting <em>type form object</em> encodes the information
supplied in the type expression. This enables a variety of use cases including
runtime type checking, introspection, and metaprogramming.</p>
<p>Such use cases have proliferated, but there is currently no way to accurately
annotate functions that accept type form objects. Developers are forced to use
an overly-wide type like <code class="docutils literal notranslate"><span class="pre">object</span></code>, which makes some use cases impossible and
generally reduces type safety. This PEP addresses this limitation by
introducing a new special form <code class="docutils literal notranslate"><span class="pre">typing.TypeForm</span></code>.</p>
<p>This PEP makes no changes to the Python grammar. Correct usage of <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> is
intended to be enforced only by type checkers, not by the Python runtime.</p>
</section>
<section id="motivation">
<h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
<p>A function that operates on type form objects must understand how type
expression details are encoded in these objects. For example, <code class="docutils literal notranslate"><span class="pre">int</span> <span class="pre">|</span> <span class="pre">str</span></code>,
<code class="docutils literal notranslate"><span class="pre">&quot;int</span> <span class="pre">|</span> <span class="pre">str&quot;</span></code>, <code class="docutils literal notranslate"><span class="pre">list[int]</span></code>, and <code class="docutils literal notranslate"><span class="pre">MyTypeAlias</span></code> are all valid type
expressions, and they evaluate to instances of <code class="docutils literal notranslate"><span class="pre">types.UnionType</span></code>,
<code class="docutils literal notranslate"><span class="pre">builtins.str</span></code>, <code class="docutils literal notranslate"><span class="pre">types.GenericAlias</span></code>, and <code class="docutils literal notranslate"><span class="pre">typing.TypeAliasType</span></code>,
respectively.</p>
<p>There is currently no way to indicate to a type checker that a function accepts
type form objects and knows how to work with them. <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> addresses this
limitation. For example, here is a function that checks whether a value is
assignable to a specified type and returns None if it is not:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">trycast</span><span class="p">[</span><span class="n">T</span><span class="p">](</span><span class="n">typx</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">T</span><span class="p">],</span> <span class="n">value</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span>
</pre></div>
</div>
<p>The use of <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> and the type variable <code class="docutils literal notranslate"><span class="pre">T</span></code> describes a relationship
between the type form passed to parameter <code class="docutils literal notranslate"><span class="pre">typx</span></code> and the functions
return type.</p>
<p><code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> can also be used with <a class="reference external" href="https://typing.readthedocs.io/en/latest/spec/narrowing.html#typeis" title="(in typing)"><span>TypeIs</span></a> to define custom type
narrowing behaviors:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">isassignable</span><span class="p">[</span><span class="n">T</span><span class="p">](</span><span class="n">value</span><span class="p">:</span> <span class="nb">object</span><span class="p">,</span> <span class="n">typx</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">TypeIs</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span> <span class="o">...</span>
<span class="n">request_json</span><span class="p">:</span> <span class="nb">object</span> <span class="o">=</span> <span class="o">...</span>
<span class="k">if</span> <span class="n">isassignable</span><span class="p">(</span><span class="n">request_json</span><span class="p">,</span> <span class="n">MyTypedDict</span><span class="p">):</span>
<span class="n">assert_type</span><span class="p">(</span><span class="n">request_json</span><span class="p">,</span> <span class="n">MyTypedDict</span><span class="p">)</span> <span class="c1"># Type of variable is narrowed</span>
</pre></div>
</div>
<p>The <code class="docutils literal notranslate"><span class="pre">isassignable</span></code> function implements something like an enhanced
<code class="docutils literal notranslate"><span class="pre">isinstance</span></code> check. This is useful for validating whether a value decoded
from JSON conforms to a particular structure of nested <code class="docutils literal notranslate"><span class="pre">TypedDict</span></code>s,
lists, unions, <code class="docutils literal notranslate"><span class="pre">Literal</span></code>s, or any other type form that can be described
with a type expression. This kind of check was alluded to in
<a class="pep reference internal" href="../pep-0589/#using-typeddict-types" title="PEP 589 TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys § Using TypedDict Types">PEP 589</a> but could not be implemented without
<code class="docutils literal notranslate"><span class="pre">TypeForm</span></code>.</p>
<section id="why-not-type-c">
<h3><a class="toc-backref" href="#why-not-type-c" role="doc-backlink">Why not <code class="docutils literal notranslate"><span class="pre">type[C]</span></code>?</a></h3>
<p>One might think that <code class="docutils literal notranslate"><span class="pre">type[C]</span></code> would suffice for these use cases. However,
only class objects (instances of the <code class="docutils literal notranslate"><span class="pre">builtins.type</span></code> class) are assignable
to <code class="docutils literal notranslate"><span class="pre">type[C]</span></code>. Many type form objects do not meet this requirement:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">trycast</span><span class="p">[</span><span class="n">T</span><span class="p">](</span><span class="n">typx</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">T</span><span class="p">],</span> <span class="n">value</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span>
<span class="n">trycast</span><span class="p">(</span><span class="nb">str</span><span class="p">,</span> <span class="s1">&#39;hi&#39;</span><span class="p">)</span> <span class="c1"># OK</span>
<span class="n">trycast</span><span class="p">(</span><span class="n">Literal</span><span class="p">[</span><span class="s1">&#39;hi&#39;</span><span class="p">],</span> <span class="s1">&#39;hi&#39;</span><span class="p">)</span> <span class="c1"># Type violation</span>
<span class="n">trycast</span><span class="p">(</span><span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">,</span> <span class="s1">&#39;hi&#39;</span><span class="p">)</span> <span class="c1"># Type violation</span>
<span class="n">trycast</span><span class="p">(</span><span class="n">MyProtocolClass</span><span class="p">,</span> <span class="n">obj</span><span class="p">)</span> <span class="c1"># Type violation</span>
</pre></div>
</div>
</section>
<section id="typeform-use-cases">
<h3><a class="toc-backref" href="#typeform-use-cases" role="doc-backlink">TypeForm use cases</a></h3>
<p><a class="reference external" href="https://github.com/python/mypy/issues/9773#issuecomment-2017998886">A survey of Python libraries</a> reveals several categories of functions that
would benefit from <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code>:</p>
<ul class="simple">
<li>Assignability checkers:<ul>
<li>Determines whether a value is assignable to a specified type</li>
<li>Pattern 1: <code class="docutils literal notranslate"><span class="pre">def</span> <span class="pre">is_assignable[T](value:</span> <span class="pre">object,</span> <span class="pre">typx:</span> <span class="pre">TypeForm[T])</span> <span class="pre">-&gt;</span> <span class="pre">TypeIs[T]</span></code></li>
<li>Pattern 2: <code class="docutils literal notranslate"><span class="pre">def</span> <span class="pre">is_match[T](value:</span> <span class="pre">object,</span> <span class="pre">typx:</span> <span class="pre">TypeForm[T])</span> <span class="pre">-&gt;</span> <span class="pre">TypeGuard[T]</span></code></li>
<li>Examples: beartype.<a class="reference external" href="https://github.com/beartype/beartype/issues/255">is_bearable</a>, trycast.<a class="reference external" href="https://github.com/davidfstr/trycast?tab=readme-ov-file#isassignable-api">isassignable</a>,
typeguard.<a class="reference external" href="https://typeguard.readthedocs.io/en/latest/api.html#typeguard.check_type">check_type</a>, xdsl.<a class="reference external" href="https://github.com/xdslproject/xdsl/blob/ac12c9ab0d64618475efb98d1d197bdd79f593c3/xdsl/utils/hints.py#L23">isa</a></li>
</ul>
</li>
</ul>
<ul>
<li>Converters:<ul>
<li>If a value is assignable to (or coercible to) a specified type,
a <em>converter</em> returns the value narrowed to (or coerced to) that type.
Otherwise, an exception is raised.</li>
<li>Pattern 1:<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">convert</span><span class="p">[</span><span class="n">T</span><span class="p">](</span><span class="n">value</span><span class="p">:</span> <span class="nb">object</span><span class="p">,</span> <span class="n">typx</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">T</span>
</pre></div>
</div>
<ul class="simple">
<li>Examples: cattrs.BaseConverter.<a class="reference external" href="https://github.com/python-attrs/cattrs/blob/5f5c11627a7f67a23d6212bc7df9f96243c62dc5/src/cattrs/converters.py#L332-L334">structure</a>, trycast.<a class="reference external" href="https://github.com/davidfstr/trycast#checkcast-api">checkcast</a>,
typedload.<a class="reference external" href="https://ltworf.github.io/typedload/">load</a></li>
</ul>
</li>
<li>Pattern 2:<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Converter</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">typx</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span>
<span class="k">def</span> <span class="nf">convert</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span> <span class="o">...</span>
</pre></div>
</div>
<ul class="simple">
<li>Examples: pydantic.<a class="reference external" href="https://stackoverflow.com/a/61021183/604063">TypeAdapter(T).validate_python</a>,
mashumaro.<a class="reference external" href="https://github.com/Fatal1ty/mashumaro?tab=readme-ov-file#usage-example">JSONDecoder(T).decode</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li>Typed field definitions:<ul>
<li>Pattern:<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Field</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
<span class="n">value_type</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">T</span><span class="p">]</span>
</pre></div>
</div>
</li>
<li>Examples: attrs.<a class="reference external" href="https://www.attrs.org/en/stable/api.html#attrs.make_class">make_class</a>,
dataclasses.<a class="reference external" href="https://github.com/python/typeshed/issues/11653">make_dataclass</a> <a class="footnote-reference brackets" href="#dataclassinitvar" id="id1">[3]</a>, <a class="reference external" href="https://github.com/Fatal1ty/openapify/blob/c8d968c7c9c8fd7d4888bd2ddbe18ffd1469f3ca/openapify/core/models.py#L16">openapify</a></li>
</ul>
</li>
</ul>
<p>The survey also identified some introspection functions that accept runtime
type forms as input. Today, these functions are annotated with <code class="docutils literal notranslate"><span class="pre">object</span></code>:</p>
<ul class="simple">
<li>General introspection operations:<ul>
<li>Pattern: <code class="docutils literal notranslate"><span class="pre">def</span> <span class="pre">get_annotation_info(typx:</span> <span class="pre">object)</span> <span class="pre">-&gt;</span> <span class="pre">object</span></code></li>
<li>Examples: typing.{<a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.get_origin">get_origin</a>, <a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.get_args">get_args</a>},
<a class="reference external" href="https://github.com/ilevkivskyi/typing_inspect?tab=readme-ov-file#readme">typing_inspect</a>.{is_*_type, get_origin, get_parameters}</li>
</ul>
</li>
</ul>
<p>These functions accept values evaluated from arbitrary annotation expressions,
not just type expressions, so they cannot be altered to use <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code>.</p>
</section>
</section>
<section id="specification">
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
<p>When a type expression is evaluated at runtime, the resulting value is a
<em>type form</em> object. This value encodes the information supplied in the type
expression, and it represents the type described by that type expression.</p>
<p><code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> is a special form that, when used in a type expression, describes
a set of type form objects. It accepts a single type argument, which must be a
valid type expression. <code class="docutils literal notranslate"><span class="pre">TypeForm[T]</span></code> describes the set of all type form
objects that represent the type <code class="docutils literal notranslate"><span class="pre">T</span></code> or types that are
<a class="reference external" href="https://typing.readthedocs.io/en/latest/spec/glossary.html#term-assignable" title="(in typing)"><span class="xref std std-term">assignable to</span></a> <code class="docutils literal notranslate"><span class="pre">T</span></code>. For example,
<code class="docutils literal notranslate"><span class="pre">TypeForm[str</span> <span class="pre">|</span> <span class="pre">None]</span></code> describes the set of all type form objects
that represent a type assignable to <code class="docutils literal notranslate"><span class="pre">str</span> <span class="pre">|</span> <span class="pre">None</span></code>:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">ok1</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">]</span> <span class="o">=</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="c1"># OK</span>
<span class="n">ok2</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">]</span> <span class="o">=</span> <span class="nb">str</span> <span class="c1"># OK</span>
<span class="n">ok3</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span> <span class="c1"># OK</span>
<span class="n">ok4</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">]</span> <span class="o">=</span> <span class="n">Literal</span><span class="p">[</span><span class="kc">None</span><span class="p">]</span> <span class="c1"># OK</span>
<span class="n">ok5</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">]</span> <span class="o">=</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="c1"># OK</span>
<span class="n">ok6</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;str | None&quot;</span> <span class="c1"># OK</span>
<span class="n">ok7</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">]</span> <span class="o">=</span> <span class="n">Any</span> <span class="c1"># OK</span>
<span class="n">err1</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">]</span> <span class="o">=</span> <span class="nb">str</span> <span class="o">|</span> <span class="nb">int</span> <span class="c1"># Error</span>
<span class="n">err2</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">]</span> <span class="o">=</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">]</span> <span class="c1"># Error</span>
</pre></div>
</div>
<p>By this same definition, <code class="docutils literal notranslate"><span class="pre">TypeForm[Any]</span></code> describes a type form object
that represents the type <code class="docutils literal notranslate"><span class="pre">Any</span></code> or any type that is assignable to <code class="docutils literal notranslate"><span class="pre">Any</span></code>.
Since all types in the Python type system are assignable to <code class="docutils literal notranslate"><span class="pre">Any</span></code>,
<code class="docutils literal notranslate"><span class="pre">TypeForm[Any]</span></code> describes the set of all type form objects
evaluated from all valid type expressions.</p>
<p>The type expression <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code>, with no type argument provided, is
equivalent to <code class="docutils literal notranslate"><span class="pre">TypeForm[Any]</span></code>.</p>
<section id="implicit-typeform-evaluation">
<h3><a class="toc-backref" href="#implicit-typeform-evaluation" role="doc-backlink">Implicit <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> Evaluation</a></h3>
<p>When a static type checker encounters an expression that follows all of the
syntactic, semantic and contextual rules for a type expression as detailed
in the typing spec, the evaluated type of this expression should be assignable
to <code class="docutils literal notranslate"><span class="pre">TypeForm[T]</span></code> if the type it describes is assignable to <code class="docutils literal notranslate"><span class="pre">T</span></code>.</p>
<p>For example, if a static type checker encounters the expression <code class="docutils literal notranslate"><span class="pre">str</span> <span class="pre">|</span> <span class="pre">None</span></code>,
it may normally evaluate its type as <code class="docutils literal notranslate"><span class="pre">UnionType</span></code> because it produces a
runtime value that is an instance of <code class="docutils literal notranslate"><span class="pre">types.UnionType</span></code>. However, because
this expression is a valid type expression, it is also assignable to the
type <code class="docutils literal notranslate"><span class="pre">TypeForm[str</span> <span class="pre">|</span> <span class="pre">None]</span></code>:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">v1_actual</span><span class="p">:</span> <span class="n">UnionType</span> <span class="o">=</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="c1"># OK</span>
<span class="n">v1_type_form</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">]</span> <span class="o">=</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="c1"># OK</span>
<span class="n">v2_actual</span><span class="p">:</span> <span class="nb">type</span> <span class="o">=</span> <span class="nb">list</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="c1"># OK</span>
<span class="n">v2_type_form</span><span class="p">:</span> <span class="n">TypeForm</span> <span class="o">=</span> <span class="nb">list</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="c1"># OK</span>
</pre></div>
</div>
<p>The <code class="docutils literal notranslate"><span class="pre">Annotated</span></code> special form is allowed in type expressions, so it can
also appear in an expression that is assignable to <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code>. Consistent
with the typing specs rules for <code class="docutils literal notranslate"><span class="pre">Annotated</span></code>, a static type checker may choose
to ignore any <code class="docutils literal notranslate"><span class="pre">Annotated</span></code> metadata that it does not understand:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">v3</span><span class="p">:</span> <span class="n">TypeForm</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="o">=</span> <span class="n">Annotated</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="s2">&quot;metadata&quot;</span><span class="p">]</span> <span class="c1"># OK</span>
<span class="n">v4</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">Annotated</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="s2">&quot;metadata&quot;</span><span class="p">]]</span> <span class="o">=</span> <span class="nb">int</span> <span class="o">|</span> <span class="nb">str</span> <span class="c1"># OK</span>
</pre></div>
</div>
<p>A string literal expression containing a valid type expression should likewise
be assignable to <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code>:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">v5</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="s2">&quot;set[str]&quot;</span> <span class="c1"># OK</span>
</pre></div>
</div>
<p>Expressions that violate one or more of the syntactic, semantic, or contextual
rules for type expressions should not evaluate to a <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> type. The rules
for type expression validity are explained in detail within the typing spec, so
they are not repeated here:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">bad1</span><span class="p">:</span> <span class="n">TypeForm</span> <span class="o">=</span> <span class="nb">tuple</span><span class="p">()</span> <span class="c1"># Error: Call expression not allowed in type expression</span>
<span class="n">bad2</span><span class="p">:</span> <span class="n">TypeForm</span> <span class="o">=</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"># Error: Tuple expression not allowed in type expression</span>
<span class="n">bad3</span><span class="p">:</span> <span class="n">TypeForm</span> <span class="o">=</span> <span class="mi">1</span> <span class="c1"># Non-class object not allowed in type expression</span>
<span class="n">bad4</span><span class="p">:</span> <span class="n">TypeForm</span> <span class="o">=</span> <span class="n">Self</span> <span class="c1"># Error: Self not allowed outside of a class</span>
<span class="n">bad5</span><span class="p">:</span> <span class="n">TypeForm</span> <span class="o">=</span> <span class="n">Literal</span><span class="p">[</span><span class="n">var</span><span class="p">]</span> <span class="c1"># Error: Variable not allowed in type expression</span>
<span class="n">bad6</span><span class="p">:</span> <span class="n">TypeForm</span> <span class="o">=</span> <span class="n">Literal</span><span class="p">[</span><span class="sa">f</span><span class="s2">&quot;&quot;</span><span class="p">]</span> <span class="c1"># Error: f-strings not allowed in type expression</span>
<span class="n">bad7</span><span class="p">:</span> <span class="n">TypeForm</span> <span class="o">=</span> <span class="n">ClassVar</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="c1"># Error: ClassVar not allowed in type expression</span>
<span class="n">bad8</span><span class="p">:</span> <span class="n">TypeForm</span> <span class="o">=</span> <span class="n">Required</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="c1"># Error: Required not allowed in type expression</span>
<span class="n">bad9</span><span class="p">:</span> <span class="n">TypeForm</span> <span class="o">=</span> <span class="n">Final</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="c1"># Error: Final not allowed in type expression</span>
<span class="n">bad10</span><span class="p">:</span> <span class="n">TypeForm</span> <span class="o">=</span> <span class="n">Unpack</span><span class="p">[</span><span class="n">Ts</span><span class="p">]</span> <span class="c1"># Error: Unpack not allowed in this context</span>
<span class="n">bad11</span><span class="p">:</span> <span class="n">TypeForm</span> <span class="o">=</span> <span class="n">Optional</span> <span class="c1"># Error: Invalid use of Optional special form</span>
<span class="n">bad12</span><span class="p">:</span> <span class="n">TypeForm</span> <span class="o">=</span> <span class="n">T</span> <span class="c1"># Error if T is an out-of-scope TypeVar</span>
<span class="n">bad13</span><span class="p">:</span> <span class="n">TypeForm</span> <span class="o">=</span> <span class="s2">&quot;int + str&quot;</span> <span class="c1"># Error: invalid quoted type expression</span>
</pre></div>
</div>
</section>
<section id="explicit-typeform-evaluation">
<h3><a class="toc-backref" href="#explicit-typeform-evaluation" role="doc-backlink">Explicit <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> Evaluation</a></h3>
<p><code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> also acts as a function that can be called with a single argument.
Type checkers should validate that this argument is a valid type expression:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">x1</span> <span class="o">=</span> <span class="n">TypeForm</span><span class="p">(</span><span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">)</span>
<span class="n">reveal_type</span><span class="p">(</span><span class="n">v1</span><span class="p">)</span> <span class="c1"># Revealed type is &quot;TypeForm[str | None]&quot;</span>
<span class="n">x2</span> <span class="o">=</span> <span class="n">TypeForm</span><span class="p">(</span><span class="s2">&quot;list[int]&quot;</span><span class="p">)</span>
<span class="n">revealed_type</span><span class="p">(</span><span class="n">v2</span><span class="p">)</span> <span class="c1"># Revealed type is &quot;TypeForm[list[int]]&quot;</span>
<span class="n">x3</span> <span class="o">=</span> <span class="n">TypeForm</span><span class="p">(</span><span class="s1">&#39;type(1)&#39;</span><span class="p">)</span> <span class="c1"># Error: invalid type expression</span>
</pre></div>
</div>
<p>At runtime the <code class="docutils literal notranslate"><span class="pre">TypeForm(...)</span></code> callable simply returns the value passed to it.</p>
<p>This explicit syntax serves two purposes. First, it documents the developers
intent to use the value as a type form object. Second, static type checkers
validate that all rules for type expressions are followed:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">x4</span> <span class="o">=</span> <span class="nb">type</span><span class="p">(</span><span class="nb">int</span><span class="p">)</span> <span class="c1"># No error, evaluates to &quot;type[int]&quot;</span>
<span class="n">x5</span> <span class="o">=</span> <span class="n">TypeForm</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="nb">int</span><span class="p">))</span> <span class="c1"># Error: call not allowed in type expression</span>
</pre></div>
</div>
</section>
<section id="assignability">
<h3><a class="toc-backref" href="#assignability" role="doc-backlink">Assignability</a></h3>
<p><code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> has a single type parameter, which is covariant. That means
<code class="docutils literal notranslate"><span class="pre">TypeForm[B]</span></code> is assignable to <code class="docutils literal notranslate"><span class="pre">TypeForm[A]</span></code> if <code class="docutils literal notranslate"><span class="pre">B</span></code> is assignable to
<code class="docutils literal notranslate"><span class="pre">A</span></code>:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">get_type_form</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">TypeForm</span><span class="p">[</span><span class="nb">int</span><span class="p">]:</span> <span class="o">...</span>
<span class="n">t1</span><span class="p">:</span> <span class="n">TypeForm</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="o">=</span> <span class="n">get_type_form</span><span class="p">()</span> <span class="c1"># OK</span>
<span class="n">t2</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">get_type_form</span><span class="p">()</span> <span class="c1"># Error</span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">type[T]</span></code> is a subtype of <code class="docutils literal notranslate"><span class="pre">TypeForm[T]</span></code>, which means that <code class="docutils literal notranslate"><span class="pre">type[B]</span></code> is
assignable to <code class="docutils literal notranslate"><span class="pre">TypeForm[A]</span></code> if <code class="docutils literal notranslate"><span class="pre">B</span></code> is assignable to <code class="docutils literal notranslate"><span class="pre">A</span></code>:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">get_type</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">type</span><span class="p">[</span><span class="nb">int</span><span class="p">]:</span> <span class="o">...</span>
<span class="n">t3</span><span class="p">:</span> <span class="n">TypeForm</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="o">=</span> <span class="n">get_type</span><span class="p">()</span> <span class="c1"># OK</span>
<span class="n">t4</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">get_type</span><span class="p">()</span> <span class="c1"># Error</span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> is a subtype of <code class="docutils literal notranslate"><span class="pre">object</span></code> and is assumed to have all of the
attributes and methods of <code class="docutils literal notranslate"><span class="pre">object</span></code>.</p>
</section>
</section>
<section id="backward-compatibility">
<h2><a class="toc-backref" href="#backward-compatibility" role="doc-backlink">Backward Compatibility</a></h2>
<p>This PEP clarifies static type checker behaviors when evaluating type
expressions in “value expression” contexts (that is, contexts where type
expressions are not mandated by the typing spec). In the absence of a
<code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> type annotation, existing type evaluation behaviors persist,
so no backward compatibility issues are anticipated. For example, if a static
type checker previously evaluated the type of expression <code class="docutils literal notranslate"><span class="pre">str</span> <span class="pre">|</span> <span class="pre">None</span></code> as
<code class="docutils literal notranslate"><span class="pre">UnionType</span></code>, it will continue to do so unless this expression is assigned
to a variable or parameter whose type is annotated as <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code>.</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>Type expressions are used in annotations to describe which values are accepted
by a function parameter, returned by a function, or stored in a variable:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span> parameter type return type
| |
v v
def plus(n1: int, n2: int) -&gt; int:
sum: int = n1 + n2
^
|
variable type
return sum
</pre></div>
</div>
<p>Type expressions evaluate to valid <em>type form</em> objects at runtime and can be
assigned to variables and manipulated like any other data in a program:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span> a variable a type expression
| |
v v
int_type_form: TypeForm = int | None
^
|
the type of a type form object
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">TypeForm[]</span></code> is how you spell the type of a <em>type form</em> object, which is
a runtime representation of a type.</p>
<p><code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> is similar to <code class="docutils literal notranslate"><span class="pre">type</span></code>, but <code class="docutils literal notranslate"><span class="pre">type</span></code> is compatible only with
<strong>class objects</strong> like <code class="docutils literal notranslate"><span class="pre">int</span></code>, <code class="docutils literal notranslate"><span class="pre">str</span></code>, <code class="docutils literal notranslate"><span class="pre">list</span></code>, or <code class="docutils literal notranslate"><span class="pre">MyClass</span></code>.
<code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> accommodates any type form that can be expressed using
a valid type expression, including those with brackets (<code class="docutils literal notranslate"><span class="pre">list[int]</span></code>), union
operators (<code class="docutils literal notranslate"><span class="pre">int</span> <span class="pre">|</span> <span class="pre">None</span></code>), and special forms (<code class="docutils literal notranslate"><span class="pre">Any</span></code>, <code class="docutils literal notranslate"><span class="pre">LiteralString</span></code>,
<code class="docutils literal notranslate"><span class="pre">Never</span></code>, etc.).</p>
<p>Most programmers will not define their <em>own</em> functions that accept a <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code>
parameter or return a <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> value. It is more common to pass a type
form object to a library function that knows how to decode and use such objects.</p>
<p>For example, the <code class="docutils literal notranslate"><span class="pre">isassignable</span></code> function in the <code class="docutils literal notranslate"><span class="pre">trycast</span></code> library
can be used like Pythons built-in <code class="docutils literal notranslate"><span class="pre">isinstance</span></code> function to check whether
a value matches the shape of a particular type. <code class="docutils literal notranslate"><span class="pre">isassignable</span></code> accepts <em>any</em>
type form object as input.</p>
<ul>
<li>Yes:<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">trycast</span> <span class="kn">import</span> <span class="n">isassignable</span>
<span class="k">if</span> <span class="n">isassignable</span><span class="p">(</span><span class="n">some_object</span><span class="p">,</span> <span class="n">MyTypedDict</span><span class="p">):</span> <span class="c1"># OK: MyTypedDict is a TypeForm[]</span>
<span class="o">...</span>
</pre></div>
</div>
</li>
<li>No:<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">some_object</span><span class="p">,</span> <span class="n">MyTypedDict</span><span class="p">):</span> <span class="c1"># ERROR: MyTypedDict is not a type[]</span>
<span class="o">...</span>
</pre></div>
</div>
</li>
</ul>
</section>
<section id="advanced-examples">
<h2><a class="toc-backref" href="#advanced-examples" role="doc-backlink">Advanced Examples</a></h2>
<p>If you want to write your own runtime type checker or a function that
manipulates type form objects as values at runtime, this section provides
examples of how such a function can use <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code>.</p>
<section id="introspecting-type-form-objects">
<h3><a class="toc-backref" href="#introspecting-type-form-objects" role="doc-backlink">Introspecting type form objects</a></h3>
<p>Functions like <code class="docutils literal notranslate"><span class="pre">typing.get_origin</span></code> and <code class="docutils literal notranslate"><span class="pre">typing.get_args</span></code> can be used to
extract components of some type form objects.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">typing</span>
<span class="k">def</span> <span class="nf">strip_annotated_metadata</span><span class="p">(</span><span class="n">typx</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
<span class="k">if</span> <span class="n">typing</span><span class="o">.</span><span class="n">get_origin</span><span class="p">(</span><span class="n">typx</span><span class="p">)</span> <span class="ow">is</span> <span class="n">typing</span><span class="o">.</span><span class="n">Annotated</span><span class="p">:</span>
<span class="n">typx</span> <span class="o">=</span> <span class="n">cast</span><span class="p">(</span><span class="n">TypeForm</span><span class="p">[</span><span class="n">T</span><span class="p">],</span> <span class="n">typing</span><span class="o">.</span><span class="n">get_args</span><span class="p">(</span><span class="n">typx</span><span class="p">)[</span><span class="mi">0</span><span class="p">])</span>
<span class="k">return</span> <span class="n">typx</span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">isinstance</span></code> and <code class="docutils literal notranslate"><span class="pre">is</span></code> can also be used to distinguish between different
kinds of type form objects:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">types</span>
<span class="kn">import</span> <span class="nn">typing</span>
<span class="k">def</span> <span class="nf">split_union</span><span class="p">(</span><span class="n">typx</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="n">TypeForm</span><span class="p">,</span> <span class="o">...</span><span class="p">]:</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">typ</span><span class="p">,</span> <span class="n">types</span><span class="o">.</span><span class="n">UnionType</span><span class="p">):</span> <span class="c1"># X | Y</span>
<span class="k">return</span> <span class="n">cast</span><span class="p">(</span><span class="nb">tuple</span><span class="p">[</span><span class="n">TypeForm</span><span class="p">,</span> <span class="o">...</span><span class="p">],</span> <span class="n">typing</span><span class="o">.</span><span class="n">get_args</span><span class="p">(</span><span class="n">typ</span><span class="p">))</span>
<span class="k">if</span> <span class="n">typing</span><span class="o">.</span><span class="n">get_origin</span><span class="p">(</span><span class="n">typ</span><span class="p">)</span> <span class="ow">is</span> <span class="n">typing</span><span class="o">.</span><span class="n">Union</span><span class="p">:</span> <span class="c1"># Union[X, Y]</span>
<span class="k">return</span> <span class="n">cast</span><span class="p">(</span><span class="nb">tuple</span><span class="p">[</span><span class="n">TypeForm</span><span class="p">,</span> <span class="o">...</span><span class="p">],</span> <span class="n">typing</span><span class="o">.</span><span class="n">get_args</span><span class="p">(</span><span class="n">typ</span><span class="p">))</span>
<span class="k">if</span> <span class="n">typ</span> <span class="ow">in</span> <span class="p">(</span><span class="n">typing</span><span class="o">.</span><span class="n">Never</span><span class="p">,</span> <span class="n">typing</span><span class="o">.</span><span class="n">NoReturn</span><span class="p">,):</span>
<span class="k">return</span> <span class="p">()</span>
<span class="k">return</span> <span class="p">(</span><span class="n">typ</span><span class="p">,)</span>
</pre></div>
</div>
</section>
<section id="combining-with-a-type-variable">
<h3><a class="toc-backref" href="#combining-with-a-type-variable" role="doc-backlink">Combining with a type variable</a></h3>
<p><code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> can be parameterized by a type variable that is used elsewhere
within the same function definition:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">as_instance</span><span class="p">[</span><span class="n">T</span><span class="p">](</span><span class="n">typx</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">T</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">return</span> <span class="n">typ</span><span class="p">()</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">typ</span><span class="p">,</span> <span class="nb">type</span><span class="p">)</span> <span class="k">else</span> <span class="kc">None</span>
</pre></div>
</div>
</section>
<section id="combining-with-type">
<h3><a class="toc-backref" href="#combining-with-type" role="doc-backlink">Combining with <code class="docutils literal notranslate"><span class="pre">type</span></code></a></h3>
<p>Both <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> and <code class="docutils literal notranslate"><span class="pre">type</span></code> can be parameterized by the same type
variable within the same function definition:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">as_type</span><span class="p">[</span><span class="n">T</span><span class="p">](</span><span class="n">typx</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">type</span><span class="p">[</span><span class="n">T</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">return</span> <span class="n">typ</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">typ</span><span class="p">,</span> <span class="nb">type</span><span class="p">)</span> <span class="k">else</span> <span class="kc">None</span>
</pre></div>
</div>
</section>
<section id="combining-with-typeis-and-typeguard">
<h3><a class="toc-backref" href="#combining-with-typeis-and-typeguard" role="doc-backlink">Combining with <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>A type variable can also be used by a <code class="docutils literal notranslate"><span class="pre">TypeIs</span></code> or <code class="docutils literal notranslate"><span class="pre">TypeGuard</span></code> return type:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">isassignable</span><span class="p">[</span><span class="n">T</span><span class="p">](</span><span class="n">value</span><span class="p">:</span> <span class="nb">object</span><span class="p">,</span> <span class="n">typx</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">TypeIs</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span> <span class="o">...</span>
<span class="n">count</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="nb">str</span> <span class="o">=</span> <span class="o">...</span>
<span class="k">if</span> <span class="n">isassignable</span><span class="p">(</span><span class="n">count</span><span class="p">,</span> <span class="nb">int</span><span class="p">):</span>
<span class="n">assert_type</span><span class="p">(</span><span class="n">count</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">count</span><span class="p">,</span> <span class="nb">str</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="challenges-when-accepting-all-typeforms">
<h3><a class="toc-backref" href="#challenges-when-accepting-all-typeforms" role="doc-backlink">Challenges When Accepting All TypeForms</a></h3>
<p>A function that takes an <em>arbitrary</em> <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> as input must support a
variety of possible type form objects. Such functions are not easy to write.</p>
<ul class="simple">
<li>New special forms are introduced with each new Python version, and
special handling may be required for each one.</li>
<li>Quoted annotations <a class="footnote-reference brackets" href="#quoted-less-common" id="id2">[5]</a> (like <code class="docutils literal notranslate"><span class="pre">'list[str]'</span></code>)
must be <em>parsed</em> (to something like <code class="docutils literal notranslate"><span class="pre">list[str]</span></code>).</li>
<li>Resolving quoted forward references inside type expressions is typically
done with <code class="docutils literal notranslate"><span class="pre">eval()</span></code>, which is difficult to use in a safe way.</li>
<li>Recursive types like <code class="docutils literal notranslate"><span class="pre">IntTree</span> <span class="pre">=</span> <span class="pre">list[int</span> <span class="pre">|</span> <span class="pre">'IntTree']</span></code> are difficult
to resolve.</li>
<li>User-defined generic types (like Djangos <code class="docutils literal notranslate"><span class="pre">QuerySet[User]</span></code>) can introduce
non-standard behaviors that require runtime support.</li>
</ul>
</section>
</section>
<section id="reference-implementation">
<h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2>
<p>Pyright (version 1.1.379) provides a reference implementation for <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code>.</p>
<p>Mypy contributors also <a class="reference external" href="https://github.com/python/mypy/issues/9773">plan to implement</a>
support for <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code>.</p>
<p>A reference implementation of the runtime component is provided in the
<code class="docutils literal notranslate"><span class="pre">typing_extensions</span></code> module.</p>
</section>
<section id="rejected-ideas">
<h2><a class="toc-backref" href="#rejected-ideas" role="doc-backlink">Rejected Ideas</a></h2>
<section id="alternative-names">
<h3><a class="toc-backref" href="#alternative-names" role="doc-backlink">Alternative names</a></h3>
<p>Alternate names were considered for <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code>. <code class="docutils literal notranslate"><span class="pre">TypeObject</span></code>
and <code class="docutils literal notranslate"><span class="pre">TypeType</span></code> were deemed too generic. <code class="docutils literal notranslate"><span class="pre">TypeExpression</span></code> and <code class="docutils literal notranslate"><span class="pre">TypeExpr</span></code>
were also considered, but these were considered confusing because these objects
are not themselves “expressions” but rather the result of evaluating a type
expression.</p>
</section>
<section id="widen-type-c-to-support-all-type-expressions">
<h3><a class="toc-backref" href="#widen-type-c-to-support-all-type-expressions" role="doc-backlink">Widen <code class="docutils literal notranslate"><span class="pre">type[C]</span></code> to support all type expressions</a></h3>
<p><code class="docutils literal notranslate"><span class="pre">type</span></code> was <a class="reference external" href="https://mail.python.org/archives/list/typing-sig&#64;python.org/message/D5FHORQVPHX3BHUDGF3A3TBZURBXLPHD/">designed</a> to describe class objects, subclasses of the
<code class="docutils literal notranslate"><span class="pre">type</span></code> class. A value with the type <code class="docutils literal notranslate"><span class="pre">type</span></code> is assumed to be instantiable
through a constructor call. Widening the meaning of <code class="docutils literal notranslate"><span class="pre">type</span></code> to represent
arbitrary type form objects would present backward compatibility problems
and would eliminate a way to describe the set of values limited to subclasses
of <code class="docutils literal notranslate"><span class="pre">type</span></code>.</p>
</section>
<section id="accept-arbitrary-annotation-expressions">
<h3><a class="toc-backref" href="#accept-arbitrary-annotation-expressions" role="doc-backlink">Accept arbitrary annotation expressions</a></h3>
<p>Certain special forms act as type qualifiers and can be used in
<em>some</em> but not <em>all</em> annotation contexts:</p>
<p>For example. the type qualifier <code class="docutils literal notranslate"><span class="pre">Final</span></code> can be used as a variable type but
not as a parameter type or a return type:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">some_const</span><span class="p">:</span> <span class="n">Final</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="o">...</span> <span class="c1"># OK</span>
<span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">not_reassignable</span><span class="p">:</span> <span class="n">Final</span><span class="p">[</span><span class="nb">object</span><span class="p">]):</span> <span class="o">...</span> <span class="c1"># Error: Final not allowed here</span>
<span class="k">def</span> <span class="nf">nonsense</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">Final</span><span class="p">[</span><span class="nb">object</span><span class="p">]:</span> <span class="o">...</span> <span class="c1"># Error: Final not alowed here</span>
</pre></div>
</div>
<p>With the exception of <code class="docutils literal notranslate"><span class="pre">Annotated</span></code>, type qualifiers are not allowed in type
expressions. <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> is limited to type expressions because its
assignability rules are based on the assignability rules for types. It is
nonsensical to ask whether <code class="docutils literal notranslate"><span class="pre">Final[int]</span></code> is assignable to <code class="docutils literal notranslate"><span class="pre">int</span></code> because the
former is not a valid type expression.</p>
<p>Functions that wish to operate on objects that are evaluated from annotation
expressions can continue to accept such inputs as <code class="docutils literal notranslate"><span class="pre">object</span></code> parameters.</p>
</section>
<section id="pattern-matching-on-type-forms">
<h3><a class="toc-backref" href="#pattern-matching-on-type-forms" role="doc-backlink">Pattern matching on type forms</a></h3>
<p>It was asserted that some functions may wish to pattern match on the
interior of type expressions in their signatures.</p>
<p>One use case is to allow a function to explicitly enumerate all the
<em>specific</em> kinds of type expressions it supports as input.
Consider the following possible pattern matching syntax:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@overload</span>
<span class="k">def</span> <span class="nf">checkcast</span><span class="p">(</span><span class="n">typx</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">AT</span><span class="o">=</span><span class="n">Annotated</span><span class="p">[</span><span class="n">T</span><span class="p">,</span> <span class="o">*</span><span class="n">A</span><span class="p">]],</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span> <span class="o">...</span>
<span class="nd">@overload</span>
<span class="k">def</span> <span class="nf">checkcast</span><span class="p">(</span><span class="n">typx</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">UT</span><span class="o">=</span><span class="n">Union</span><span class="p">[</span><span class="o">*</span><span class="n">Ts</span><span class="p">]],</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Union</span><span class="p">[</span><span class="o">*</span><span class="n">Ts</span><span class="p">]:</span> <span class="o">...</span>
<span class="nd">@overload</span>
<span class="k">def</span> <span class="nf">checkcast</span><span class="p">(</span><span class="n">typx</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">C</span><span class="p">],</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">C</span><span class="p">:</span> <span class="o">...</span>
<span class="c1"># ... (more)</span>
</pre></div>
</div>
<p>All functions observed in the wild that conceptually accept type form
objects generally try to support <em>all</em> kinds of type expressions, so it
doesnt seem valuable to enumerate a particular subset.</p>
<p>Additionally, the above syntax isnt precise enough to fully describe the
input constraints for a typical function in the wild. For example, many
functions do not support type expressions with quoted subexpressions
like <code class="docutils literal notranslate"><span class="pre">list['Movie']</span></code>.</p>
<p>A second use case for pattern matching is to explicitly match an <code class="docutils literal notranslate"><span class="pre">Annotated</span></code>
form to extract the interior type argument and strip away any metadata:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">checkcast</span><span class="p">(</span>
<span class="n">typx</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">T</span><span class="p">]</span> <span class="o">|</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">AT</span><span class="o">=</span><span class="n">Annotated</span><span class="p">[</span><span class="n">T</span><span class="p">,</span> <span class="o">*</span><span class="n">A</span><span class="p">]],</span>
<span class="n">value</span><span class="p">:</span> <span class="nb">object</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</pre></div>
</div>
<p>However, <code class="docutils literal notranslate"><span class="pre">Annotated[T,</span> <span class="pre">metadata]</span></code> is already treated equivalent to <code class="docutils literal notranslate"><span class="pre">T</span></code>
by static type checkers. Theres no additional value in being explicit about
this behavior. The example above could more simply be written as the equivalent:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">checkcast</span><span class="p">(</span><span class="n">typx</span><span class="p">:</span> <span class="n">TypeForm</span><span class="p">[</span><span class="n">T</span><span class="p">],</span> <span class="n">value</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</pre></div>
</div>
</section>
</section>
<section id="footnotes">
<h2><a class="toc-backref" href="#footnotes" role="doc-backlink">Footnotes</a></h2>
<aside class="footnote-list brackets">
<aside class="footnote brackets" id="type-t" role="doc-footnote">
<dt class="label" id="type-t">[1]</dt>
<dd><a class="reference external" href="https://typing.readthedocs.io/en/latest/spec/special-types.html#type-brackets" title="(in typing)"><span class="xref std std-ref">Type[T]</span></a> spells a class object</aside>
<aside class="footnote brackets" id="typeis" role="doc-footnote">
<dt class="label" id="typeis">[2]</dt>
<dd><a class="reference external" href="https://typing.readthedocs.io/en/latest/spec/narrowing.html#typeis" title="(in typing)"><span class="xref std std-ref">TypeIs[T]</span></a> is similar to bool</aside>
<aside class="footnote brackets" id="dataclassinitvar" role="doc-footnote">
<dt class="label" id="dataclassinitvar">[<a href="#id1">3</a>]</dt>
<dd><code class="docutils literal notranslate"><span class="pre">dataclass.make_dataclass</span></code> allows the type qualifier <code class="docutils literal notranslate"><span class="pre">InitVar[...]</span></code>,
so <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> cannot be used in this case.</aside>
<aside class="footnote brackets" id="forward-ref-normalization" role="doc-footnote">
<dt class="label" id="forward-ref-normalization">[4]</dt>
<dd>Special forms normalize string arguments to <code class="docutils literal notranslate"><span class="pre">ForwardRef</span></code> instances
at runtime using internal helper functions in the <code class="docutils literal notranslate"><span class="pre">typing</span></code> module.
Runtime type checkers may wish to implement similar functions when
working with string-based forward references.</aside>
<aside class="footnote brackets" id="quoted-less-common" role="doc-footnote">
<dt class="label" id="quoted-less-common">[<a href="#id2">5</a>]</dt>
<dd>Quoted annotations are expected to become less common starting in Python
3.14 when <a class="pep reference internal" href="../pep-0649/" title="PEP 649 Deferred Evaluation Of Annotations Using Descriptors">deferred annotations</a> is implemented. However,
code written for earlier Python versions relies on quoted annotations and
will need to be supported for several years.</aside>
</aside>
</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-0747.rst">https://github.com/python/peps/blob/main/peps/pep-0747.rst</a></p>
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0747.rst">2024-09-29 12:39:10 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><ul>
<li><a class="reference internal" href="#why-not-type-c">Why not <code class="docutils literal notranslate"><span class="pre">type[C]</span></code>?</a></li>
<li><a class="reference internal" href="#typeform-use-cases">TypeForm use cases</a></li>
</ul>
</li>
<li><a class="reference internal" href="#specification">Specification</a><ul>
<li><a class="reference internal" href="#implicit-typeform-evaluation">Implicit <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> Evaluation</a></li>
<li><a class="reference internal" href="#explicit-typeform-evaluation">Explicit <code class="docutils literal notranslate"><span class="pre">TypeForm</span></code> Evaluation</a></li>
<li><a class="reference internal" href="#assignability">Assignability</a></li>
</ul>
</li>
<li><a class="reference internal" href="#backward-compatibility">Backward Compatibility</a></li>
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li>
<li><a class="reference internal" href="#advanced-examples">Advanced Examples</a><ul>
<li><a class="reference internal" href="#introspecting-type-form-objects">Introspecting type form objects</a></li>
<li><a class="reference internal" href="#combining-with-a-type-variable">Combining with a type variable</a></li>
<li><a class="reference internal" href="#combining-with-type">Combining with <code class="docutils literal notranslate"><span class="pre">type</span></code></a></li>
<li><a class="reference internal" href="#combining-with-typeis-and-typeguard">Combining with <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>
<li><a class="reference internal" href="#challenges-when-accepting-all-typeforms">Challenges When Accepting All TypeForms</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="#alternative-names">Alternative names</a></li>
<li><a class="reference internal" href="#widen-type-c-to-support-all-type-expressions">Widen <code class="docutils literal notranslate"><span class="pre">type[C]</span></code> to support all type expressions</a></li>
<li><a class="reference internal" href="#accept-arbitrary-annotation-expressions">Accept arbitrary annotation expressions</a></li>
<li><a class="reference internal" href="#pattern-matching-on-type-forms">Pattern matching on type forms</a></li>
</ul>
</li>
<li><a class="reference internal" href="#footnotes">Footnotes</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-0747.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>