CINXE.COM
PEP 646 – Variadic Generics | peps.python.org
<!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 646 – Variadic Generics | peps.python.org</title> <link rel="shortcut icon" href="../_static/py.png"> <link rel="canonical" href="https://peps.python.org/pep-0646/"> <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 646 – Variadic Generics | peps.python.org'> <meta property="og:description" content="PEP 484 introduced TypeVar, enabling creation of generics parameterised with a single type. In this PEP, we introduce TypeVarTuple, enabling parameterisation with an arbitrary number of types - that is, a variadic type variable, enabling variadic generi..."> <meta property="og:type" content="website"> <meta property="og:url" content="https://peps.python.org/pep-0646/"> <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="PEP 484 introduced TypeVar, enabling creation of generics parameterised with a single type. In this PEP, we introduce TypeVarTuple, enabling parameterisation with an arbitrary number of types - that is, a variadic type variable, enabling variadic generi..."> <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 646</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 646 – Variadic Generics</h1> <dl class="rfc2822 field-list simple"> <dt class="field-odd">Author<span class="colon">:</span></dt> <dd class="field-odd">Mark Mendoza <mendoza.mark.a at gmail.com>, Matthew Rahtz <mrahtz at google.com>, Pradeep Kumar Srinivasan <gohanpra at gmail.com>, Vincent Siles <vsiles at fb.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">Status<span class="colon">:</span></dt> <dd class="field-odd"><abbr title="Accepted and implementation complete, or no longer active">Final</abbr></dd> <dt class="field-even">Type<span class="colon">:</span></dt> <dd class="field-even"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd> <dt class="field-odd">Topic<span class="colon">:</span></dt> <dd class="field-odd"><a class="reference external" href="../topic/typing/">Typing</a></dd> <dt class="field-even">Created<span class="colon">:</span></dt> <dd class="field-even">16-Sep-2020</dd> <dt class="field-odd">Python-Version<span class="colon">:</span></dt> <dd class="field-odd">3.11</dd> <dt class="field-even">Post-History<span class="colon">:</span></dt> <dd class="field-even">07-Oct-2020, 23-Dec-2020, 29-Dec-2020</dd> <dt class="field-odd">Resolution<span class="colon">:</span></dt> <dd class="field-odd"><a class="reference external" href="https://mail.python.org/archives/list/python-dev@python.org/message/OR5RKV7GAVSGLVH3JAGQ6OXFAXIP5XDX/">Python-Dev message</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="#acceptance">Acceptance</a></li> <li><a class="reference internal" href="#motivation">Motivation</a></li> <li><a class="reference internal" href="#summary-examples">Summary Examples</a></li> <li><a class="reference internal" href="#specification">Specification</a><ul> <li><a class="reference internal" href="#type-variable-tuples">Type Variable Tuples</a><ul> <li><a class="reference internal" href="#using-type-variable-tuples-in-generic-classes">Using Type Variable Tuples in Generic Classes</a></li> <li><a class="reference internal" href="#using-type-variable-tuples-in-functions">Using Type Variable Tuples in Functions</a></li> <li><a class="reference internal" href="#type-variable-tuples-must-always-be-unpacked">Type Variable Tuples Must Always be Unpacked</a></li> <li><a class="reference internal" href="#unpack-for-backwards-compatibility"><code class="docutils literal notranslate"><span class="pre">Unpack</span></code> for Backwards Compatibility</a></li> <li><a class="reference internal" href="#variance-type-constraints-and-type-bounds-not-yet-supported">Variance, Type Constraints and Type Bounds: Not (Yet) Supported</a></li> <li><a class="reference internal" href="#type-variable-tuple-equality">Type Variable Tuple Equality</a></li> <li><a class="reference internal" href="#multiple-type-variable-tuples-not-allowed">Multiple Type Variable Tuples: Not Allowed</a></li> </ul> </li> <li><a class="reference internal" href="#type-concatenation">Type Concatenation</a></li> <li><a class="reference internal" href="#unpacking-tuple-types">Unpacking Tuple Types</a><ul> <li><a class="reference internal" href="#unpacking-concrete-tuple-types">Unpacking Concrete Tuple Types</a></li> <li><a class="reference internal" href="#unpacking-unbounded-tuple-types">Unpacking Unbounded Tuple Types</a></li> <li><a class="reference internal" href="#multiple-unpackings-in-a-tuple-not-allowed">Multiple Unpackings in a Tuple: Not Allowed</a></li> </ul> </li> <li><a class="reference internal" href="#args-as-a-type-variable-tuple"><code class="docutils literal notranslate"><span class="pre">*args</span></code> as a Type Variable Tuple</a></li> <li><a class="reference internal" href="#type-variable-tuples-with-callable">Type Variable Tuples with <code class="docutils literal notranslate"><span class="pre">Callable</span></code></a></li> <li><a class="reference internal" href="#behaviour-when-type-parameters-are-not-specified">Behaviour when Type Parameters are not Specified</a></li> <li><a class="reference internal" href="#aliases">Aliases</a></li> <li><a class="reference internal" href="#substitution-in-aliases">Substitution in Aliases</a><ul> <li><a class="reference internal" href="#type-arguments-can-be-variadic">Type Arguments can be Variadic</a></li> <li><a class="reference internal" href="#variadic-arguments-require-variadic-aliases">Variadic Arguments Require Variadic Aliases</a></li> <li><a class="reference internal" href="#aliases-with-both-typevars-and-typevartuples">Aliases with Both TypeVars and TypeVarTuples</a></li> <li><a class="reference internal" href="#splitting-arbitrary-length-tuples">Splitting Arbitrary-Length Tuples</a></li> <li><a class="reference internal" href="#typevartuples-cannot-be-split">TypeVarTuples Cannot be Split</a></li> </ul> </li> <li><a class="reference internal" href="#overloads-for-accessing-individual-types">Overloads for Accessing Individual Types</a></li> </ul> </li> <li><a class="reference internal" href="#rationale-and-rejected-ideas">Rationale and Rejected Ideas</a><ul> <li><a class="reference internal" href="#shape-arithmetic">Shape Arithmetic</a></li> <li><a class="reference internal" href="#supporting-variadicity-through-aliases">Supporting Variadicity Through Aliases</a></li> <li><a class="reference internal" href="#construction-of-typevartuple">Construction of <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code></a></li> <li><a class="reference internal" href="#unspecified-type-parameters-tuple-vs-typevartuple">Unspecified Type Parameters: Tuple vs TypeVarTuple</a></li> </ul> </li> <li><a class="reference internal" href="#alternatives">Alternatives</a></li> <li><a class="reference internal" href="#grammar-changes">Grammar Changes</a><ul> <li><a class="reference internal" href="#change-1-star-expressions-in-indexes">Change 1: Star Expressions in Indexes</a><ul> <li><a class="reference internal" href="#typevartuple-implementation">TypeVarTuple Implementation</a></li> <li><a class="reference internal" href="#implications">Implications</a></li> </ul> </li> <li><a class="reference internal" href="#change-2-args-as-a-typevartuple">Change 2: <code class="docutils literal notranslate"><span class="pre">*args</span></code> as a TypeVarTuple</a><ul> <li><a class="reference internal" href="#id12">Implications</a></li> </ul> </li> <li><a class="reference internal" href="#alternatives-why-not-just-use-unpack">Alternatives (Why Not Just Use <code class="docutils literal notranslate"><span class="pre">Unpack</span></code>?)</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="#appendix-a-shape-typing-use-cases">Appendix A: Shape Typing Use Cases</a><ul> <li><a class="reference internal" href="#use-case-1-specifying-shape-values">Use Case 1: Specifying Shape Values</a></li> <li><a class="reference internal" href="#use-case-2-specifying-shape-semantics">Use Case 2: Specifying Shape Semantics</a></li> <li><a class="reference internal" href="#discussion">Discussion</a></li> <li><a class="reference internal" href="#why-not-both">Why Not Both?</a></li> </ul> </li> <li><a class="reference internal" href="#appendix-b-shaped-types-vs-named-axes">Appendix B: Shaped Types vs Named Axes</a></li> <li><a class="reference internal" href="#footnotes">Footnotes</a></li> <li><a class="reference internal" href="#endorsements">Endorsements</a></li> <li><a class="reference internal" href="#acknowledgements">Acknowledgements</a></li> <li><a class="reference internal" href="#resources">Resources</a></li> <li><a class="reference internal" href="#references">References</a></li> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> </details></section> <div class="pep-banner canonical-doc sticky-banner admonition important"> <p class="admonition-title">Important</p> <p>This PEP is a historical document. The up-to-date, canonical documentation can now be found at <a class="reference external" href="https://typing.python.org/en/latest/spec/generics.html#typevartuple" title="(in typing)"><span>TypeVarTuple</span></a> and <a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.TypeVarTuple" title="(in Python v3.13)"><code class="xref py py-class docutils literal notranslate"><span class="pre">typing.TypeVarTuple</span></code></a>.</p> <p class="close-button">×</p> <p>See <a class="pep reference internal" href="../pep-0001/" title="PEP 1 – PEP Purpose and Guidelines">PEP 1</a> for how to propose changes.</p> </div> <section id="abstract"> <h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2> <p><a class="pep reference internal" href="../pep-0484/" title="PEP 484 – Type Hints">PEP 484</a> introduced <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code>, enabling creation of generics parameterised with a single type. In this PEP, we introduce <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code>, enabling parameterisation with an <em>arbitrary</em> number of types - that is, a <em>variadic</em> type variable, enabling <em>variadic</em> generics. This enables a wide variety of use cases. In particular, it allows the type of array-like structures in numerical computing libraries such as NumPy and TensorFlow to be parameterised with the array <em>shape</em>, enabling static type checkers to catch shape-related bugs in code that uses these libraries.</p> </section> <section id="acceptance"> <h2><a class="toc-backref" href="#acceptance" role="doc-backlink">Acceptance</a></h2> <p>This PEP was accepted for Python 3.11, with the caveat that details around multiple unpackings in a type expression aren’t specified precisely. This gives individual type checkers some leeway, but can be tightened in future PEPs.</p> </section> <section id="motivation"> <h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2> <p>Variadic generics have long been a requested feature, for a myriad of use cases <a class="footnote-reference brackets" href="#typing193" id="id1">[4]</a>. One particular use case - a use case with potentially large impact, and the main case this PEP targets - concerns typing in numerical libraries.</p> <p>In the context of numerical computation with libraries such as NumPy and TensorFlow, the <em>shape</em> of variables is often just as important as the variable <em>type</em>. For example, consider the following function which converts a batch <a class="footnote-reference brackets" href="#batch" id="id2">[1]</a> of videos to grayscale:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">to_gray</span><span class="p">(</span><span class="n">videos</span><span class="p">:</span> <span class="n">Array</span><span class="p">):</span> <span class="o">...</span> </pre></div> </div> <p>From the signature alone, it is not obvious what shape of array <a class="footnote-reference brackets" href="#array" id="id3">[2]</a> we should pass for the <code class="docutils literal notranslate"><span class="pre">videos</span></code> argument. Possibilities include, for example,</p> <blockquote> <div>batch × time × height × width × channels</div></blockquote> <p>and</p> <blockquote> <div>time × batch × channels × height × width. <a class="footnote-reference brackets" href="#timebatch" id="id4">[3]</a></div></blockquote> <p>This is important for three reasons:</p> <ul class="simple"> <li><strong>Documentation</strong>. Without the required shape being clear in the signature, the user must hunt in the docstring or the code in question to determine what the input/output shape requirements are.</li> <li><strong>Catching shape bugs before runtime</strong>. Ideally, use of incorrect shapes should be an error we can catch ahead of time using static analysis. (This is particularly important for machine learning code, where iteration times can be slow.)</li> <li><strong>Preventing subtle shape bugs</strong>. In the worst case, use of the wrong shape will result in the program appearing to run fine, but with a subtle bug that can take days to track down. (See <a class="reference external" href="https://spinningup.openai.com/en/latest/spinningup/exercise2_2_soln.html">this exercise</a> in a popular machine learning tutorial for a particularly pernicious example.)</li> </ul> <p>Ideally, we should have some way of making shape requirements explicit in type signatures. Multiple proposals <a class="footnote-reference brackets" href="#numeric-stack" id="id5">[6]</a> <a class="footnote-reference brackets" href="#typing-ideas" id="id6">[7]</a> <a class="footnote-reference brackets" href="#syntax-proposal" id="id7">[9]</a> have suggested the use of the standard generics syntax for this purpose. We would write:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">to_gray</span><span class="p">(</span><span class="n">videos</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Time</span><span class="p">,</span> <span class="n">Batch</span><span class="p">,</span> <span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">,</span> <span class="n">Channels</span><span class="p">]):</span> <span class="o">...</span> </pre></div> </div> <p>However, note that arrays can be of arbitrary rank - <code class="docutils literal notranslate"><span class="pre">Array</span></code> as used above is generic in an arbitrary number of axes. One way around this would be to use a different <code class="docutils literal notranslate"><span class="pre">Array</span></code> class for each rank…</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Axis1</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'Axis1'</span><span class="p">)</span> <span class="n">Axis2</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'Axis2'</span><span class="p">)</span> <span class="k">class</span><span class="w"> </span><span class="nc">Array1</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">Axis1</span><span class="p">]):</span> <span class="o">...</span> <span class="k">class</span><span class="w"> </span><span class="nc">Array2</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">Axis1</span><span class="p">,</span> <span class="n">Axis2</span><span class="p">]):</span> <span class="o">...</span> </pre></div> </div> <p>…but this would be cumbersome, both for users (who would have to sprinkle 1s and 2s and so on throughout their code) and for the authors of array libraries (who would have to duplicate implementations throughout multiple classes).</p> <p>Variadic generics are necessary for an <code class="docutils literal notranslate"><span class="pre">Array</span></code> that is generic in an arbitrary number of axes to be cleanly defined as a single class.</p> </section> <section id="summary-examples"> <h2><a class="toc-backref" href="#summary-examples" role="doc-backlink">Summary Examples</a></h2> <p>Cutting right to the chase, this PEP allows an <code class="docutils literal notranslate"><span class="pre">Array</span></code> class that is generic in its shape (and datatype) to be defined using a newly-introduced arbitrary-length type variable, <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code>, as follows:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">TypeVarTuple</span> <span class="n">DType</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'DType'</span><span class="p">)</span> <span class="n">Shape</span> <span class="o">=</span> <span class="n">TypeVarTuple</span><span class="p">(</span><span class="s1">'Shape'</span><span class="p">)</span> <span class="k">class</span><span class="w"> </span><span class="nc">Array</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">DType</span><span class="p">,</span> <span class="o">*</span><span class="n">Shape</span><span class="p">]):</span> <span class="k">def</span><span class="w"> </span><span class="fm">__abs__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="n">Array</span><span class="p">[</span><span class="n">DType</span><span class="p">,</span> <span class="o">*</span><span class="n">Shape</span><span class="p">]:</span> <span class="o">...</span> <span class="k">def</span><span class="w"> </span><span class="fm">__add__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">DType</span><span class="p">,</span> <span class="o">*</span><span class="n">Shape</span><span class="p">])</span> <span class="o">-></span> <span class="n">Array</span><span class="p">[</span><span class="n">DType</span><span class="p">,</span> <span class="o">*</span><span class="n">Shape</span><span class="p">]:</span> <span class="o">...</span> </pre></div> </div> <p>Such an <code class="docutils literal notranslate"><span class="pre">Array</span></code> can be used to support a number of different kinds of shape annotations. For example, we can add labels describing the semantic meaning of each axis:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">NewType</span> <span class="n">Height</span> <span class="o">=</span> <span class="n">NewType</span><span class="p">(</span><span class="s1">'Height'</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span> <span class="n">Width</span> <span class="o">=</span> <span class="n">NewType</span><span class="p">(</span><span class="s1">'Width'</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span> <span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">]</span> <span class="o">=</span> <span class="n">Array</span><span class="p">()</span> </pre></div> </div> <p>We could also add annotations describing the actual size of each axis:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">Literal</span> <span class="k">as</span> <span class="n">L</span> <span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="n">L</span><span class="p">[</span><span class="mi">480</span><span class="p">],</span> <span class="n">L</span><span class="p">[</span><span class="mi">640</span><span class="p">]]</span> <span class="o">=</span> <span class="n">Array</span><span class="p">()</span> </pre></div> </div> <p>For consistency, we use semantic axis annotations as the basis of the examples in this PEP, but this PEP is agnostic about which of these two (or possibly other) ways of using <code class="docutils literal notranslate"><span class="pre">Array</span></code> is preferable; that decision is left to library authors.</p> <p>(Note also that for the rest of this PEP, for conciseness of example, we use a simpler version of <code class="docutils literal notranslate"><span class="pre">Array</span></code> which is generic only in the shape - <em>not</em> the data type.)</p> </section> <section id="specification"> <h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2> <p>In order to support the above use cases, we introduce <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code>. This serves as a placeholder not for a single type but for a <em>tuple</em> of types.</p> <p>In addition, we introduce a new use for the star operator: to ‘unpack’ <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> instances and tuple types such as <code class="docutils literal notranslate"><span class="pre">Tuple[int,</span> <span class="pre">str]</span></code>. Unpacking a <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> or tuple type is the typing equivalent of unpacking a variable or a tuple of values.</p> <section id="type-variable-tuples"> <h3><a class="toc-backref" href="#type-variable-tuples" role="doc-backlink">Type Variable Tuples</a></h3> <p>In the same way that a normal type variable is a stand-in for a single type such as <code class="docutils literal notranslate"><span class="pre">int</span></code>, a type variable <em>tuple</em> is a stand-in for a <em>tuple</em> type such as <code class="docutils literal notranslate"><span class="pre">Tuple[int,</span> <span class="pre">str]</span></code>.</p> <p>Type variable tuples are created with:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">TypeVarTuple</span> <span class="n">Ts</span> <span class="o">=</span> <span class="n">TypeVarTuple</span><span class="p">(</span><span class="s1">'Ts'</span><span class="p">)</span> </pre></div> </div> <section id="using-type-variable-tuples-in-generic-classes"> <h4><a class="toc-backref" href="#using-type-variable-tuples-in-generic-classes" role="doc-backlink">Using Type Variable Tuples in Generic Classes</a></h4> <p>Type variable tuples behave like a number of individual type variables packed in a <code class="docutils literal notranslate"><span class="pre">Tuple</span></code>. To understand this, consider the following example:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Shape</span> <span class="o">=</span> <span class="n">TypeVarTuple</span><span class="p">(</span><span class="s1">'Shape'</span><span class="p">)</span> <span class="k">class</span><span class="w"> </span><span class="nc">Array</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="o">*</span><span class="n">Shape</span><span class="p">]):</span> <span class="o">...</span> <span class="n">Height</span> <span class="o">=</span> <span class="n">NewType</span><span class="p">(</span><span class="s1">'Height'</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span> <span class="n">Width</span> <span class="o">=</span> <span class="n">NewType</span><span class="p">(</span><span class="s1">'Width'</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span> <span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">]</span> <span class="o">=</span> <span class="n">Array</span><span class="p">()</span> </pre></div> </div> <p>The <code class="docutils literal notranslate"><span class="pre">Shape</span></code> type variable tuple here behaves like <code class="docutils literal notranslate"><span class="pre">Tuple[T1,</span> <span class="pre">T2]</span></code>, where <code class="docutils literal notranslate"><span class="pre">T1</span></code> and <code class="docutils literal notranslate"><span class="pre">T2</span></code> are type variables. To use these type variables as type parameters of <code class="docutils literal notranslate"><span class="pre">Array</span></code>, we must <em>unpack</em> the type variable tuple using the star operator: <code class="docutils literal notranslate"><span class="pre">*Shape</span></code>. The signature of <code class="docutils literal notranslate"><span class="pre">Array</span></code> then behaves as if we had simply written <code class="docutils literal notranslate"><span class="pre">class</span> <span class="pre">Array(Generic[T1,</span> <span class="pre">T2]):</span> <span class="pre">...</span></code>.</p> <p>In contrast to <code class="docutils literal notranslate"><span class="pre">Generic[T1,</span> <span class="pre">T2]</span></code>, however, <code class="docutils literal notranslate"><span class="pre">Generic[*Shape]</span></code> allows us to parameterise the class with an <em>arbitrary</em> number of type parameters. That is, in addition to being able to define rank-2 arrays such as <code class="docutils literal notranslate"><span class="pre">Array[Height,</span> <span class="pre">Width]</span></code>, we could also define rank-3 arrays, rank-4 arrays, and so on:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Time</span> <span class="o">=</span> <span class="n">NewType</span><span class="p">(</span><span class="s1">'Time'</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span> <span class="n">Batch</span> <span class="o">=</span> <span class="n">NewType</span><span class="p">(</span><span class="s1">'Batch'</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span> <span class="n">y</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Batch</span><span class="p">,</span> <span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">]</span> <span class="o">=</span> <span class="n">Array</span><span class="p">()</span> <span class="n">z</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Time</span><span class="p">,</span> <span class="n">Batch</span><span class="p">,</span> <span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">]</span> <span class="o">=</span> <span class="n">Array</span><span class="p">()</span> </pre></div> </div> </section> <section id="using-type-variable-tuples-in-functions"> <h4><a class="toc-backref" href="#using-type-variable-tuples-in-functions" role="doc-backlink">Using Type Variable Tuples in Functions</a></h4> <p>Type variable tuples can be used anywhere a normal <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code> can. This includes class definitions, as shown above, as well as function signatures and variable annotations:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Array</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="o">*</span><span class="n">Shape</span><span class="p">]):</span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">shape</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">[</span><span class="o">*</span><span class="n">Shape</span><span class="p">]):</span> <span class="bp">self</span><span class="o">.</span><span class="n">_shape</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">[</span><span class="o">*</span><span class="n">Shape</span><span class="p">]</span> <span class="o">=</span> <span class="n">shape</span> <span class="k">def</span><span class="w"> </span><span class="nf">get_shape</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="n">Tuple</span><span class="p">[</span><span class="o">*</span><span class="n">Shape</span><span class="p">]:</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_shape</span> <span class="n">shape</span> <span class="o">=</span> <span class="p">(</span><span class="n">Height</span><span class="p">(</span><span class="mi">480</span><span class="p">),</span> <span class="n">Width</span><span class="p">(</span><span class="mi">640</span><span class="p">))</span> <span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">]</span> <span class="o">=</span> <span class="n">Array</span><span class="p">(</span><span class="n">shape</span><span class="p">)</span> <span class="n">y</span> <span class="o">=</span> <span class="nb">abs</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="c1"># Inferred type is Array[Height, Width]</span> <span class="n">z</span> <span class="o">=</span> <span class="n">x</span> <span class="o">+</span> <span class="n">x</span> <span class="c1"># ... is Array[Height, Width]</span> </pre></div> </div> </section> <section id="type-variable-tuples-must-always-be-unpacked"> <h4><a class="toc-backref" href="#type-variable-tuples-must-always-be-unpacked" role="doc-backlink">Type Variable Tuples Must Always be Unpacked</a></h4> <p>Note that in the previous example, the <code class="docutils literal notranslate"><span class="pre">shape</span></code> argument to <code class="docutils literal notranslate"><span class="pre">__init__</span></code> was annotated as <code class="docutils literal notranslate"><span class="pre">Tuple[*Shape]</span></code>. Why is this necessary - if <code class="docutils literal notranslate"><span class="pre">Shape</span></code> behaves like <code class="docutils literal notranslate"><span class="pre">Tuple[T1,</span> <span class="pre">T2,</span> <span class="pre">...]</span></code>, couldn’t we have annotated the <code class="docutils literal notranslate"><span class="pre">shape</span></code> argument as <code class="docutils literal notranslate"><span class="pre">Shape</span></code> directly?</p> <p>This is, in fact, deliberately not possible: type variable tuples must <em>always</em> be used unpacked (that is, prefixed by the star operator). This is for two reasons:</p> <ul class="simple"> <li>To avoid potential confusion about whether to use a type variable tuple in a packed or unpacked form (“Hmm, should I write ‘<code class="docutils literal notranslate"><span class="pre">-></span> <span class="pre">Shape</span></code>’, or ‘<code class="docutils literal notranslate"><span class="pre">-></span> <span class="pre">Tuple[Shape]</span></code>’, or ‘<code class="docutils literal notranslate"><span class="pre">-></span> <span class="pre">Tuple[*Shape]</span></code>’…?”)</li> <li>To improve readability: the star also functions as an explicit visual indicator that the type variable tuple is not a normal type variable.</li> </ul> </section> <section id="unpack-for-backwards-compatibility"> <h4><a class="toc-backref" href="#unpack-for-backwards-compatibility" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">Unpack</span></code> for Backwards Compatibility</a></h4> <p>Note that the use of the star operator in this context requires a grammar change, and is therefore available only in new versions of Python. To enable use of type variable tuples in older versions of Python, we introduce the <code class="docutils literal notranslate"><span class="pre">Unpack</span></code> type operator that can be used in place of the star operator:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Unpacking using the star operator in new versions of Python</span> <span class="k">class</span><span class="w"> </span><span class="nc">Array</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="o">*</span><span class="n">Shape</span><span class="p">]):</span> <span class="o">...</span> <span class="c1"># Unpacking using ``Unpack`` in older versions of Python</span> <span class="k">class</span><span class="w"> </span><span class="nc">Array</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">Unpack</span><span class="p">[</span><span class="n">Shape</span><span class="p">]]):</span> <span class="o">...</span> </pre></div> </div> </section> <section id="variance-type-constraints-and-type-bounds-not-yet-supported"> <h4><a class="toc-backref" href="#variance-type-constraints-and-type-bounds-not-yet-supported" role="doc-backlink">Variance, Type Constraints and Type Bounds: Not (Yet) Supported</a></h4> <p>To keep this PEP minimal, <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> does not yet support specification of:</p> <ul class="simple"> <li>Variance (e.g. <code class="docutils literal notranslate"><span class="pre">TypeVar('T',</span> <span class="pre">covariant=True)</span></code>)</li> <li>Type constraints (<code class="docutils literal notranslate"><span class="pre">TypeVar('T',</span> <span class="pre">int,</span> <span class="pre">float)</span></code>)</li> <li>Type bounds (<code class="docutils literal notranslate"><span class="pre">TypeVar('T',</span> <span class="pre">bound=ParentClass)</span></code>)</li> </ul> <p>We leave the decision of how these arguments should behave to a future PEP, when variadic generics have been tested in the field. As of this PEP, type variable tuples are invariant.</p> </section> <section id="type-variable-tuple-equality"> <h4><a class="toc-backref" href="#type-variable-tuple-equality" role="doc-backlink">Type Variable Tuple Equality</a></h4> <p>If the same <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> instance is used in multiple places in a signature or class, a valid type inference might be to bind the <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> to a <code class="docutils literal notranslate"><span class="pre">Tuple</span></code> of a <code class="docutils literal notranslate"><span class="pre">Union</span></code> of types:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">(</span><span class="n">arg1</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">[</span><span class="o">*</span><span class="n">Ts</span><span class="p">],</span> <span class="n">arg2</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">[</span><span class="o">*</span><span class="n">Ts</span><span class="p">]):</span> <span class="o">...</span> <span class="n">a</span> <span class="o">=</span> <span class="p">(</span><span class="mi">0</span><span class="p">,)</span> <span class="n">b</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'0'</span><span class="p">,)</span> <span class="n">foo</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span> <span class="c1"># Can Ts be bound to Tuple[int | str]?</span> </pre></div> </div> <p>We do <em>not</em> allow this; type unions may <em>not</em> appear within the <code class="docutils literal notranslate"><span class="pre">Tuple</span></code>. If a type variable tuple appears in multiple places in a signature, the types must match exactly (the list of type parameters must be the same length, and the type parameters themselves must be identical):</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">pointwise_multiply</span><span class="p">(</span> <span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="o">*</span><span class="n">Shape</span><span class="p">],</span> <span class="n">y</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="o">*</span><span class="n">Shape</span><span class="p">]</span> <span class="p">)</span> <span class="o">-></span> <span class="n">Array</span><span class="p">[</span><span class="o">*</span><span class="n">Shape</span><span class="p">]:</span> <span class="o">...</span> <span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Height</span><span class="p">]</span> <span class="n">y</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Width</span><span class="p">]</span> <span class="n">z</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">]</span> <span class="n">pointwise_multiply</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span> <span class="c1"># Valid</span> <span class="n">pointwise_multiply</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="c1"># Error</span> <span class="n">pointwise_multiply</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">z</span><span class="p">)</span> <span class="c1"># Error</span> </pre></div> </div> </section> <section id="multiple-type-variable-tuples-not-allowed"> <h4><a class="toc-backref" href="#multiple-type-variable-tuples-not-allowed" role="doc-backlink">Multiple Type Variable Tuples: Not Allowed</a></h4> <p>As of this PEP, only a single type variable tuple may appear in a type parameter list:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Array</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="o">*</span><span class="n">Ts1</span><span class="p">,</span> <span class="o">*</span><span class="n">Ts2</span><span class="p">]):</span> <span class="o">...</span> <span class="c1"># Error</span> </pre></div> </div> <p>The reason is that multiple type variable tuples make it ambiguous which parameters get bound to which type variable tuple:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">,</span> <span class="nb">bool</span><span class="p">]</span> <span class="c1"># Ts1 = ???, Ts2 = ???</span> </pre></div> </div> </section> </section> <section id="type-concatenation"> <h3><a class="toc-backref" href="#type-concatenation" role="doc-backlink">Type Concatenation</a></h3> <p>Type variable tuples don’t have to be alone; normal types can be prefixed and/or suffixed:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Shape</span> <span class="o">=</span> <span class="n">TypeVarTuple</span><span class="p">(</span><span class="s1">'Shape'</span><span class="p">)</span> <span class="n">Batch</span> <span class="o">=</span> <span class="n">NewType</span><span class="p">(</span><span class="s1">'Batch'</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span> <span class="n">Channels</span> <span class="o">=</span> <span class="n">NewType</span><span class="p">(</span><span class="s1">'Channels'</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">add_batch_axis</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="o">*</span><span class="n">Shape</span><span class="p">])</span> <span class="o">-></span> <span class="n">Array</span><span class="p">[</span><span class="n">Batch</span><span class="p">,</span> <span class="o">*</span><span class="n">Shape</span><span class="p">]:</span> <span class="o">...</span> <span class="k">def</span><span class="w"> </span><span class="nf">del_batch_axis</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Batch</span><span class="p">,</span> <span class="o">*</span><span class="n">Shape</span><span class="p">])</span> <span class="o">-></span> <span class="n">Array</span><span class="p">[</span><span class="o">*</span><span class="n">Shape</span><span class="p">]:</span> <span class="o">...</span> <span class="k">def</span><span class="w"> </span><span class="nf">add_batch_channels</span><span class="p">(</span> <span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="o">*</span><span class="n">Shape</span><span class="p">]</span> <span class="p">)</span> <span class="o">-></span> <span class="n">Array</span><span class="p">[</span><span class="n">Batch</span><span class="p">,</span> <span class="o">*</span><span class="n">Shape</span><span class="p">,</span> <span class="n">Channels</span><span class="p">]:</span> <span class="o">...</span> <span class="n">a</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">]</span> <span class="n">b</span> <span class="o">=</span> <span class="n">add_batch_axis</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="c1"># Inferred type is Array[Batch, Height, Width]</span> <span class="n">c</span> <span class="o">=</span> <span class="n">del_batch_axis</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="c1"># Array[Height, Width]</span> <span class="n">d</span> <span class="o">=</span> <span class="n">add_batch_channels</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="c1"># Array[Batch, Height, Width, Channels]</span> </pre></div> </div> <p>Normal <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code> instances can also be prefixed and/or suffixed:</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="s1">'T'</span><span class="p">)</span> <span class="n">Ts</span> <span class="o">=</span> <span class="n">TypeVarTuple</span><span class="p">(</span><span class="s1">'Ts'</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">prefix_tuple</span><span class="p">(</span> <span class="n">x</span><span class="p">:</span> <span class="n">T</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">[</span><span class="o">*</span><span class="n">Ts</span><span class="p">]</span> <span class="p">)</span> <span class="o">-></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="n">Ts</span><span class="p">]:</span> <span class="o">...</span> <span class="n">z</span> <span class="o">=</span> <span class="n">prefix_tuple</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="p">(</span><span class="kc">True</span><span class="p">,</span> <span class="s1">'a'</span><span class="p">))</span> <span class="c1"># Inferred type of z is Tuple[int, bool, str]</span> </pre></div> </div> </section> <section id="unpacking-tuple-types"> <h3><a class="toc-backref" href="#unpacking-tuple-types" role="doc-backlink">Unpacking Tuple Types</a></h3> <p>We mentioned that a <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> stands for a tuple of types. Since we can unpack a <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code>, for consistency, we also allow unpacking a tuple type. As we shall see, this also enables a number of interesting features.</p> <section id="unpacking-concrete-tuple-types"> <h4><a class="toc-backref" href="#unpacking-concrete-tuple-types" role="doc-backlink">Unpacking Concrete Tuple Types</a></h4> <p>Unpacking a concrete tuple type is analogous to unpacking a tuple of values at runtime. <code class="docutils literal notranslate"><span class="pre">Tuple[int,</span> <span class="pre">*Tuple[bool,</span> <span class="pre">bool],</span> <span class="pre">str]</span></code> is equivalent to <code class="docutils literal notranslate"><span class="pre">Tuple[int,</span> <span class="pre">bool,</span> <span class="pre">bool,</span> <span class="pre">str]</span></code>.</p> </section> <section id="unpacking-unbounded-tuple-types"> <h4><a class="toc-backref" href="#unpacking-unbounded-tuple-types" role="doc-backlink">Unpacking Unbounded Tuple Types</a></h4> <p>Unpacking an unbounded tuple preserves the unbounded tuple as it is. That is, <code class="docutils literal notranslate"><span class="pre">*Tuple[int,</span> <span class="pre">...]</span></code> remains <code class="docutils literal notranslate"><span class="pre">*Tuple[int,</span> <span class="pre">...]</span></code>; there’s no simpler form. This enables us to specify types such as <code class="docutils literal notranslate"><span class="pre">Tuple[int,</span> <span class="pre">*Tuple[str,</span> <span class="pre">...],</span> <span class="pre">str]</span></code> - a tuple type where the first element is guaranteed to be of type <code class="docutils literal notranslate"><span class="pre">int</span></code>, the last element is guaranteed to be of type <code class="docutils literal notranslate"><span class="pre">str</span></code>, and the elements in the middle are zero or more elements of type <code class="docutils literal notranslate"><span class="pre">str</span></code>. Note that <code class="docutils literal notranslate"><span class="pre">Tuple[*Tuple[int,</span> <span class="pre">...]]</span></code> is equivalent to <code class="docutils literal notranslate"><span class="pre">Tuple[int,</span> <span class="pre">...]</span></code>.</p> <p>Unpacking unbounded tuples is also useful in function signatures where we don’t care about the exact elements and don’t want to define an unnecessary <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code>:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">process_batch_channels</span><span class="p">(</span> <span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Batch</span><span class="p">,</span> <span class="o">*</span><span class="n">Tuple</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="o">...</span><span class="p">],</span> <span class="n">Channels</span><span class="p">]</span> <span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span> <span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Batch</span><span class="p">,</span> <span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">,</span> <span class="n">Channels</span><span class="p">]</span> <span class="n">process_batch_channels</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="c1"># OK</span> <span class="n">y</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Batch</span><span class="p">,</span> <span class="n">Channels</span><span class="p">]</span> <span class="n">process_batch_channels</span><span class="p">(</span><span class="n">y</span><span class="p">)</span> <span class="c1"># OK</span> <span class="n">z</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Batch</span><span class="p">]</span> <span class="n">process_batch_channels</span><span class="p">(</span><span class="n">z</span><span class="p">)</span> <span class="c1"># Error: Expected Channels.</span> </pre></div> </div> <p>We can also pass a <code class="docutils literal notranslate"><span class="pre">*Tuple[int,</span> <span class="pre">...]</span></code> wherever a <code class="docutils literal notranslate"><span class="pre">*Ts</span></code> is expected. This is useful when we have particularly dynamic code and cannot state the precise number of dimensions or the precise types for each of the dimensions. In those cases, we can smoothly fall back to an unbounded tuple:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">y</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="o">*</span><span class="n">Tuple</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="o">...</span><span class="p">]]</span> <span class="o">=</span> <span class="n">read_from_file</span><span class="p">()</span> <span class="k">def</span><span class="w"> </span><span class="nf">expect_variadic_array</span><span class="p">(</span> <span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Batch</span><span class="p">,</span> <span class="o">*</span><span class="n">Shape</span><span class="p">]</span> <span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span> <span class="n">expect_variadic_array</span><span class="p">(</span><span class="n">y</span><span class="p">)</span> <span class="c1"># OK</span> <span class="k">def</span><span class="w"> </span><span class="nf">expect_precise_array</span><span class="p">(</span> <span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Batch</span><span class="p">,</span> <span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">,</span> <span class="n">Channels</span><span class="p">]</span> <span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span> <span class="n">expect_precise_array</span><span class="p">(</span><span class="n">y</span><span class="p">)</span> <span class="c1"># OK</span> </pre></div> </div> <p><code class="docutils literal notranslate"><span class="pre">Array[*Tuple[Any,</span> <span class="pre">...]]</span></code> stands for an array with an arbitrary number of dimensions of type <code class="docutils literal notranslate"><span class="pre">Any</span></code>. This means that, in the call to <code class="docutils literal notranslate"><span class="pre">expect_variadic_array</span></code>, <code class="docutils literal notranslate"><span class="pre">Batch</span></code> is bound to <code class="docutils literal notranslate"><span class="pre">Any</span></code> and <code class="docutils literal notranslate"><span class="pre">Shape</span></code> is bound to <code class="docutils literal notranslate"><span class="pre">Tuple[Any,</span> <span class="pre">...]</span></code>. In the call to <code class="docutils literal notranslate"><span class="pre">expect_precise_array</span></code>, the variables <code class="docutils literal notranslate"><span class="pre">Batch</span></code>, <code class="docutils literal notranslate"><span class="pre">Height</span></code>, <code class="docutils literal notranslate"><span class="pre">Width</span></code>, and <code class="docutils literal notranslate"><span class="pre">Channels</span></code> are all bound to <code class="docutils literal notranslate"><span class="pre">Any</span></code>.</p> <p>This allows users to handle dynamic code gracefully while still explicitly marking the code as unsafe (by using <code class="docutils literal notranslate"><span class="pre">y:</span> <span class="pre">Array[*Tuple[Any,</span> <span class="pre">...]]</span></code>). Otherwise, users would face noisy errors from the type checker every time they tried to use the variable <code class="docutils literal notranslate"><span class="pre">y</span></code>, which would hinder them when migrating a legacy code base to use <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code>.</p> </section> <section id="multiple-unpackings-in-a-tuple-not-allowed"> <h4><a class="toc-backref" href="#multiple-unpackings-in-a-tuple-not-allowed" role="doc-backlink">Multiple Unpackings in a Tuple: Not Allowed</a></h4> <p>As with <code class="docutils literal notranslate"><span class="pre">TypeVarTuples</span></code>, <a class="reference internal" href="#multiple-type-variable-tuples-not-allowed">only one</a> unpacking may appear in a tuple:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">x</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="o">*</span><span class="n">Ts</span><span class="p">,</span> <span class="nb">str</span><span class="p">,</span> <span class="o">*</span><span class="n">Ts2</span><span class="p">]</span> <span class="c1"># Error</span> <span class="n">y</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="o">*</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="o">...</span><span class="p">],</span> <span class="nb">str</span><span class="p">,</span> <span class="o">*</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="c1"># Error</span> </pre></div> </div> </section> </section> <section id="args-as-a-type-variable-tuple"> <h3><a class="toc-backref" href="#args-as-a-type-variable-tuple" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">*args</span></code> as a Type Variable Tuple</a></h3> <p><a class="pep reference internal" href="../pep-0484/" title="PEP 484 – Type Hints">PEP 484</a> states that when a type annotation is provided for <code class="docutils literal notranslate"><span class="pre">*args</span></code>, every argument must be of the type annotated. That is, if we specify <code class="docutils literal notranslate"><span class="pre">*args</span></code> to be type <code class="docutils literal notranslate"><span class="pre">int</span></code>, then <em>all</em> arguments must be of type <code class="docutils literal notranslate"><span class="pre">int</span></code>. This limits our ability to specify the type signatures of functions that take heterogeneous argument types.</p> <p>If <code class="docutils literal notranslate"><span class="pre">*args</span></code> is annotated as a type variable tuple, however, the types of the individual arguments become the types in the type variable tuple:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Ts</span> <span class="o">=</span> <span class="n">TypeVarTuple</span><span class="p">(</span><span class="s1">'Ts'</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">args_to_tuple</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="o">*</span><span class="n">Ts</span><span class="p">)</span> <span class="o">-></span> <span class="n">Tuple</span><span class="p">[</span><span class="o">*</span><span class="n">Ts</span><span class="p">]:</span> <span class="o">...</span> <span class="n">args_to_tuple</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'a'</span><span class="p">)</span> <span class="c1"># Inferred type is Tuple[int, str]</span> </pre></div> </div> <p>In the above example, <code class="docutils literal notranslate"><span class="pre">Ts</span></code> is bound to <code class="docutils literal notranslate"><span class="pre">Tuple[int,</span> <span class="pre">str]</span></code>. If no arguments are passed, the type variable tuple behaves like an empty tuple, <code class="docutils literal notranslate"><span class="pre">Tuple[()]</span></code>.</p> <p>As usual, we can unpack any tuple types. For example, by using a type variable tuple inside a tuple of other types, we can refer to prefixes or suffixes of the variadic argument list. For example:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># os.execle takes arguments 'path, arg0, arg1, ..., env'</span> <span class="k">def</span><span class="w"> </span><span class="nf">execle</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="o">*</span><span class="n">Tuple</span><span class="p">[</span><span class="o">*</span><span class="n">Ts</span><span class="p">,</span> <span class="n">Env</span><span class="p">])</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span> </pre></div> </div> <p>Note that this is different to</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">execle</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="o">*</span><span class="n">Ts</span><span class="p">,</span> <span class="n">env</span><span class="p">:</span> <span class="n">Env</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span> </pre></div> </div> <p>as this would make <code class="docutils literal notranslate"><span class="pre">env</span></code> a keyword-only argument.</p> <p>Using an unpacked unbounded tuple is equivalent to the <a class="pep reference internal" href="../pep-0484/#arbitrary-argument-lists-and-default-argument-values" title="PEP 484 – Type Hints § Arbitrary argument lists and default argument values">PEP 484</a> behavior of <code class="docutils literal notranslate"><span class="pre">*args:</span> <span class="pre">int</span></code>, which accepts zero or more values of type <code class="docutils literal notranslate"><span class="pre">int</span></code>:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="o">*</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="o">...</span><span class="p">])</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span> <span class="c1"># equivalent to:</span> <span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span> </pre></div> </div> <p>Unpacking tuple types also allows more precise types for heterogeneous <code class="docutils literal notranslate"><span class="pre">*args</span></code>. The following function expects an <code class="docutils literal notranslate"><span class="pre">int</span></code> at the beginning, zero or more <code class="docutils literal notranslate"><span class="pre">str</span></code> values, and a <code class="docutils literal notranslate"><span class="pre">str</span></code> at the end:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="o">*</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="o">*</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="nb">str</span><span class="p">])</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span> </pre></div> </div> <p>For completeness, we mention that unpacking a concrete tuple allows us to specify <code class="docutils literal notranslate"><span class="pre">*args</span></code> of a fixed number of heterogeneous types:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="o">*</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">])</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span> <span class="n">foo</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s2">"hello"</span><span class="p">)</span> <span class="c1"># OK</span> </pre></div> </div> <p>Note that, in keeping with the rule that type variable tuples must always be used unpacked, annotating <code class="docutils literal notranslate"><span class="pre">*args</span></code> as being a plain type variable tuple instance is <em>not</em> allowed:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Ts</span><span class="p">):</span> <span class="o">...</span> <span class="c1"># NOT valid</span> </pre></div> </div> <p><code class="docutils literal notranslate"><span class="pre">*args</span></code> is the only case where an argument can be annotated as <code class="docutils literal notranslate"><span class="pre">*Ts</span></code> directly; other arguments should use <code class="docutils literal notranslate"><span class="pre">*Ts</span></code> to parameterise something else, e.g. <code class="docutils literal notranslate"><span class="pre">Tuple[*Ts]</span></code>. If <code class="docutils literal notranslate"><span class="pre">*args</span></code> itself is annotated as <code class="docutils literal notranslate"><span class="pre">Tuple[*Ts]</span></code>, the old behaviour still applies: all arguments must be a <code class="docutils literal notranslate"><span class="pre">Tuple</span></code> parameterised with the same types.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">[</span><span class="o">*</span><span class="n">Ts</span><span class="p">]):</span> <span class="o">...</span> <span class="n">foo</span><span class="p">((</span><span class="mi">0</span><span class="p">,),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,))</span> <span class="c1"># Valid</span> <span class="n">foo</span><span class="p">((</span><span class="mi">0</span><span class="p">,),</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</span> <span class="n">foo</span><span class="p">((</span><span class="mi">0</span><span class="p">,),</span> <span class="p">(</span><span class="s1">'1'</span><span class="p">,))</span> <span class="c1"># Error</span> </pre></div> </div> <p>Finally, note that a type variable tuple may <em>not</em> be used as the type of <code class="docutils literal notranslate"><span class="pre">**kwargs</span></code>. (We do not yet know of a use case for this feature, so we prefer to leave the ground fresh for a potential future PEP.)</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># NOT valid</span> <span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="o">*</span><span class="n">Ts</span><span class="p">):</span> <span class="o">...</span> </pre></div> </div> </section> <section id="type-variable-tuples-with-callable"> <h3><a class="toc-backref" href="#type-variable-tuples-with-callable" role="doc-backlink">Type Variable Tuples with <code class="docutils literal notranslate"><span class="pre">Callable</span></code></a></h3> <p>Type variable tuples can also be used in the arguments section of a <code class="docutils literal notranslate"><span class="pre">Callable</span></code>:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Process</span><span class="p">:</span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span> <span class="bp">self</span><span class="p">,</span> <span class="n">target</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[</span><span class="o">*</span><span class="n">Ts</span><span class="p">],</span> <span class="kc">None</span><span class="p">],</span> <span class="n">args</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">[</span><span class="o">*</span><span class="n">Ts</span><span class="p">],</span> <span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span> <span class="k">def</span><span class="w"> </span><span class="nf">func</span><span class="p">(</span><span class="n">arg1</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">arg2</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span> <span class="n">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">func</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="s1">'foo'</span><span class="p">))</span> <span class="c1"># Valid</span> <span class="n">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">func</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="s1">'foo'</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span> <span class="c1"># Error</span> </pre></div> </div> <p>Other types and normal type variables can also be prefixed/suffixed to the type variable tuple:</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="s1">'T'</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">(</span><span class="n">f</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[</span><span class="nb">int</span><span class="p">,</span> <span class="o">*</span><span class="n">Ts</span><span class="p">,</span> <span class="n">T</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="n">Ts</span><span class="p">]]):</span> <span class="o">...</span> </pre></div> </div> <p>The behavior of a Callable containing an unpacked item, whether the item is a <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> or a tuple type, is to treat the elements as if they were the type for <code class="docutils literal notranslate"><span class="pre">*args</span></code>. So, <code class="docutils literal notranslate"><span class="pre">Callable[[*Ts],</span> <span class="pre">None]</span></code> is treated as the type of the function:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="o">*</span><span class="n">Ts</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span> </pre></div> </div> <p><code class="docutils literal notranslate"><span class="pre">Callable[[int,</span> <span class="pre">*Ts,</span> <span class="pre">T],</span> <span class="pre">Tuple[T,</span> <span class="pre">*Ts]]</span></code> is treated as the type of the function:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="o">*</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="o">*</span><span class="n">Ts</span><span class="p">,</span> <span class="n">T</span><span class="p">])</span> <span class="o">-></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="n">Ts</span><span class="p">]:</span> <span class="o">...</span> </pre></div> </div> </section> <section id="behaviour-when-type-parameters-are-not-specified"> <h3><a class="toc-backref" href="#behaviour-when-type-parameters-are-not-specified" role="doc-backlink">Behaviour when Type Parameters are not Specified</a></h3> <p>When a generic class parameterised by a type variable tuple is used without any type parameters, it behaves as if the type variable tuple was substituted with <code class="docutils literal notranslate"><span class="pre">Tuple[Any,</span> <span class="pre">...]</span></code>:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">takes_any_array</span><span class="p">(</span><span class="n">arr</span><span class="p">:</span> <span class="n">Array</span><span class="p">):</span> <span class="o">...</span> <span class="c1"># equivalent to:</span> <span class="k">def</span><span class="w"> </span><span class="nf">takes_any_array</span><span class="p">(</span><span class="n">arr</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="o">*</span><span class="n">Tuple</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="o">...</span><span class="p">]]):</span> <span class="o">...</span> <span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">]</span> <span class="n">takes_any_array</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="c1"># Valid</span> <span class="n">y</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Time</span><span class="p">,</span> <span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">]</span> <span class="n">takes_any_array</span><span class="p">(</span><span class="n">y</span><span class="p">)</span> <span class="c1"># Also valid</span> </pre></div> </div> <p>This enables gradual typing: existing functions accepting, for example, a plain TensorFlow <code class="docutils literal notranslate"><span class="pre">Tensor</span></code> will still be valid even if <code class="docutils literal notranslate"><span class="pre">Tensor</span></code> is made generic and calling code passes a <code class="docutils literal notranslate"><span class="pre">Tensor[Height,</span> <span class="pre">Width]</span></code>.</p> <p>This also works in the opposite direction:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">takes_specific_array</span><span class="p">(</span><span class="n">arr</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">]):</span> <span class="o">...</span> <span class="n">z</span><span class="p">:</span> <span class="n">Array</span> <span class="c1"># equivalent to Array[*Tuple[Any, ...]]</span> <span class="n">takes_specific_array</span><span class="p">(</span><span class="n">z</span><span class="p">)</span> </pre></div> </div> <p>(For details, see the section on <a class="reference internal" href="#unpacking-unbounded-tuple-types">Unpacking Unbounded Tuple Types</a>.)</p> <p>This way, even if libraries are updated to use types like <code class="docutils literal notranslate"><span class="pre">Array[Height,</span> <span class="pre">Width]</span></code>, users of those libraries won’t be forced to also apply type annotations to all of their code; users still have a choice about what parts of their code to type and which parts to not.</p> </section> <section id="aliases"> <h3><a class="toc-backref" href="#aliases" role="doc-backlink">Aliases</a></h3> <p>Generic aliases can be created using a type variable tuple in a similar way to regular type variables:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">IntTuple</span> <span class="o">=</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="o">*</span><span class="n">Ts</span><span class="p">]</span> <span class="n">NamedArray</span> <span class="o">=</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Array</span><span class="p">[</span><span class="o">*</span><span class="n">Ts</span><span class="p">]]</span> <span class="n">IntTuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">bool</span><span class="p">]</span> <span class="c1"># Equivalent to Tuple[int, float, bool]</span> <span class="n">NamedArray</span><span class="p">[</span><span class="n">Height</span><span class="p">]</span> <span class="c1"># Equivalent to Tuple[str, Array[Height]]</span> </pre></div> </div> <p>As this example shows, all type parameters passed to the alias are bound to the type variable tuple.</p> <p>Importantly for our original <code class="docutils literal notranslate"><span class="pre">Array</span></code> example (see <a class="reference internal" href="#summary-examples">Summary Examples</a>), this allows us to define convenience aliases for arrays of a fixed shape or datatype:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Shape</span> <span class="o">=</span> <span class="n">TypeVarTuple</span><span class="p">(</span><span class="s1">'Shape'</span><span class="p">)</span> <span class="n">DType</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'DType'</span><span class="p">)</span> <span class="k">class</span><span class="w"> </span><span class="nc">Array</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">DType</span><span class="p">,</span> <span class="o">*</span><span class="n">Shape</span><span class="p">]):</span> <span class="c1"># E.g. Float32Array[Height, Width, Channels]</span> <span class="n">Float32Array</span> <span class="o">=</span> <span class="n">Array</span><span class="p">[</span><span class="n">np</span><span class="o">.</span><span class="n">float32</span><span class="p">,</span> <span class="o">*</span><span class="n">Shape</span><span class="p">]</span> <span class="c1"># E.g. Array1D[np.uint8]</span> <span class="n">Array1D</span> <span class="o">=</span> <span class="n">Array</span><span class="p">[</span><span class="n">DType</span><span class="p">,</span> <span class="n">Any</span><span class="p">]</span> </pre></div> </div> <p>If an explicitly empty type parameter list is given, the type variable tuple in the alias is set empty:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">IntTuple</span><span class="p">[()]</span> <span class="c1"># Equivalent to Tuple[int]</span> <span class="n">NamedArray</span><span class="p">[()]</span> <span class="c1"># Equivalent to Tuple[str, Array[()]]</span> </pre></div> </div> <p>If the type parameter list is omitted entirely, the unspecified type variable tuples are treated as <code class="docutils literal notranslate"><span class="pre">Tuple[Any,</span> <span class="pre">...]</span></code> (similar to <a class="reference internal" href="#behaviour-when-type-parameters-are-not-specified">Behaviour when Type Parameters are not Specified</a>):</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">takes_float_array_of_any_shape</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Float32Array</span><span class="p">):</span> <span class="o">...</span> <span class="n">x</span><span class="p">:</span> <span class="n">Float32Array</span><span class="p">[</span><span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">]</span> <span class="o">=</span> <span class="n">Array</span><span class="p">()</span> <span class="n">takes_float_array_of_any_shape</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="c1"># Valid</span> <span class="k">def</span><span class="w"> </span><span class="nf">takes_float_array_with_specific_shape</span><span class="p">(</span> <span class="n">y</span><span class="p">:</span> <span class="n">Float32Array</span><span class="p">[</span><span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">]</span> <span class="p">):</span> <span class="o">...</span> <span class="n">y</span><span class="p">:</span> <span class="n">Float32Array</span> <span class="o">=</span> <span class="n">Array</span><span class="p">()</span> <span class="n">takes_float_array_with_specific_shape</span><span class="p">(</span><span class="n">y</span><span class="p">)</span> <span class="c1"># Valid</span> </pre></div> </div> <p>Normal <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code> instances can also be used in such aliases:</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="s1">'T'</span><span class="p">)</span> <span class="n">Foo</span> <span class="o">=</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="n">Ts</span><span class="p">]</span> <span class="c1"># T bound to str, Ts to Tuple[int]</span> <span class="n">Foo</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">int</span><span class="p">]</span> <span class="c1"># T bound to float, Ts to Tuple[()]</span> <span class="n">Foo</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span> <span class="c1"># T bound to Any, Ts to an Tuple[Any, ...]</span> <span class="n">Foo</span> </pre></div> </div> </section> <section id="substitution-in-aliases"> <h3><a class="toc-backref" href="#substitution-in-aliases" role="doc-backlink">Substitution in Aliases</a></h3> <p>In the previous section, we only discussed simple usage of generic aliases in which the type arguments were just simple types. However, a number of more exotic constructions are also possible.</p> <section id="type-arguments-can-be-variadic"> <h4><a class="toc-backref" href="#type-arguments-can-be-variadic" role="doc-backlink">Type Arguments can be Variadic</a></h4> <p>First, type arguments to generic aliases can be variadic. For example, a <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> can be used as a type argument:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Ts1</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'Ts1'</span><span class="p">)</span> <span class="n">Ts2</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'Ts2'</span><span class="p">)</span> <span class="n">IntTuple</span> <span class="o">=</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="o">*</span><span class="n">Ts1</span><span class="p">]</span> <span class="n">IntFloatTuple</span> <span class="o">=</span> <span class="n">IntTuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="o">*</span><span class="n">Ts2</span><span class="p">]</span> <span class="c1"># Valid</span> </pre></div> </div> <p>Here, <code class="docutils literal notranslate"><span class="pre">*Ts1</span></code> in the <code class="docutils literal notranslate"><span class="pre">IntTuple</span></code> alias is bound to <code class="docutils literal notranslate"><span class="pre">Tuple[float,</span> <span class="pre">*Ts2]</span></code>, resulting in an alias <code class="docutils literal notranslate"><span class="pre">IntFloatTuple</span></code> equivalent to <code class="docutils literal notranslate"><span class="pre">Tuple[int,</span> <span class="pre">float,</span> <span class="pre">*Ts2]</span></code>.</p> <p>Unpacked arbitrary-length tuples can also be used as type arguments, with similar effects:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">IntFloatsTuple</span> <span class="o">=</span> <span class="n">IntTuple</span><span class="p">[</span><span class="o">*</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="o">...</span><span class="p">]]</span> <span class="c1"># Valid</span> </pre></div> </div> <p>Here, <code class="docutils literal notranslate"><span class="pre">*Ts1</span></code> is bound to <code class="docutils literal notranslate"><span class="pre">*Tuple[float,</span> <span class="pre">...]</span></code>, resulting in <code class="docutils literal notranslate"><span class="pre">IntFloatsTuple</span></code> being equivalent to <code class="docutils literal notranslate"><span class="pre">Tuple[int,</span> <span class="pre">*Tuple[float,</span> <span class="pre">...]]</span></code>: a tuple consisting of an <code class="docutils literal notranslate"><span class="pre">int</span></code> then zero or more <code class="docutils literal notranslate"><span class="pre">float</span></code>s.</p> </section> <section id="variadic-arguments-require-variadic-aliases"> <h4><a class="toc-backref" href="#variadic-arguments-require-variadic-aliases" role="doc-backlink">Variadic Arguments Require Variadic Aliases</a></h4> <p>Variadic type arguments can only be used with generic aliases that are themselves variadic. For 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="s1">'T'</span><span class="p">)</span> <span class="n">IntTuple</span> <span class="o">=</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">T</span><span class="p">]</span> <span class="n">IntTuple</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="c1"># Valid</span> <span class="n">IntTuple</span><span class="p">[</span><span class="o">*</span><span class="n">Ts</span><span class="p">]</span> <span class="c1"># NOT valid</span> <span class="n">IntTuple</span><span class="p">[</span><span class="o">*</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="o">...</span><span class="p">]]</span> <span class="c1"># NOT valid</span> </pre></div> </div> <p>Here, <code class="docutils literal notranslate"><span class="pre">IntTuple</span></code> is a <em>non</em>-variadic generic alias that takes exactly one type argument. Hence, it cannot accept <code class="docutils literal notranslate"><span class="pre">*Ts</span></code> or <code class="docutils literal notranslate"><span class="pre">*Tuple[float,</span> <span class="pre">...]</span></code> as type arguments, because they represent an arbitrary number of types.</p> </section> <section id="aliases-with-both-typevars-and-typevartuples"> <h4><a class="toc-backref" href="#aliases-with-both-typevars-and-typevartuples" role="doc-backlink">Aliases with Both TypeVars and TypeVarTuples</a></h4> <p>In <a class="reference internal" href="#aliases">Aliases</a>, we briefly mentioned that aliases can be generic in both <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code>s and <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code>s:</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="s1">'T'</span><span class="p">)</span> <span class="n">Foo</span> <span class="o">=</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="n">Ts</span><span class="p">]</span> <span class="n">Foo</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">int</span><span class="p">]</span> <span class="c1"># T bound to str, Ts to Tuple[int]</span> <span class="n">Foo</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">int</span><span class="p">,</span> <span class="nb">float</span><span class="p">]</span> <span class="c1"># T bound to str, Ts to Tuple[int, float]</span> </pre></div> </div> <p>In accordance with <a class="reference internal" href="#multiple-type-variable-tuples-not-allowed">Multiple Type Variable Tuples: Not Allowed</a>, at most one <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> may appear in the type parameters to an alias. However, a <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> can be combined with an arbitrary number of <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code>s, both before and after:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">T1</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'T1'</span><span class="p">)</span> <span class="n">T2</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'T2'</span><span class="p">)</span> <span class="n">T3</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'T3'</span><span class="p">)</span> <span class="n">Tuple</span><span class="p">[</span><span class="o">*</span><span class="n">Ts</span><span class="p">,</span> <span class="n">T1</span><span class="p">,</span> <span class="n">T2</span><span class="p">]</span> <span class="c1"># Valid</span> <span class="n">Tuple</span><span class="p">[</span><span class="n">T1</span><span class="p">,</span> <span class="n">T2</span><span class="p">,</span> <span class="o">*</span><span class="n">Ts</span><span class="p">]</span> <span class="c1"># Valid</span> <span class="n">Tuple</span><span class="p">[</span><span class="n">T1</span><span class="p">,</span> <span class="o">*</span><span class="n">Ts</span><span class="p">,</span> <span class="n">T2</span><span class="p">,</span> <span class="n">T3</span><span class="p">]</span> <span class="c1"># Valid</span> </pre></div> </div> <p>In order to substitute these type variables with supplied type arguments, any type variables at the beginning or end of the type parameter list first consume type arguments, and then any remaining type arguments are bound to the <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code>:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Shrubbery</span> <span class="o">=</span> <span class="n">Tuple</span><span class="p">[</span><span class="o">*</span><span class="n">Ts</span><span class="p">,</span> <span class="n">T1</span><span class="p">,</span> <span class="n">T2</span><span class="p">]</span> <span class="n">Shrubbery</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">bool</span><span class="p">]</span> <span class="c1"># T2=bool, T1=str, Ts=Tuple[()]</span> <span class="n">Shrubbery</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">bool</span><span class="p">,</span> <span class="nb">float</span><span class="p">]</span> <span class="c1"># T2=float, T1=bool, Ts=Tuple[str]</span> <span class="n">Shrubbery</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">bool</span><span class="p">,</span> <span class="nb">float</span><span class="p">,</span> <span class="nb">int</span><span class="p">]</span> <span class="c1"># T2=int, T1=float, Ts=Tuple[str, bool]</span> <span class="n">Ptang</span> <span class="o">=</span> <span class="n">Tuple</span><span class="p">[</span><span class="n">T1</span><span class="p">,</span> <span class="o">*</span><span class="n">Ts</span><span class="p">,</span> <span class="n">T2</span><span class="p">,</span> <span class="n">T3</span><span class="p">]</span> <span class="n">Ptang</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">bool</span><span class="p">,</span> <span class="nb">float</span><span class="p">]</span> <span class="c1"># T1=str, T3=float, T2=bool, Ts=Tuple[()]</span> <span class="n">Ptang</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">bool</span><span class="p">,</span> <span class="nb">float</span><span class="p">,</span> <span class="nb">int</span><span class="p">]</span> <span class="c1"># T1=str, T3=int, T2=float, Ts=Tuple[bool]</span> </pre></div> </div> <p>Note that the minimum number of type arguments in such cases is set by the number of <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code>s:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Shrubbery</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="c1"># Not valid; Shrubbery needs at least two type arguments</span> </pre></div> </div> </section> <section id="splitting-arbitrary-length-tuples"> <h4><a class="toc-backref" href="#splitting-arbitrary-length-tuples" role="doc-backlink">Splitting Arbitrary-Length Tuples</a></h4> <p>A final complication occurs when an unpacked arbitrary-length tuple is used as a type argument to an alias consisting of both <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code>s and a <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code>:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Elderberries</span> <span class="o">=</span> <span class="n">Tuple</span><span class="p">[</span><span class="o">*</span><span class="n">Ts</span><span class="p">,</span> <span class="n">T1</span><span class="p">]</span> <span class="n">Hamster</span> <span class="o">=</span> <span class="n">Elderberries</span><span class="p">[</span><span class="o">*</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="o">...</span><span class="p">]]</span> <span class="c1"># valid</span> </pre></div> </div> <p>In such cases, the arbitrary-length tuple is split between the <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code>s and the <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code>. We assume the arbitrary-length tuple contains at least as many items as there are <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code>s, such that individual instances of the inner type - here <code class="docutils literal notranslate"><span class="pre">int</span></code> - are bound to any <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code>s present. The ‘rest’ of the arbitrary-length tuple - here <code class="docutils literal notranslate"><span class="pre">*Tuple[int,</span> <span class="pre">...]</span></code>, since a tuple of arbitrary length minus two items is still arbitrary-length - is bound to the <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code>.</p> <p>Here, therefore, <code class="docutils literal notranslate"><span class="pre">Hamster</span></code> is equivalent to <code class="docutils literal notranslate"><span class="pre">Tuple[*Tuple[int,</span> <span class="pre">...],</span> <span class="pre">int]</span></code>: a tuple consisting of zero or more <code class="docutils literal notranslate"><span class="pre">int</span></code>s, then a final <code class="docutils literal notranslate"><span class="pre">int</span></code>.</p> <p>Of course, such splitting only occurs if necessary. For example, if we instead did:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Elderberries</span><span class="p">[</span><span class="o">*</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="o">...</span><span class="p">],</span> <span class="nb">str</span><span class="p">]</span> </pre></div> </div> <p>Then splitting would not occur; <code class="docutils literal notranslate"><span class="pre">T1</span></code> would be bound to <code class="docutils literal notranslate"><span class="pre">str</span></code>, and <code class="docutils literal notranslate"><span class="pre">Ts</span></code> to <code class="docutils literal notranslate"><span class="pre">*Tuple[int,</span> <span class="pre">...]</span></code>.</p> <p>In particularly awkward cases, a <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> may consume both a type <em>and</em> a part of an arbitrary-length tuple type:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Elderberries</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="o">*</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="o">...</span><span class="p">]]</span> </pre></div> </div> <p>Here, <code class="docutils literal notranslate"><span class="pre">T1</span></code> is bound to <code class="docutils literal notranslate"><span class="pre">int</span></code>, and <code class="docutils literal notranslate"><span class="pre">Ts</span></code> is bound to <code class="docutils literal notranslate"><span class="pre">Tuple[str,</span> <span class="pre">*Tuple[int,</span> <span class="pre">...]]</span></code>. This expression is therefore equivalent to <code class="docutils literal notranslate"><span class="pre">Tuple[str,</span> <span class="pre">*Tuple[int,</span> <span class="pre">...],</span> <span class="pre">int]</span></code>: a tuple consisting of a <code class="docutils literal notranslate"><span class="pre">str</span></code>, then zero or more <code class="docutils literal notranslate"><span class="pre">int</span></code>s, ending with an <code class="docutils literal notranslate"><span class="pre">int</span></code>.</p> </section> <section id="typevartuples-cannot-be-split"> <h4><a class="toc-backref" href="#typevartuples-cannot-be-split" role="doc-backlink">TypeVarTuples Cannot be Split</a></h4> <p>Finally, although any arbitrary-length tuples in the type argument list can be split between the type variables and the type variable tuple, the same is not true of <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code>s in the argument list:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Ts1</span> <span class="o">=</span> <span class="n">TypeVarTuple</span><span class="p">(</span><span class="s1">'Ts1'</span><span class="p">)</span> <span class="n">Ts2</span> <span class="o">=</span> <span class="n">TypeVarTuple</span><span class="p">(</span><span class="s1">'Ts2'</span><span class="p">)</span> <span class="n">Camelot</span> <span class="o">=</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="n">Ts1</span><span class="p">]</span> <span class="n">Camelot</span><span class="p">[</span><span class="o">*</span><span class="n">Ts2</span><span class="p">]</span> <span class="c1"># NOT valid</span> </pre></div> </div> <p>This is not possible because, unlike in the case of an unpacked arbitrary-length tuple, there is no way to ‘peer inside’ the <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> to see what its individual types are.</p> </section> </section> <section id="overloads-for-accessing-individual-types"> <h3><a class="toc-backref" href="#overloads-for-accessing-individual-types" role="doc-backlink">Overloads for Accessing Individual Types</a></h3> <p>For situations where we require access to each individual type in the type variable tuple, overloads can be used with individual <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code> instances in place of the type variable tuple:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Shape</span> <span class="o">=</span> <span class="n">TypeVarTuple</span><span class="p">(</span><span class="s1">'Shape'</span><span class="p">)</span> <span class="n">Axis1</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'Axis1'</span><span class="p">)</span> <span class="n">Axis2</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'Axis2'</span><span class="p">)</span> <span class="n">Axis3</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'Axis3'</span><span class="p">)</span> <span class="k">class</span><span class="w"> </span><span class="nc">Array</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="o">*</span><span class="n">Shape</span><span class="p">]):</span> <span class="nd">@overload</span> <span class="k">def</span><span class="w"> </span><span class="nf">transpose</span><span class="p">(</span> <span class="bp">self</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Axis1</span><span class="p">,</span> <span class="n">Axis2</span><span class="p">]</span> <span class="p">)</span> <span class="o">-></span> <span class="n">Array</span><span class="p">[</span><span class="n">Axis2</span><span class="p">,</span> <span class="n">Axis1</span><span class="p">]:</span> <span class="o">...</span> <span class="nd">@overload</span> <span class="k">def</span><span class="w"> </span><span class="nf">transpose</span><span class="p">(</span> <span class="bp">self</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Axis1</span><span class="p">,</span> <span class="n">Axis2</span><span class="p">,</span> <span class="n">Axis3</span><span class="p">]</span> <span class="p">)</span> <span class="o">-></span> <span class="n">Array</span><span class="p">[</span><span class="n">Axis3</span><span class="p">,</span> <span class="n">Axis2</span><span class="p">,</span> <span class="n">Axis1</span><span class="p">]:</span> <span class="o">...</span> </pre></div> </div> <p>(For array shape operations in particular, having to specify overloads for each possible rank is, of course, a rather cumbersome solution. However, it’s the best we can do without additional type manipulation mechanisms. We plan to introduce these in a future PEP.)</p> </section> </section> <section id="rationale-and-rejected-ideas"> <h2><a class="toc-backref" href="#rationale-and-rejected-ideas" role="doc-backlink">Rationale and Rejected Ideas</a></h2> <section id="shape-arithmetic"> <h3><a class="toc-backref" href="#shape-arithmetic" role="doc-backlink">Shape Arithmetic</a></h3> <p>Considering the use case of array shapes in particular, note that as of this PEP, it is not yet possible to describe arithmetic transformations of array dimensions - for example, <code class="docutils literal notranslate"><span class="pre">def</span> <span class="pre">repeat_each_element(x:</span> <span class="pre">Array[N])</span> <span class="pre">-></span> <span class="pre">Array[2*N]</span></code>. We consider this out-of-scope for the current PEP, but plan to propose additional mechanisms that <em>will</em> enable this in a future PEP.</p> </section> <section id="supporting-variadicity-through-aliases"> <h3><a class="toc-backref" href="#supporting-variadicity-through-aliases" role="doc-backlink">Supporting Variadicity Through Aliases</a></h3> <p>As noted in the introduction, it <em>is</em> possible to avoid variadic generics by simply defining aliases for each possible number of type parameters:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Array1</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">Axis1</span><span class="p">]):</span> <span class="o">...</span> <span class="k">class</span><span class="w"> </span><span class="nc">Array2</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">Axis1</span><span class="p">,</span> <span class="n">Axis2</span><span class="p">]):</span> <span class="o">...</span> </pre></div> </div> <p>However, this seems somewhat clumsy - it requires users to unnecessarily pepper their code with 1s, 2s, and so on for each rank necessary.</p> </section> <section id="construction-of-typevartuple"> <h3><a class="toc-backref" href="#construction-of-typevartuple" role="doc-backlink">Construction of <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code></a></h3> <p><code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> began as <code class="docutils literal notranslate"><span class="pre">ListVariadic</span></code>, based on its naming in an early implementation in Pyre.</p> <p>We then changed this to <code class="docutils literal notranslate"><span class="pre">TypeVar(list=True)</span></code>, on the basis that a) it better emphasises the similarity to <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code>, and b) the meaning of ‘list’ is more easily understood than the jargon of ‘variadic’.</p> <p>Once we’d decided that a variadic type variable should behave like a <code class="docutils literal notranslate"><span class="pre">Tuple</span></code>, we also considered <code class="docutils literal notranslate"><span class="pre">TypeVar(bound=Tuple)</span></code>, which is similarly intuitive and accomplishes most what we wanted without requiring any new arguments to <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code>. However, we realised this may constrain us in the future, if for example we want type bounds or variance to function slightly differently for variadic type variables than what the semantics of <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code> might otherwise imply. Also, we may later wish to support arguments that should not be supported by regular type variables (such as <code class="docutils literal notranslate"><span class="pre">arbitrary_len</span></code> <a class="footnote-reference brackets" href="#arbitrary-len" id="id8">[10]</a>).</p> <p>We therefore settled on <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code>.</p> </section> <section id="unspecified-type-parameters-tuple-vs-typevartuple"> <h3><a class="toc-backref" href="#unspecified-type-parameters-tuple-vs-typevartuple" role="doc-backlink">Unspecified Type Parameters: Tuple vs TypeVarTuple</a></h3> <p>In order to support gradual typing, this PEP states that <em>both</em> of the following examples should type-check correctly:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">takes_any_array</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">):</span> <span class="o">...</span> <span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">]</span> <span class="n">takes_any_array</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">takes_specific_array</span><span class="p">(</span><span class="n">y</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">]):</span> <span class="o">...</span> <span class="n">y</span><span class="p">:</span> <span class="n">Array</span> <span class="n">takes_specific_array</span><span class="p">(</span><span class="n">y</span><span class="p">)</span> </pre></div> </div> <p>Note that this is in contrast to the behaviour of the only currently-existing variadic type in Python, <code class="docutils literal notranslate"><span class="pre">Tuple</span></code>:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">takes_any_tuple</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">):</span> <span class="o">...</span> <span class="n">x</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="n">takes_any_tuple</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="c1"># Valid</span> <span class="k">def</span><span class="w"> </span><span class="nf">takes_specific_tuple</span><span class="p">(</span><span class="n">y</span><span class="p">:</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">]):</span> <span class="o">...</span> <span class="n">y</span><span class="p">:</span> <span class="n">Tuple</span> <span class="n">takes_specific_tuple</span><span class="p">(</span><span class="n">y</span><span class="p">)</span> <span class="c1"># Error</span> </pre></div> </div> <p>The rules for <code class="docutils literal notranslate"><span class="pre">Tuple</span></code> were deliberately chosen such that the latter case is an error: it was thought to be more likely that the programmer has made a mistake than that the function expects a specific kind of <code class="docutils literal notranslate"><span class="pre">Tuple</span></code> but the specific kind of <code class="docutils literal notranslate"><span class="pre">Tuple</span></code> passed is unknown to the type checker. Additionally, <code class="docutils literal notranslate"><span class="pre">Tuple</span></code> is something of a special case, in that it is used to represent immutable sequences. That is, if an object’s type is inferred to be an unparameterised <code class="docutils literal notranslate"><span class="pre">Tuple</span></code>, it is not necessarily because of incomplete typing.</p> <p>In contrast, if an object’s type is inferred to be an unparameterised <code class="docutils literal notranslate"><span class="pre">Array</span></code>, it is much more likely that the user has simply not yet fully annotated their code, or that the signature of a shape-manipulating library function cannot yet be expressed using the typing system and therefore returning a plain <code class="docutils literal notranslate"><span class="pre">Array</span></code> is the only option. We rarely deal with arrays of truly arbitrary shape; in certain cases, <em>some</em> parts of the shape will be arbitrary - for example, when dealing with sequences, the first two parts of the shape are often ‘batch’ and ‘time’ - but we plan to support these cases explicitly in a future PEP with a syntax such as <code class="docutils literal notranslate"><span class="pre">Array[Batch,</span> <span class="pre">Time,</span> <span class="pre">...]</span></code>.</p> <p>We therefore made the decision to have variadic generics <em>other</em> than <code class="docutils literal notranslate"><span class="pre">Tuple</span></code> behave differently, in order to give the user more flexibility in how much of their code they wish to annotate, and to enable compatibility between old unannotated code and new versions of libraries which do use these type annotations.</p> </section> </section> <section id="alternatives"> <h2><a class="toc-backref" href="#alternatives" role="doc-backlink">Alternatives</a></h2> <p>It should be noted that the approach outlined in this PEP to solve the issue of shape checking in numerical libraries is <em>not</em> the only approach possible. Examples of lighter-weight alternatives based on <em>runtime</em> checking include ShapeGuard <a class="footnote-reference brackets" href="#shapeguard" id="id9">[13]</a>, tsanley <a class="footnote-reference brackets" href="#tsanley" id="id10">[11]</a>, and PyContracts <a class="footnote-reference brackets" href="#pycontracts" id="id11">[12]</a>.</p> <p>While these existing approaches improve significantly on the default situation of shape checking only being possible through lengthy and verbose assert statements, none of them enable <em>static</em> analysis of shape correctness. As mentioned in <a class="reference internal" href="#motivation">Motivation</a>, this is particularly desirable for machine learning applications where, due to library and infrastructure complexity, even relatively simple programs must suffer long startup times; iterating by running the program until it crashes, as is necessary with these existing runtime-based approaches, can be a tedious and frustrating experience.</p> <p>Our hope with this PEP is to begin to codify generic type annotations as an official, language-supported way of dealing with shape correctness. With something of a standard in place, in the long run, this will hopefully enable a thriving ecosystem of tools for analysing and verifying shape properties of numerical computing programs.</p> </section> <section id="grammar-changes"> <h2><a class="toc-backref" href="#grammar-changes" role="doc-backlink">Grammar Changes</a></h2> <p>This PEP requires two grammar changes.</p> <section id="change-1-star-expressions-in-indexes"> <h3><a class="toc-backref" href="#change-1-star-expressions-in-indexes" role="doc-backlink">Change 1: Star Expressions in Indexes</a></h3> <p>The first grammar change enables use of star expressions in index operations (that is, within square brackets), necessary to support star-unpacking of TypeVarTuples:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">DType</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'DType'</span><span class="p">)</span> <span class="n">Shape</span> <span class="o">=</span> <span class="n">TypeVarTuple</span><span class="p">(</span><span class="s1">'Shape'</span><span class="p">)</span> <span class="k">class</span><span class="w"> </span><span class="nc">Array</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">DType</span><span class="p">,</span> <span class="o">*</span><span class="n">Shape</span><span class="p">]):</span> <span class="o">...</span> </pre></div> </div> <p>Before:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span>slices: | slice !',' | ','.slice+ [','] </pre></div> </div> <p>After:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span>slices: | slice !',' | ','.(slice | starred_expression)+ [','] </pre></div> </div> <p>As with star-unpacking in other contexts, the star operator calls <code class="docutils literal notranslate"><span class="pre">__iter__</span></code> on the callee, and adds the contents of the resulting iterator to the argument passed to <code class="docutils literal notranslate"><span class="pre">__getitem__</span></code>. For example, if we do <code class="docutils literal notranslate"><span class="pre">foo[a,</span> <span class="pre">*b,</span> <span class="pre">c]</span></code>, and <code class="docutils literal notranslate"><span class="pre">b.__iter__</span></code> produces an iterator yielding <code class="docutils literal notranslate"><span class="pre">d</span></code> and <code class="docutils literal notranslate"><span class="pre">e</span></code>, <code class="docutils literal notranslate"><span class="pre">foo.__getitem__</span></code> would receive <code class="docutils literal notranslate"><span class="pre">(a,</span> <span class="pre">d,</span> <span class="pre">e,</span> <span class="pre">c)</span></code>.</p> <p>To put it another way, note that <code class="docutils literal notranslate"><span class="pre">x[...,</span> <span class="pre">*a,</span> <span class="pre">...]</span></code> produces the same result as <code class="docutils literal notranslate"><span class="pre">x[(...,</span> <span class="pre">*a,</span> <span class="pre">...)]</span></code> (with any slices <code class="docutils literal notranslate"><span class="pre">i:j</span></code> in <code class="docutils literal notranslate"><span class="pre">...</span></code> replaced with <code class="docutils literal notranslate"><span class="pre">slice(i,</span> <span class="pre">j)</span></code>, with the one edge case that <code class="docutils literal notranslate"><span class="pre">x[*a]</span></code> becomes <code class="docutils literal notranslate"><span class="pre">x[(*a,)]</span></code>).</p> <section id="typevartuple-implementation"> <h4><a class="toc-backref" href="#typevartuple-implementation" role="doc-backlink">TypeVarTuple Implementation</a></h4> <p>With this grammar change, <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> is implemented as follows. Note that this implementation is useful only for the benefit of a) correct <code class="docutils literal notranslate"><span class="pre">repr()</span></code> and b) runtime analysers; static analysers would not use the implementation.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">TypeVarTuple</span><span class="p">:</span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">_name</span> <span class="o">=</span> <span class="n">name</span> <span class="bp">self</span><span class="o">.</span><span class="n">_unpacked</span> <span class="o">=</span> <span class="n">UnpackedTypeVarTuple</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="fm">__iter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">yield</span> <span class="bp">self</span><span class="o">.</span><span class="n">_unpacked</span> <span class="k">def</span><span class="w"> </span><span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_name</span> <span class="k">class</span><span class="w"> </span><span class="nc">UnpackedTypeVarTuple</span><span class="p">:</span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">_name</span> <span class="o">=</span> <span class="n">name</span> <span class="k">def</span><span class="w"> </span><span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">return</span> <span class="s1">'*'</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">_name</span> </pre></div> </div> </section> <section id="implications"> <h4><a class="toc-backref" href="#implications" role="doc-backlink">Implications</a></h4> <p>This grammar change implies a number of additional changes in behaviour not required by this PEP. We choose to allow these additional changes rather than disallowing them at a syntax level in order to keep the syntax change as small as possible.</p> <p>First, the grammar change enables star-unpacking of other structures, such as lists, within indexing operations:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">idxs</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="n">array_slice</span> <span class="o">=</span> <span class="n">array</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="o">*</span><span class="n">idxs</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="c1"># Equivalent to [0, 1, 2, -1]</span> <span class="n">array</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="o">*</span><span class="n">idxs</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">array_slice</span> <span class="c1"># Also allowed</span> </pre></div> </div> <p>Second, more than one instance of a star-unpack can occur within an index:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">array</span><span class="p">[</span><span class="o">*</span><span class="n">idxs_to_select</span><span class="p">,</span> <span class="o">*</span><span class="n">idxs_to_select</span><span class="p">]</span> <span class="c1"># Equivalent to array[1, 2, 1, 2]</span> </pre></div> </div> <p>Note that this PEP disallows multiple unpacked TypeVarTuples within a single type parameter list. This requirement would therefore need to be implemented in type checking tools themselves rather than at the syntax level.</p> <p>Third, slices may co-occur with starred expressions:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">array</span><span class="p">[</span><span class="mi">3</span><span class="p">:</span><span class="mi">5</span><span class="p">,</span> <span class="o">*</span><span class="n">idxs_to_select</span><span class="p">]</span> <span class="c1"># Equivalent to array[3:5, 1, 2]</span> </pre></div> </div> <p>However, note that slices involving starred expressions are still invalid:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Syntax error</span> <span class="n">array</span><span class="p">[</span><span class="o">*</span><span class="n">idxs_start</span><span class="p">:</span><span class="o">*</span><span class="n">idxs_end</span><span class="p">]</span> </pre></div> </div> </section> </section> <section id="change-2-args-as-a-typevartuple"> <h3><a class="toc-backref" href="#change-2-args-as-a-typevartuple" role="doc-backlink">Change 2: <code class="docutils literal notranslate"><span class="pre">*args</span></code> as a TypeVarTuple</a></h3> <p>The second change enables use of <code class="docutils literal notranslate"><span class="pre">*args:</span> <span class="pre">*Ts</span></code> in function definitions.</p> <p>Before:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">star_etc</span><span class="p">:</span> <span class="o">|</span> <span class="s1">'*'</span> <span class="n">param_no_default</span> <span class="n">param_maybe_default</span><span class="o">*</span> <span class="p">[</span><span class="n">kwds</span><span class="p">]</span> <span class="o">|</span> <span class="s1">'*'</span> <span class="s1">','</span> <span class="n">param_maybe_default</span><span class="o">+</span> <span class="p">[</span><span class="n">kwds</span><span class="p">]</span> <span class="o">|</span> <span class="n">kwds</span> </pre></div> </div> <p>After:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">star_etc</span><span class="p">:</span> <span class="o">|</span> <span class="s1">'*'</span> <span class="n">param_no_default</span> <span class="n">param_maybe_default</span><span class="o">*</span> <span class="p">[</span><span class="n">kwds</span><span class="p">]</span> <span class="o">|</span> <span class="s1">'*'</span> <span class="n">param_no_default_star_annotation</span> <span class="n">param_maybe_default</span><span class="o">*</span> <span class="p">[</span><span class="n">kwds</span><span class="p">]</span> <span class="c1"># New</span> <span class="o">|</span> <span class="s1">'*'</span> <span class="s1">','</span> <span class="n">param_maybe_default</span><span class="o">+</span> <span class="p">[</span><span class="n">kwds</span><span class="p">]</span> <span class="o">|</span> <span class="n">kwds</span> </pre></div> </div> <p>Where:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span>param_no_default_star_annotation: | param_star_annotation ',' TYPE_COMMENT? | param_star_annotation TYPE_COMMENT? &')' param_star_annotation: NAME star_annotation star_annotation: ':' star_expression </pre></div> </div> <p>We also need to deal with the <code class="docutils literal notranslate"><span class="pre">star_expression</span></code> that results from this construction. Normally, a <code class="docutils literal notranslate"><span class="pre">star_expression</span></code> occurs within the context of e.g. a list, so a <code class="docutils literal notranslate"><span class="pre">star_expression</span></code> is handled by essentially calling <code class="docutils literal notranslate"><span class="pre">iter()</span></code> on the starred object, and inserting the results of the resulting iterator into the list at the appropriate place. For <code class="docutils literal notranslate"><span class="pre">*args:</span> <span class="pre">*Ts</span></code>, however, we must process the <code class="docutils literal notranslate"><span class="pre">star_expression</span></code> in a different way.</p> <p>We do this by instead making a special case for the <code class="docutils literal notranslate"><span class="pre">star_expression</span></code> resulting from <code class="docutils literal notranslate"><span class="pre">*args:</span> <span class="pre">*Ts</span></code>, emitting code equivalent to <code class="docutils literal notranslate"><span class="pre">[annotation_value]</span> <span class="pre">=</span> <span class="pre">[*Ts]</span></code>. That is, we create an iterator from <code class="docutils literal notranslate"><span class="pre">Ts</span></code> by calling <code class="docutils literal notranslate"><span class="pre">Ts.__iter__</span></code>, fetch a single value from the iterator, verify that the iterator is exhausted, and set that value as the annotation value. This results in the unpacked <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> being set directly as the runtime annotation for <code class="docutils literal notranslate"><span class="pre">*args</span></code>:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">Ts</span> <span class="o">=</span> <span class="n">TypeVarTuple</span><span class="p">(</span><span class="s1">'Ts'</span><span class="p">)</span> <span class="gp">>>> </span><span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="o">*</span><span class="n">Ts</span><span class="p">):</span> <span class="k">pass</span> <span class="gp">>>> </span><span class="n">foo</span><span class="o">.</span><span class="vm">__annotations__</span> <span class="go">{'args': *Ts}</span> <span class="go"># *Ts is the repr() of Ts._unpacked, an instance of UnpackedTypeVarTuple</span> </pre></div> </div> <p>This allows the runtime annotation to be consistent with an AST representation that uses a <code class="docutils literal notranslate"><span class="pre">Starred</span></code> node for the annotations of <code class="docutils literal notranslate"><span class="pre">args</span></code> - in turn important for tools that rely on the AST such as mypy to correctly recognise the construction:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="nb">print</span><span class="p">(</span><span class="n">ast</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">ast</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="s1">'def foo(*args: *Ts): pass'</span><span class="p">),</span> <span class="n">indent</span><span class="o">=</span><span class="mi">2</span><span class="p">))</span> <span class="go">Module(</span> <span class="go"> body=[</span> <span class="go"> FunctionDef(</span> <span class="go"> name='foo',</span> <span class="go"> args=arguments(</span> <span class="go"> posonlyargs=[],</span> <span class="go"> args=[],</span> <span class="go"> vararg=arg(</span> <span class="go"> arg='args',</span> <span class="go"> annotation=Starred(</span> <span class="go"> value=Name(id='Ts', ctx=Load()),</span> <span class="go"> ctx=Load())),</span> <span class="go"> kwonlyargs=[],</span> <span class="go"> kw_defaults=[],</span> <span class="go"> defaults=[]),</span> <span class="go"> body=[</span> <span class="go"> Pass()],</span> <span class="go"> decorator_list=[])],</span> <span class="go"> type_ignores=[])</span> </pre></div> </div> <p>Note that the only scenario in which this grammar change allows <code class="docutils literal notranslate"><span class="pre">*Ts</span></code> to be used as a direct annotation (rather than being wrapped in e.g. <code class="docutils literal notranslate"><span class="pre">Tuple[*Ts]</span></code>) is <code class="docutils literal notranslate"><span class="pre">*args</span></code>. Other uses are still invalid:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">x</span><span class="p">:</span> <span class="o">*</span><span class="n">Ts</span> <span class="c1"># Syntax error</span> <span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="o">*</span><span class="n">Ts</span><span class="p">):</span> <span class="k">pass</span> <span class="c1"># Syntax error</span> </pre></div> </div> <section id="id12"> <h4><a class="toc-backref" href="#id12" role="doc-backlink">Implications</a></h4> <p>As with the first grammar change, this change also has a number of side effects. In particular, the annotation of <code class="docutils literal notranslate"><span class="pre">*args</span></code> could be set to a starred object other than a <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code> - for example, the following nonsensical annotations are possible:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">foo</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="gp">>>> </span><span class="k">def</span><span class="w"> </span><span class="nf">bar</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="o">*</span><span class="n">foo</span><span class="p">):</span> <span class="k">pass</span> <span class="gp">>>> </span><span class="n">bar</span><span class="o">.</span><span class="vm">__annotations__</span> <span class="go">{'args': 1}</span> <span class="gp">>>> </span><span class="n">foo</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="gp">>>> </span><span class="k">def</span><span class="w"> </span><span class="nf">bar</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="o">*</span><span class="n">foo</span><span class="p">):</span> <span class="k">pass</span> <span class="go">ValueError: too many values to unpack (expected 1)</span> </pre></div> </div> <p>Again, prevention of such annotations will need to be done by, say, static checkers, rather than at the level of syntax.</p> </section> </section> <section id="alternatives-why-not-just-use-unpack"> <h3><a class="toc-backref" href="#alternatives-why-not-just-use-unpack" role="doc-backlink">Alternatives (Why Not Just Use <code class="docutils literal notranslate"><span class="pre">Unpack</span></code>?)</a></h3> <p>If these grammar changes are considered too burdensome, there are two alternatives.</p> <p>The first would be to <strong>support change 1 but not change 2</strong>. Variadic generics are more important to us than the ability to annotate <code class="docutils literal notranslate"><span class="pre">*args</span></code>.</p> <p>The second alternative would be to <strong>use ``Unpack`` instead</strong>, requiring no grammar changes. However, we regard this as a suboptimal solution for two reasons:</p> <ul class="simple"> <li><strong>Readability</strong>. <code class="docutils literal notranslate"><span class="pre">class</span> <span class="pre">Array(Generic[DType,</span> <span class="pre">Unpack[Shape]])</span></code> is a bit of a mouthful; the flow of reading is interrupted by length of <code class="docutils literal notranslate"><span class="pre">Unpack</span></code> and the extra set of square brackets. <code class="docutils literal notranslate"><span class="pre">class</span> <span class="pre">Array(Generic[DType,</span> <span class="pre">*Shape])</span></code> is much easier to skim, while still marking <code class="docutils literal notranslate"><span class="pre">Shape</span></code> as special.</li> <li><strong>Intuitiveness</strong>. We think a user is more likely to intuitively understand the meaning of <code class="docutils literal notranslate"><span class="pre">*Ts</span></code> - especially when they see that <code class="docutils literal notranslate"><span class="pre">Ts</span></code> is a TypeVar**Tuple** - than the meaning of <code class="docutils literal notranslate"><span class="pre">Unpack[Ts]</span></code>. (This assumes the user is familiar with star-unpacking in other contexts; if the user is reading or writing code that uses variadic generics, this seems reasonable.)</li> </ul> <p>If even change 1 is thought too significant a change, therefore, it might be better for us to reconsider our options before going ahead with this second alternative.</p> </section> </section> <section id="backwards-compatibility"> <h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2> <p>The <code class="docutils literal notranslate"><span class="pre">Unpack</span></code> version of the PEP should be back-portable to previous versions of Python.</p> <p>Gradual typing is enabled by the fact that unparameterised variadic classes are compatible with an arbitrary number of type parameters. This means that if existing classes are made generic, a) all existing (unparameterised) uses of the class will still work, and b) parameterised and unparameterised versions of the class can be used together (relevant if, for example, library code is updated to use parameters while user code is not, or vice-versa).</p> </section> <section id="reference-implementation"> <h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2> <p>Two reference implementations of type-checking functionality exist: one in Pyre, as of v0.9.0, and one in Pyright, as of v1.1.108.</p> <p>A preliminary implementation of the <code class="docutils literal notranslate"><span class="pre">Unpack</span></code> version of the PEP in CPython is available in <a class="reference external" href="https://github.com/python/cpython/pull/24527">cpython/23527</a>. A preliminary version of the version using the star operator, based on an early implementation of <a class="pep reference internal" href="../pep-0637/" title="PEP 637 – Support for indexing with keyword arguments">PEP 637</a>, is also available at <a class="reference external" href="https://github.com/mrahtz/cpython/tree/pep637%2B646">mrahtz/cpython/pep637+646</a>.</p> </section> <section id="appendix-a-shape-typing-use-cases"> <h2><a class="toc-backref" href="#appendix-a-shape-typing-use-cases" role="doc-backlink">Appendix A: Shape Typing Use Cases</a></h2> <p>To give this PEP additional context for those particularly interested in the array typing use case, in this appendix we expand on the different ways this PEP can be used for specifying shape-based subtypes.</p> <section id="use-case-1-specifying-shape-values"> <h3><a class="toc-backref" href="#use-case-1-specifying-shape-values" role="doc-backlink">Use Case 1: Specifying Shape Values</a></h3> <p>The simplest way to parameterise array types is using <code class="docutils literal notranslate"><span class="pre">Literal</span></code> type parameters - e.g. <code class="docutils literal notranslate"><span class="pre">Array[Literal[64],</span> <span class="pre">Literal[64]]</span></code>.</p> <p>We can attach names to each parameter using normal type variables:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">K</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'K'</span><span class="p">)</span> <span class="n">N</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'N'</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">matrix_vector_multiply</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">K</span><span class="p">,</span> <span class="n">N</span><span class="p">],</span> <span class="n">y</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">N</span><span class="p">])</span> <span class="o">-></span> <span class="n">Array</span><span class="p">[</span><span class="n">K</span><span class="p">]:</span> <span class="o">...</span> <span class="n">a</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Literal</span><span class="p">[</span><span class="mi">64</span><span class="p">],</span> <span class="n">Literal</span><span class="p">[</span><span class="mi">32</span><span class="p">]]</span> <span class="n">b</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Literal</span><span class="p">[</span><span class="mi">32</span><span class="p">]]</span> <span class="n">matrix_vector_multiply</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span> <span class="c1"># Result is Array[Literal[64]]</span> </pre></div> </div> <p>Note that such names have a purely local scope. That is, the name <code class="docutils literal notranslate"><span class="pre">K</span></code> is bound to <code class="docutils literal notranslate"><span class="pre">Literal[64]</span></code> only within <code class="docutils literal notranslate"><span class="pre">matrix_vector_multiply</span></code>. To put it another way, there’s no relationship between the value of <code class="docutils literal notranslate"><span class="pre">K</span></code> in different signatures. This is important: it would be inconvenient if every axis named <code class="docutils literal notranslate"><span class="pre">K</span></code> were constrained to have the same value throughout the entire program.</p> <p>The disadvantage of this approach is that we have no ability to enforce shape semantics across different calls. For example, we can’t address the problem mentioned in <a class="reference internal" href="#motivation">Motivation</a>: if one function returns an array with leading dimensions ‘Time × Batch’, and another function takes the same array assuming leading dimensions ‘Batch × Time’, we have no way of detecting this.</p> <p>The main advantage is that in some cases, axis sizes really are what we care about. This is true for both simple linear algebra operations such as the matrix manipulations above, but also in more complicated transformations such as convolutional layers in neural networks, where it would be of great utility to the programmer to be able to inspect the array size after each layer using static analysis. To aid this, in the future we would like to explore possibilities for additional type operators that enable arithmetic on array shapes - for example:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">repeat_each_element</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">N</span><span class="p">])</span> <span class="o">-></span> <span class="n">Array</span><span class="p">[</span><span class="n">Mul</span><span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="n">N</span><span class="p">]]:</span> <span class="o">...</span> </pre></div> </div> <p>Such arithmetic type operators would only make sense if names such as <code class="docutils literal notranslate"><span class="pre">N</span></code> refer to axis size.</p> </section> <section id="use-case-2-specifying-shape-semantics"> <h3><a class="toc-backref" href="#use-case-2-specifying-shape-semantics" role="doc-backlink">Use Case 2: Specifying Shape Semantics</a></h3> <p>A second approach (the one that most of the examples in this PEP are based around) is to forgo annotation with actual axis size, and instead annotate axis <em>type</em>.</p> <p>This would enable us to solve the problem of enforcing shape properties across calls. For example:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># lib.py</span> <span class="k">class</span><span class="w"> </span><span class="nc">Batch</span><span class="p">:</span> <span class="k">pass</span> <span class="k">class</span><span class="w"> </span><span class="nc">Time</span><span class="p">:</span> <span class="k">pass</span> <span class="k">def</span><span class="w"> </span><span class="nf">make_array</span><span class="p">()</span> <span class="o">-></span> <span class="n">Array</span><span class="p">[</span><span class="n">Batch</span><span class="p">,</span> <span class="n">Time</span><span class="p">]:</span> <span class="o">...</span> <span class="c1"># user.py</span> <span class="kn">from</span><span class="w"> </span><span class="nn">lib</span><span class="w"> </span><span class="kn">import</span> <span class="n">Batch</span><span class="p">,</span> <span class="n">Time</span> <span class="c1"># `Batch` and `Time` have the same identity as in `lib`,</span> <span class="c1"># so must take array as produced by `lib.make_array`</span> <span class="k">def</span><span class="w"> </span><span class="nf">use_array</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Batch</span><span class="p">,</span> <span class="n">Time</span><span class="p">]):</span> <span class="o">...</span> </pre></div> </div> <p>Note that in this case, names are <em>global</em> (to the extent that we use the same <code class="docutils literal notranslate"><span class="pre">Batch</span></code> type in different place). However, because names refer only to axis <em>types</em>, this doesn’t constrain the <em>value</em> of certain axes to be the same through (that is, this doesn’t constrain all axes named <code class="docutils literal notranslate"><span class="pre">Height</span></code> to have a value of, say, 480 throughout).</p> <p>The argument <em>for</em> this approach is that in many cases, axis <em>type</em> is the more important thing to verify; we care more about which axis is which than what the specific size of each axis is.</p> <p>It also does not preclude cases where we wish to describe shape transformations without knowing the type ahead of time. For example, we can still write:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">K</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'K'</span><span class="p">)</span> <span class="n">N</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">'N'</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">matrix_vector_multiply</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">K</span><span class="p">,</span> <span class="n">N</span><span class="p">],</span> <span class="n">y</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">N</span><span class="p">])</span> <span class="o">-></span> <span class="n">Array</span><span class="p">[</span><span class="n">K</span><span class="p">]:</span> <span class="o">...</span> </pre></div> </div> <p>We can then use this with:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Batch</span><span class="p">:</span> <span class="k">pass</span> <span class="k">class</span><span class="w"> </span><span class="nc">Values</span><span class="p">:</span> <span class="k">pass</span> <span class="n">batch_of_values</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Batch</span><span class="p">,</span> <span class="n">Values</span><span class="p">]</span> <span class="n">value_weights</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Values</span><span class="p">]</span> <span class="n">matrix_vector_multiply</span><span class="p">(</span><span class="n">batch_of_values</span><span class="p">,</span> <span class="n">value_weights</span><span class="p">)</span> <span class="c1"># Result is Array[Batch]</span> </pre></div> </div> <p>The disadvantages are the inverse of the advantages from use case 1. In particular, this approach does not lend itself well to arithmetic on axis types: <code class="docutils literal notranslate"><span class="pre">Mul[2,</span> <span class="pre">Batch]</span></code> would be as meaningless as <code class="docutils literal notranslate"><span class="pre">2</span> <span class="pre">*</span> <span class="pre">int</span></code>.</p> </section> <section id="discussion"> <h3><a class="toc-backref" href="#discussion" role="doc-backlink">Discussion</a></h3> <p>Note that use cases 1 and 2 are mutually exclusive in user code. Users can verify size or semantic type but not both.</p> <p>As of this PEP, we are agnostic about which approach will provide most benefit. Since the features introduced in this PEP are compatible with both approaches, however, we leave the door open.</p> </section> <section id="why-not-both"> <h3><a class="toc-backref" href="#why-not-both" role="doc-backlink">Why Not Both?</a></h3> <p>Consider the following ‘normal’ code:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">):</span> <span class="o">...</span> </pre></div> </div> <p>Note that we have symbols for both the value of the thing (<code class="docutils literal notranslate"><span class="pre">x</span></code>) and the type of the thing (<code class="docutils literal notranslate"><span class="pre">int</span></code>). Why can’t we do the same with axes? For example, with an imaginary syntax, we could write:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">f</span><span class="p">(</span><span class="n">array</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">TimeValue</span><span class="p">:</span> <span class="n">TimeType</span><span class="p">]):</span> <span class="o">...</span> </pre></div> </div> <p>This would allow us to access the axis size (say, 32) through the symbol <code class="docutils literal notranslate"><span class="pre">TimeValue</span></code> <em>and</em> the type through the symbol <code class="docutils literal notranslate"><span class="pre">TypeType</span></code>.</p> <p>This might even be possible using existing syntax, through a second level of parameterisation:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">f</span><span class="p">(</span><span class="n">array</span><span class="p">:</span> <span class="n">array</span><span class="p">[</span><span class="n">TimeValue</span><span class="p">[</span><span class="n">TimeType</span><span class="p">]]):</span> <span class="o">..</span> </pre></div> </div> <p>However, we leave exploration of this approach to the future.</p> </section> </section> <section id="appendix-b-shaped-types-vs-named-axes"> <h2><a class="toc-backref" href="#appendix-b-shaped-types-vs-named-axes" role="doc-backlink">Appendix B: Shaped Types vs Named Axes</a></h2> <p>An issue related to those addressed by this PEP concerns axis <em>selection</em>. For example, if we have an image stored in an array of shape 64×64x3, we might wish to convert to black-and-white by computing the mean over the third axis, <code class="docutils literal notranslate"><span class="pre">mean(image,</span> <span class="pre">axis=2)</span></code>. Unfortunately, the simple typo <code class="docutils literal notranslate"><span class="pre">axis=1</span></code> is difficult to spot and will produce a result that means something completely different (all while likely allowing the program to keep on running, resulting in a bug that is serious but silent).</p> <p>In response, some libraries have implemented so-called ‘named tensors’ (in this context, ‘tensor’ is synonymous with ‘array’), in which axes are selected not by index but by label - e.g. <code class="docutils literal notranslate"><span class="pre">mean(image,</span> <span class="pre">axis='channels')</span></code>.</p> <p>A question we are often asked about this PEP is: why not just use named tensors? The answer is that we consider the named tensors approach insufficient, for two main reasons:</p> <ul class="simple"> <li><strong>Static checking</strong> of shape correctness is not possible. As mentioned in <a class="reference internal" href="#motivation">Motivation</a>, this is a highly desirable feature in machine learning code where iteration times are slow by default.</li> <li><strong>Interface documentation</strong> is still not possible with this approach. If a function should <em>only</em> be willing to take array arguments that have image-like shapes, this cannot be stipulated with named tensors.</li> </ul> <p>Additionally, there’s the issue of <strong>poor uptake</strong>. At the time of writing, named tensors have only been implemented in a small number of numerical computing libraries. Possible explanations for this include difficulty of implementation (the whole API must be modified to allow selection by axis name instead of index), and lack of usefulness due to the fact that axis ordering conventions are often strong enough that axis names provide little benefit (e.g. when working with images, 3D tensors are basically <em>always</em> height × width × channels). However, ultimately we are still uncertain why this is the case.</p> <p>Can the named tensors approach be combined with the approach we advocate for in this PEP? We’re not sure. One area of overlap is that in some contexts, we could do, say:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Image</span><span class="p">:</span> <span class="n">Array</span><span class="p">[</span><span class="n">Height</span><span class="p">,</span> <span class="n">Width</span><span class="p">,</span> <span class="n">Channels</span><span class="p">]</span> <span class="n">im</span><span class="p">:</span> <span class="n">Image</span> <span class="n">mean</span><span class="p">(</span><span class="n">im</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="n">Image</span><span class="o">.</span><span class="n">axes</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="n">Channels</span><span class="p">)</span> </pre></div> </div> <p>Ideally, we might write something like <code class="docutils literal notranslate"><span class="pre">im:</span> <span class="pre">Array[Height=64,</span> <span class="pre">Width=64,</span> <span class="pre">Channels=3]</span></code> - but this won’t be possible in the short term, due to the rejection of <a class="pep reference internal" href="../pep-0637/" title="PEP 637 – Support for indexing with keyword arguments">PEP 637</a>. In any case, our attitude towards this is mostly “Wait and see what happens before taking any further steps”.</p> </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="batch" role="doc-footnote"> <dt class="label" id="batch">[<a href="#id2">1</a>]</dt> <dd>‘Batch’ is machine learning parlance for ‘a number of’.</aside> <aside class="footnote brackets" id="array" role="doc-footnote"> <dt class="label" id="array">[<a href="#id3">2</a>]</dt> <dd>We use the term ‘array’ to refer to a matrix with an arbitrary number of dimensions. In NumPy, the corresponding class is the <code class="docutils literal notranslate"><span class="pre">ndarray</span></code>; in TensorFlow, the <code class="docutils literal notranslate"><span class="pre">Tensor</span></code>; and so on.</aside> <aside class="footnote brackets" id="timebatch" role="doc-footnote"> <dt class="label" id="timebatch">[<a href="#id4">3</a>]</dt> <dd>If the shape begins with ‘batch × time’, then <code class="docutils literal notranslate"><span class="pre">videos_batch[0][1]</span></code> would select the second frame of the first video. If the shape begins with ‘time × batch’, then <code class="docutils literal notranslate"><span class="pre">videos_batch[1][0]</span></code> would select the same frame.</aside> </aside> </section> <section id="endorsements"> <h2><a class="toc-backref" href="#endorsements" role="doc-backlink">Endorsements</a></h2> <p>Variadic generics have a wide range of uses. For the fraction of that range involving numerical computing, how likely is it that relevant libraries will actually make use of the features proposed in this PEP?</p> <p>We reached out to a number of people with this question, and received the following endorsements.</p> <p>From <strong>Stephan Hoyer</strong>, member of the NumPy Steering Council: <a class="footnote-reference brackets" href="#stephan-endorsement" id="id13">[14]</a></p> <blockquote> <div>I just wanted to thank Matthew & Pradeep for writing this PEP and for clarifications to the broader context of <a class="pep reference internal" href="../pep-0646/" title="PEP 646 – Variadic Generics">PEP 646</a> for array typing in <a class="reference external" href="https://github.com/python/peps/pull/1904">https://github.com/python/peps/pull/1904</a>.<p>As someone who is heavily involved in the Python numerical computing community (e.g., NumPy, JAX, Xarray), but who is not so familiar with the details of Python’s type system, it is reassuring to see that a broad range of use-cases related to type checking of named axes & shapes have been considered, and could build upon the infrastructure in this PEP.</p> <p>Type checking for shapes is something the NumPy community is very interested in – there are more thumbs up on the relevant issue on NumPy’s GitHub than any others (<a class="reference external" href="https://github.com/numpy/numpy/issues/7370">https://github.com/numpy/numpy/issues/7370</a>) and we recently added a “typing” module that is under active development.</p> <p>It will certainly require experimentation to figure out the best ways to use type checking for ndarrays, but this PEP looks like an excellent foundation for such work.</p> </div></blockquote> <p>From <strong>Bas van Beek</strong>, who has worked on preliminary support for shape-generics in NumPy:</p> <blockquote> <div>I very much share Stephan’s opinion here and look forward to integrating the new <a class="pep reference internal" href="../pep-0646/" title="PEP 646 – Variadic Generics">PEP 646</a> variadics into numpy.<p>In the context of numpy (and tensor typing general): the typing of array shapes is a fairly complicated subject and the introduction of variadics will likely play in big role in laying its foundation, as it allows for the expression of both dimensioability as well as basic shape manipulation.</p> <p>All in all, I’m very interested in where both <a class="pep reference internal" href="../pep-0646/" title="PEP 646 – Variadic Generics">PEP 646</a> and future PEPs will take us and look forward to further developments.</p> </div></blockquote> <p>From <strong>Dan Moldovan</strong>, a Senior Software Engineer on the TensorFlow Dev Team and author of the TensorFlow RFC, <a class="reference external" href="https://github.com/tensorflow/community/pull/208">TensorFlow Canonical Type System</a>: <a class="footnote-reference brackets" href="#dan-endorsement" id="id14">[15]</a></p> <blockquote> <div>I’d be interested in using this the mechanisms defined in this PEP to define rank-generic Tensor types in TensorFlow, which are important in specifying <code class="docutils literal notranslate"><span class="pre">tf.function</span></code> signatures in a Pythonic way, using type annotations (rather than the custom <code class="docutils literal notranslate"><span class="pre">input_signature</span></code> mechanism we have today - see this issue: <a class="reference external" href="https://github.com/tensorflow/tensorflow/issues/31579">https://github.com/tensorflow/tensorflow/issues/31579</a>). Variadic generics are among the last few missing pieces to create an elegant set of type definitions for tensors and shapes.</div></blockquote> <p>(For the sake of transparency - we also reached out to folks from a third popular numerical computing library, PyTorch, but did <em>not</em> receive a statement of endorsement from them. Our understanding is that although they are interested in some of the same issues - e.g. static shape inference - they are currently focusing on enabling this through a DSL rather than the Python type system.)</p> </section> <section id="acknowledgements"> <h2><a class="toc-backref" href="#acknowledgements" role="doc-backlink">Acknowledgements</a></h2> <p>Thank you to <strong>Alfonso Castaño</strong>, <strong>Antoine Pitrou</strong>, <strong>Bas v.B.</strong>, <strong>David Foster</strong>, <strong>Dimitris Vardoulakis</strong>, <strong>Eric Traut</strong>, <strong>Guido van Rossum</strong>, <strong>Jia Chen</strong>, <strong>Lucio Fernandez-Arjona</strong>, <strong>Nikita Sobolev</strong>, <strong>Peilonrayz</strong>, <strong>Rebecca Chen</strong>, <strong>Sergei Lebedev</strong>, and <strong>Vladimir Mikulik</strong> for helpful feedback and suggestions on drafts of this PEP.</p> <p>Thank you especially to <strong>Lucio</strong> for suggesting the star syntax (which has made multiple aspects of this proposal much more concise and intuitive), and to <strong>Stephan Hoyer</strong> and <strong>Dan Moldovan</strong> for their endorsements.</p> </section> <section id="resources"> <h2><a class="toc-backref" href="#resources" role="doc-backlink">Resources</a></h2> <p>Discussions on variadic generics in Python started in 2016 with Issue 193 on the python/typing GitHub repository <a class="footnote-reference brackets" href="#typing193" id="id15">[4]</a>.</p> <p>Inspired by this discussion, <strong>Ivan Levkivskyi</strong> made a concrete proposal at PyCon 2019, summarised in notes on ‘Type system improvements’ <a class="footnote-reference brackets" href="#type-improvements" id="id16">[5]</a> and ‘Static typing of Python numeric stack’ <a class="footnote-reference brackets" href="#numeric-stack" id="id17">[6]</a>.</p> <p>Expanding on these ideas, <strong>Mark Mendoza</strong> and <strong>Vincent Siles</strong> gave a presentation on ‘Variadic Type Variables for Decorators and Tensors’ <a class="footnote-reference brackets" href="#variadic-type-variables" id="id18">[8]</a> at the 2019 Python Typing Summit.</p> <p>Discussion over how type substitution in generic aliases should behave took place in <a class="reference external" href="https://github.com/python/cpython/issues/91162">cpython#91162</a>.</p> </section> <section id="references"> <h2><a class="toc-backref" href="#references" role="doc-backlink">References</a></h2> <aside class="footnote-list brackets"> <aside class="footnote brackets" id="typing193" role="doc-footnote"> <dt class="label" id="typing193">[4]<em> (<a href='#id1'>1</a>, <a href='#id15'>2</a>) </em></dt> <dd>Python typing issue #193: <a class="reference external" href="https://github.com/python/typing/issues/193">https://github.com/python/typing/issues/193</a></aside> <aside class="footnote brackets" id="type-improvements" role="doc-footnote"> <dt class="label" id="type-improvements">[<a href="#id16">5</a>]</dt> <dd>Ivan Levkivskyi, ‘Type system improvements’, PyCon 2019: <a class="reference external" href="https://paper.dropbox.com/doc/Type-system-improvements-HHOkniMG9WcCgS0LzXZAe">https://paper.dropbox.com/doc/Type-system-improvements-HHOkniMG9WcCgS0LzXZAe</a></aside> <aside class="footnote brackets" id="numeric-stack" role="doc-footnote"> <dt class="label" id="numeric-stack">[6]<em> (<a href='#id5'>1</a>, <a href='#id17'>2</a>) </em></dt> <dd>Ivan Levkivskyi, ‘Static typing of Python numeric stack’, PyCon 2019: <a class="reference external" href="https://paper.dropbox.com/doc/Static-typing-of-Python-numeric-stack-summary-6ZQzTkgN6e0oXko8fEWwN">https://paper.dropbox.com/doc/Static-typing-of-Python-numeric-stack-summary-6ZQzTkgN6e0oXko8fEWwN</a></aside> <aside class="footnote brackets" id="typing-ideas" role="doc-footnote"> <dt class="label" id="typing-ideas">[<a href="#id6">7</a>]</dt> <dd>Stephan Hoyer, ‘Ideas for array shape typing in Python’: <a class="reference external" href="https://docs.google.com/document/d/1vpMse4c6DrWH5rq2tQSx3qwP_m_0lyn-Ij4WHqQqRHY/edit">https://docs.google.com/document/d/1vpMse4c6DrWH5rq2tQSx3qwP_m_0lyn-Ij4WHqQqRHY/edit</a></aside> <aside class="footnote brackets" id="variadic-type-variables" role="doc-footnote"> <dt class="label" id="variadic-type-variables">[<a href="#id18">8</a>]</dt> <dd>Mark Mendoza, ‘Variadic Type Variables for Decorators and Tensors’, Python Typing Summit 2019: <a class="reference external" href="https://github.com/facebook/pyre-check/blob/ae85c0c6e99e3bbfc92ec55104bfdc5b9b3097b2/docs/Variadic_Type_Variables_for_Decorators_and_Tensors.pdf">https://github.com/facebook/pyre-check/blob/ae85c0c6e99e3bbfc92ec55104bfdc5b9b3097b2/docs/Variadic_Type_Variables_for_Decorators_and_Tensors.pdf</a></aside> <aside class="footnote brackets" id="syntax-proposal" role="doc-footnote"> <dt class="label" id="syntax-proposal">[<a href="#id7">9</a>]</dt> <dd>Matthew Rahtz et al., ‘Shape annotation syntax proposal’: <a class="reference external" href="https://docs.google.com/document/d/1But-hjet8-djv519HEKvBN6Ik2lW3yu0ojZo6pG9osY/edit">https://docs.google.com/document/d/1But-hjet8-djv519HEKvBN6Ik2lW3yu0ojZo6pG9osY/edit</a></aside> <aside class="footnote brackets" id="arbitrary-len" role="doc-footnote"> <dt class="label" id="arbitrary-len">[<a href="#id8">10</a>]</dt> <dd>Discussion on Python typing-sig mailing list: <a class="reference external" href="https://mail.python.org/archives/list/typing-sig@python.org/thread/SQVTQYWIOI4TIO7NNBTFFWFMSMS2TA4J/">https://mail.python.org/archives/list/typing-sig@python.org/thread/SQVTQYWIOI4TIO7NNBTFFWFMSMS2TA4J/</a></aside> <aside class="footnote brackets" id="tsanley" role="doc-footnote"> <dt class="label" id="tsanley">[<a href="#id10">11</a>]</dt> <dd>tsanley: <a class="reference external" href="https://github.com/ofnote/tsanley">https://github.com/ofnote/tsanley</a></aside> <aside class="footnote brackets" id="pycontracts" role="doc-footnote"> <dt class="label" id="pycontracts">[<a href="#id11">12</a>]</dt> <dd>PyContracts: <a class="reference external" href="https://github.com/AndreaCensi/contracts">https://github.com/AndreaCensi/contracts</a></aside> <aside class="footnote brackets" id="shapeguard" role="doc-footnote"> <dt class="label" id="shapeguard">[<a href="#id9">13</a>]</dt> <dd>ShapeGuard: <a class="reference external" href="https://github.com/Qwlouse/shapeguard">https://github.com/Qwlouse/shapeguard</a></aside> </aside> <aside class="footnote-list brackets"> <aside class="footnote brackets" id="stephan-endorsement" role="doc-footnote"> <dt class="label" id="stephan-endorsement">[<a href="#id13">14</a>]</dt> <dd><a class="reference external" href="https://mail.python.org/archives/list/python-dev@python.org/message/UDM7Y6HLHQBKXQEBIBD5ZLB5XNPDZDXV/">https://mail.python.org/archives/list/python-dev@python.org/message/UDM7Y6HLHQBKXQEBIBD5ZLB5XNPDZDXV/</a></aside> <aside class="footnote brackets" id="dan-endorsement" role="doc-footnote"> <dt class="label" id="dan-endorsement">[<a href="#id14">15</a>]</dt> <dd><a class="reference external" href="https://mail.python.org/archives/list/python-dev@python.org/message/HTCARTYYCHETAMHB6OVRNR5EW5T2CP4J/">https://mail.python.org/archives/list/python-dev@python.org/message/HTCARTYYCHETAMHB6OVRNR5EW5T2CP4J/</a></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-0646.rst">https://github.com/python/peps/blob/main/peps/pep-0646.rst</a></p> <p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0646.rst">2025-02-01 08:55:40 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="#acceptance">Acceptance</a></li> <li><a class="reference internal" href="#motivation">Motivation</a></li> <li><a class="reference internal" href="#summary-examples">Summary Examples</a></li> <li><a class="reference internal" href="#specification">Specification</a><ul> <li><a class="reference internal" href="#type-variable-tuples">Type Variable Tuples</a><ul> <li><a class="reference internal" href="#using-type-variable-tuples-in-generic-classes">Using Type Variable Tuples in Generic Classes</a></li> <li><a class="reference internal" href="#using-type-variable-tuples-in-functions">Using Type Variable Tuples in Functions</a></li> <li><a class="reference internal" href="#type-variable-tuples-must-always-be-unpacked">Type Variable Tuples Must Always be Unpacked</a></li> <li><a class="reference internal" href="#unpack-for-backwards-compatibility"><code class="docutils literal notranslate"><span class="pre">Unpack</span></code> for Backwards Compatibility</a></li> <li><a class="reference internal" href="#variance-type-constraints-and-type-bounds-not-yet-supported">Variance, Type Constraints and Type Bounds: Not (Yet) Supported</a></li> <li><a class="reference internal" href="#type-variable-tuple-equality">Type Variable Tuple Equality</a></li> <li><a class="reference internal" href="#multiple-type-variable-tuples-not-allowed">Multiple Type Variable Tuples: Not Allowed</a></li> </ul> </li> <li><a class="reference internal" href="#type-concatenation">Type Concatenation</a></li> <li><a class="reference internal" href="#unpacking-tuple-types">Unpacking Tuple Types</a><ul> <li><a class="reference internal" href="#unpacking-concrete-tuple-types">Unpacking Concrete Tuple Types</a></li> <li><a class="reference internal" href="#unpacking-unbounded-tuple-types">Unpacking Unbounded Tuple Types</a></li> <li><a class="reference internal" href="#multiple-unpackings-in-a-tuple-not-allowed">Multiple Unpackings in a Tuple: Not Allowed</a></li> </ul> </li> <li><a class="reference internal" href="#args-as-a-type-variable-tuple"><code class="docutils literal notranslate"><span class="pre">*args</span></code> as a Type Variable Tuple</a></li> <li><a class="reference internal" href="#type-variable-tuples-with-callable">Type Variable Tuples with <code class="docutils literal notranslate"><span class="pre">Callable</span></code></a></li> <li><a class="reference internal" href="#behaviour-when-type-parameters-are-not-specified">Behaviour when Type Parameters are not Specified</a></li> <li><a class="reference internal" href="#aliases">Aliases</a></li> <li><a class="reference internal" href="#substitution-in-aliases">Substitution in Aliases</a><ul> <li><a class="reference internal" href="#type-arguments-can-be-variadic">Type Arguments can be Variadic</a></li> <li><a class="reference internal" href="#variadic-arguments-require-variadic-aliases">Variadic Arguments Require Variadic Aliases</a></li> <li><a class="reference internal" href="#aliases-with-both-typevars-and-typevartuples">Aliases with Both TypeVars and TypeVarTuples</a></li> <li><a class="reference internal" href="#splitting-arbitrary-length-tuples">Splitting Arbitrary-Length Tuples</a></li> <li><a class="reference internal" href="#typevartuples-cannot-be-split">TypeVarTuples Cannot be Split</a></li> </ul> </li> <li><a class="reference internal" href="#overloads-for-accessing-individual-types">Overloads for Accessing Individual Types</a></li> </ul> </li> <li><a class="reference internal" href="#rationale-and-rejected-ideas">Rationale and Rejected Ideas</a><ul> <li><a class="reference internal" href="#shape-arithmetic">Shape Arithmetic</a></li> <li><a class="reference internal" href="#supporting-variadicity-through-aliases">Supporting Variadicity Through Aliases</a></li> <li><a class="reference internal" href="#construction-of-typevartuple">Construction of <code class="docutils literal notranslate"><span class="pre">TypeVarTuple</span></code></a></li> <li><a class="reference internal" href="#unspecified-type-parameters-tuple-vs-typevartuple">Unspecified Type Parameters: Tuple vs TypeVarTuple</a></li> </ul> </li> <li><a class="reference internal" href="#alternatives">Alternatives</a></li> <li><a class="reference internal" href="#grammar-changes">Grammar Changes</a><ul> <li><a class="reference internal" href="#change-1-star-expressions-in-indexes">Change 1: Star Expressions in Indexes</a><ul> <li><a class="reference internal" href="#typevartuple-implementation">TypeVarTuple Implementation</a></li> <li><a class="reference internal" href="#implications">Implications</a></li> </ul> </li> <li><a class="reference internal" href="#change-2-args-as-a-typevartuple">Change 2: <code class="docutils literal notranslate"><span class="pre">*args</span></code> as a TypeVarTuple</a><ul> <li><a class="reference internal" href="#id12">Implications</a></li> </ul> </li> <li><a class="reference internal" href="#alternatives-why-not-just-use-unpack">Alternatives (Why Not Just Use <code class="docutils literal notranslate"><span class="pre">Unpack</span></code>?)</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="#appendix-a-shape-typing-use-cases">Appendix A: Shape Typing Use Cases</a><ul> <li><a class="reference internal" href="#use-case-1-specifying-shape-values">Use Case 1: Specifying Shape Values</a></li> <li><a class="reference internal" href="#use-case-2-specifying-shape-semantics">Use Case 2: Specifying Shape Semantics</a></li> <li><a class="reference internal" href="#discussion">Discussion</a></li> <li><a class="reference internal" href="#why-not-both">Why Not Both?</a></li> </ul> </li> <li><a class="reference internal" href="#appendix-b-shaped-types-vs-named-axes">Appendix B: Shaped Types vs Named Axes</a></li> <li><a class="reference internal" href="#footnotes">Footnotes</a></li> <li><a class="reference internal" href="#endorsements">Endorsements</a></li> <li><a class="reference internal" href="#acknowledgements">Acknowledgements</a></li> <li><a class="reference internal" href="#resources">Resources</a></li> <li><a class="reference internal" href="#references">References</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-0646.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>