CINXE.COM
PEP 612 – Parameter Specification Variables | 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 612 – Parameter Specification Variables | peps.python.org</title> <link rel="shortcut icon" href="../_static/py.png"> <link rel="canonical" href="https://peps.python.org/pep-0612/"> <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 612 – Parameter Specification Variables | peps.python.org'> <meta property="og:description" content="There currently are two ways to specify the type of a callable, the Callable[[int, str], bool] syntax defined in PEP 484, and callback protocols from PEP 544. Neither of these support forwarding the parameter types of one callable over to another calla..."> <meta property="og:type" content="website"> <meta property="og:url" content="https://peps.python.org/pep-0612/"> <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="There currently are two ways to specify the type of a callable, the Callable[[int, str], bool] syntax defined in PEP 484, and callback protocols from PEP 544. Neither of these support forwarding the parameter types of one callable over to another calla..."> <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 612</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 612 – Parameter Specification Variables</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></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">BDFL-Delegate<span class="colon">:</span></dt> <dd class="field-odd">Guido van Rossum <guido at python.org></dd> <dt class="field-even">Discussions-To<span class="colon">:</span></dt> <dd class="field-even"><a class="reference external" href="https://mail.python.org/archives/list/typing-sig@python.org/">Typing-SIG list</a></dd> <dt class="field-odd">Status<span class="colon">:</span></dt> <dd class="field-odd"><abbr title="Accepted and implementation complete, or no longer active">Final</abbr></dd> <dt class="field-even">Type<span class="colon">:</span></dt> <dd class="field-even"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd> <dt class="field-odd">Topic<span class="colon">:</span></dt> <dd class="field-odd"><a class="reference external" href="../topic/typing/">Typing</a></dd> <dt class="field-even">Created<span class="colon">:</span></dt> <dd class="field-even">18-Dec-2019</dd> <dt class="field-odd">Python-Version<span class="colon">:</span></dt> <dd class="field-odd">3.10</dd> <dt class="field-even">Post-History<span class="colon">:</span></dt> <dd class="field-even">18-Dec-2019, 13-Jul-2020</dd> </dl> <hr class="docutils" /> <section id="contents"> <details><summary>Table of Contents</summary><ul class="simple"> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#motivation">Motivation</a></li> <li><a class="reference internal" href="#specification">Specification</a><ul> <li><a class="reference internal" href="#paramspec-variables"><code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code> Variables</a><ul> <li><a class="reference internal" href="#declaration">Declaration</a></li> <li><a class="reference internal" href="#valid-use-locations">Valid use locations</a></li> <li><a class="reference internal" href="#user-defined-generic-classes">User-Defined Generic Classes</a></li> <li><a class="reference internal" href="#semantics">Semantics</a></li> </ul> </li> <li><a class="reference internal" href="#the-components-of-a-paramspec">The components of a <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code></a><ul> <li><a class="reference internal" href="#id1">Valid use locations</a></li> <li><a class="reference internal" href="#id2">Semantics</a></li> </ul> </li> </ul> </li> <li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li> <li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li> <li><a class="reference internal" href="#rejected-alternatives">Rejected Alternatives</a><ul> <li><a class="reference internal" href="#using-list-variadics-and-map-variadics">Using List Variadics and Map Variadics</a></li> <li><a class="reference internal" href="#defining-parametersof">Defining ParametersOf</a></li> <li><a class="reference internal" href="#concatenating-keyword-parameters">Concatenating Keyword Parameters</a></li> <li><a class="reference internal" href="#naming-this-a-parameterspecification">Naming this a <code class="docutils literal notranslate"><span class="pre">ParameterSpecification</span></code></a></li> <li><a class="reference internal" href="#naming-this-an-argspec">Naming this an <code class="docutils literal notranslate"><span class="pre">ArgSpec</span></code></a></li> </ul> </li> <li><a class="reference internal" href="#acknowledgements">Acknowledgements</a></li> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> </details></section> <div class="pep-banner canonical-typing-spec sticky-banner admonition attention"> <p class="admonition-title">Attention</p> <p>This PEP is a historical document: see <a class="reference external" href="https://typing.readthedocs.io/en/latest/spec/generics.html#paramspec" title="(in typing)"><span>ParamSpec</span></a> and <a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.ParamSpec" title="(in Python v3.13)"><code class="xref py py-class docutils literal notranslate"><span class="pre">typing.ParamSpec</span></code></a> for up-to-date specs and documentation. Canonical typing specs are maintained at the <a class="reference external" href="https://typing.readthedocs.io/en/latest/spec/">typing specs site</a>; runtime typing behaviour is described in the CPython documentation.</p> <p class="close-button">×</p> <p>See the <a class="reference external" href="https://typing.readthedocs.io/en/latest/spec/meta.html">typing specification update process</a> for how to propose changes to the typing spec.</p> </div> <section id="abstract"> <h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2> <p>There currently are two ways to specify the type of a callable, the <code class="docutils literal notranslate"><span class="pre">Callable[[int,</span> <span class="pre">str],</span> <span class="pre">bool]</span></code> syntax defined in <a class="pep reference internal" href="../pep-0484/" title="PEP 484 – Type Hints">PEP 484</a>, and callback protocols from <a class="pep reference internal" href="../pep-0544/#callback-protocols" title="PEP 544 – Protocols: Structural subtyping (static duck typing) § Callback protocols">PEP 544</a>. Neither of these support forwarding the parameter types of one callable over to another callable, making it difficult to annotate function decorators. This PEP proposes <code class="docutils literal notranslate"><span class="pre">typing.ParamSpec</span></code> and <code class="docutils literal notranslate"><span class="pre">typing.Concatenate</span></code> to support expressing these kinds of relationships.</p> </section> <section id="motivation"> <h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2> <p>The existing standards for annotating higher order functions don’t give us the tools to annotate the following common decorator pattern satisfactorily:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Awaitable</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">TypeVar</span> <span class="n">R</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">"R"</span><span class="p">)</span> <span class="k">def</span> <span class="nf">add_logging</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="o">...</span><span class="p">,</span> <span class="n">R</span><span class="p">])</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="n">Awaitable</span><span class="p">[</span><span class="n">R</span><span class="p">]]:</span> <span class="k">async</span> <span class="k">def</span> <span class="nf">inner</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="nb">object</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-></span> <span class="n">R</span><span class="p">:</span> <span class="k">await</span> <span class="n">log_to_database</span><span class="p">()</span> <span class="k">return</span> <span class="n">f</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">kwargs</span><span class="p">)</span> <span class="k">return</span> <span class="n">inner</span> <span class="nd">@add_logging</span> <span class="k">def</span> <span class="nf">takes_int_str</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">7</span> <span class="k">await</span> <span class="n">takes_int_str</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s2">"A"</span><span class="p">)</span> <span class="k">await</span> <span class="n">takes_int_str</span><span class="p">(</span><span class="s2">"B"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="c1"># fails at runtime</span> </pre></div> </div> <p><code class="docutils literal notranslate"><span class="pre">add_logging</span></code>, a decorator which logs before each entry into the decorated function, is an instance of the Python idiom of one function passing all arguments given to it over to another function. This is done through the combination of the <code class="docutils literal notranslate"><span class="pre">*args</span></code> and <code class="docutils literal notranslate"><span class="pre">**kwargs</span></code> features in both parameters and in arguments. When one defines a function (like <code class="docutils literal notranslate"><span class="pre">inner</span></code>) that takes <code class="docutils literal notranslate"><span class="pre">(*args,</span> <span class="pre">**kwargs)</span></code> and goes on to call another function with <code class="docutils literal notranslate"><span class="pre">(*args,</span> <span class="pre">**kwargs)</span></code>, the wrapping function can only be safely called in all of the ways that the wrapped function could be safely called. To type this decorator, we’d like to be able to place a dependency between the parameters of the callable <code class="docutils literal notranslate"><span class="pre">f</span></code> and the parameters of the returned function. <a class="pep reference internal" href="../pep-0484/" title="PEP 484 – Type Hints">PEP 484</a> supports dependencies between single types, as in <code class="docutils literal notranslate"><span class="pre">def</span> <span class="pre">append(l:</span> <span class="pre">typing.List[T],</span> <span class="pre">e:</span> <span class="pre">T)</span> <span class="pre">-></span> <span class="pre">typing.List[T]:</span> <span class="pre">...</span></code>, but there is no existing way to do so with a complicated entity like the parameters of a function.</p> <p>Due to the limitations of the status quo, the <code class="docutils literal notranslate"><span class="pre">add_logging</span></code> example will type check but will fail at runtime. <code class="docutils literal notranslate"><span class="pre">inner</span></code> will pass the string “B” into <code class="docutils literal notranslate"><span class="pre">takes_int_str</span></code>, which will try to add 7 to it, triggering a type error. This was not caught by the type checker because the decorated <code class="docutils literal notranslate"><span class="pre">takes_int_str</span></code> was given the type <code class="docutils literal notranslate"><span class="pre">Callable[...,</span> <span class="pre">Awaitable[int]]</span></code> (an ellipsis in place of parameter types is specified to mean that we do no validation on arguments).</p> <p>Without the ability to define dependencies between the parameters of different callable types, there is no way, at present, to make <code class="docutils literal notranslate"><span class="pre">add_logging</span></code> compatible with all functions, while still preserving the enforcement of the parameters of the decorated function.</p> <p>With the addition of the <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code> variables proposed by this PEP, we can rewrite the previous example in a way that keeps the flexibility of the decorator and the parameter enforcement of the decorated function.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Awaitable</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">ParamSpec</span><span class="p">,</span> <span class="n">TypeVar</span> <span class="n">P</span> <span class="o">=</span> <span class="n">ParamSpec</span><span class="p">(</span><span class="s2">"P"</span><span class="p">)</span> <span class="n">R</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">"R"</span><span class="p">)</span> <span class="k">def</span> <span class="nf">add_logging</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="n">P</span><span class="p">,</span> <span class="n">R</span><span class="p">])</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="n">Awaitable</span><span class="p">[</span><span class="n">R</span><span class="p">]]:</span> <span class="k">async</span> <span class="k">def</span> <span class="nf">inner</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="n">R</span><span class="p">:</span> <span class="k">await</span> <span class="n">log_to_database</span><span class="p">()</span> <span class="k">return</span> <span class="n">f</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">kwargs</span><span class="p">)</span> <span class="k">return</span> <span class="n">inner</span> <span class="nd">@add_logging</span> <span class="k">def</span> <span class="nf">takes_int_str</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">7</span> <span class="k">await</span> <span class="n">takes_int_str</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s2">"A"</span><span class="p">)</span> <span class="c1"># Accepted</span> <span class="k">await</span> <span class="n">takes_int_str</span><span class="p">(</span><span class="s2">"B"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="c1"># Correctly rejected by the type checker</span> </pre></div> </div> <p>Another common decorator pattern that has previously been impossible to type is the practice of adding or removing arguments from the decorated function. For example:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Request</span><span class="p">:</span> <span class="o">...</span> <span class="k">def</span> <span class="nf">with_request</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="o">...</span><span class="p">,</span> <span class="n">R</span><span class="p">])</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="n">R</span><span class="p">]:</span> <span class="k">def</span> <span class="nf">inner</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="nb">object</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-></span> <span class="n">R</span><span class="p">:</span> <span class="k">return</span> <span class="n">f</span><span class="p">(</span><span class="n">Request</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">kwargs</span><span class="p">)</span> <span class="k">return</span> <span class="n">inner</span> <span class="nd">@with_request</span> <span class="k">def</span> <span class="nf">takes_int_str</span><span class="p">(</span><span class="n">request</span><span class="p">:</span> <span class="n">Request</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="n">y</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="c1"># use request</span> <span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">7</span> <span class="n">takes_int_str</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s2">"A"</span><span class="p">)</span> <span class="n">takes_int_str</span><span class="p">(</span><span class="s2">"B"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="c1"># fails at runtime</span> </pre></div> </div> <p>With the addition of the <code class="docutils literal notranslate"><span class="pre">Concatenate</span></code> operator from this PEP, we can even type this more complex decorator.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Concatenate</span> <span class="k">def</span> <span class="nf">with_request</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="n">Concatenate</span><span class="p">[</span><span class="n">Request</span><span class="p">,</span> <span class="n">P</span><span class="p">],</span> <span class="n">R</span><span class="p">])</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="n">R</span><span class="p">]:</span> <span class="k">def</span> <span class="nf">inner</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="n">R</span><span class="p">:</span> <span class="k">return</span> <span class="n">f</span><span class="p">(</span><span class="n">Request</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">kwargs</span><span class="p">)</span> <span class="k">return</span> <span class="n">inner</span> <span class="nd">@with_request</span> <span class="k">def</span> <span class="nf">takes_int_str</span><span class="p">(</span><span class="n">request</span><span class="p">:</span> <span class="n">Request</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="n">y</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="c1"># use request</span> <span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">7</span> <span class="n">takes_int_str</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s2">"A"</span><span class="p">)</span> <span class="c1"># Accepted</span> <span class="n">takes_int_str</span><span class="p">(</span><span class="s2">"B"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="c1"># Correctly rejected by the type checker</span> </pre></div> </div> </section> <section id="specification"> <h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2> <section id="paramspec-variables"> <h3><a class="toc-backref" href="#paramspec-variables" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code> Variables</a></h3> <section id="declaration"> <h4><a class="toc-backref" href="#declaration" role="doc-backlink">Declaration</a></h4> <p>A parameter specification variable is defined in a similar manner to how a normal type variable is defined with <code class="docutils literal notranslate"><span class="pre">typing.TypeVar</span></code>.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">ParamSpec</span> <span class="n">P</span> <span class="o">=</span> <span class="n">ParamSpec</span><span class="p">(</span><span class="s2">"P"</span><span class="p">)</span> <span class="c1"># Accepted</span> <span class="n">P</span> <span class="o">=</span> <span class="n">ParamSpec</span><span class="p">(</span><span class="s2">"WrongName"</span><span class="p">)</span> <span class="c1"># Rejected because P =/= WrongName</span> </pre></div> </div> <p>The runtime should accept <code class="docutils literal notranslate"><span class="pre">bound</span></code>s and <code class="docutils literal notranslate"><span class="pre">covariant</span></code> and <code class="docutils literal notranslate"><span class="pre">contravariant</span></code> arguments in the declaration just as <code class="docutils literal notranslate"><span class="pre">typing.TypeVar</span></code> does, but for now we will defer the standardization of the semantics of those options to a later PEP.</p> </section> <section id="valid-use-locations"> <h4><a class="toc-backref" href="#valid-use-locations" role="doc-backlink">Valid use locations</a></h4> <p>Previously only a list of parameter arguments (<code class="docutils literal notranslate"><span class="pre">[A,</span> <span class="pre">B,</span> <span class="pre">C]</span></code>) or an ellipsis (signifying “undefined parameters”) were acceptable as the first “argument” to <code class="docutils literal notranslate"><span class="pre">typing.Callable</span></code> . We now augment that with two new options: a parameter specification variable (<code class="docutils literal notranslate"><span class="pre">Callable[P,</span> <span class="pre">int]</span></code>) or a concatenation on a parameter specification variable (<code class="docutils literal notranslate"><span class="pre">Callable[Concatenate[int,</span> <span class="pre">P],</span> <span class="pre">int]</span></code>).</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nb">callable</span> <span class="p">:</span><span class="o">:=</span> <span class="n">Callable</span> <span class="s2">"["</span> <span class="n">parameters_expression</span><span class="p">,</span> <span class="n">type_expression</span> <span class="s2">"]"</span> <span class="n">parameters_expression</span> <span class="p">:</span><span class="o">:=</span> <span class="o">|</span> <span class="s2">"..."</span> <span class="o">|</span> <span class="s2">"["</span> <span class="p">[</span> <span class="n">type_expression</span> <span class="p">(</span><span class="s2">","</span> <span class="n">type_expression</span><span class="p">)</span><span class="o">*</span> <span class="p">]</span> <span class="s2">"]"</span> <span class="o">|</span> <span class="n">parameter_specification_variable</span> <span class="o">|</span> <span class="n">concatenate</span> <span class="s2">"["</span> <span class="n">type_expression</span> <span class="p">(</span><span class="s2">","</span> <span class="n">type_expression</span><span class="p">)</span><span class="o">*</span> <span class="s2">","</span> <span class="n">parameter_specification_variable</span> <span class="s2">"]"</span> </pre></div> </div> <p>where <code class="docutils literal notranslate"><span class="pre">parameter_specification_variable</span></code> is a <code class="docutils literal notranslate"><span class="pre">typing.ParamSpec</span></code> variable, declared in the manner as defined above, and <code class="docutils literal notranslate"><span class="pre">concatenate</span></code> is <code class="docutils literal notranslate"><span class="pre">typing.Concatenate</span></code>.</p> <p>As before, <code class="docutils literal notranslate"><span class="pre">parameters_expression</span></code>s by themselves are not acceptable in places where a type is expected</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">P</span><span class="p">)</span> <span class="o">-></span> <span class="n">P</span><span class="p">:</span> <span class="o">...</span> <span class="c1"># Rejected</span> <span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Concatenate</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">P</span><span class="p">])</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="o">...</span> <span class="c1"># Rejected</span> <span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">typing</span><span class="o">.</span><span class="n">List</span><span class="p">[</span><span class="n">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="c1"># Rejected</span> <span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">x</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="nb">str</span><span class="p">],</span> <span class="n">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="c1"># Rejected</span> </pre></div> </div> </section> <section id="user-defined-generic-classes"> <h4><a class="toc-backref" href="#user-defined-generic-classes" role="doc-backlink">User-Defined Generic Classes</a></h4> <p>Just as defining a class as inheriting from <code class="docutils literal notranslate"><span class="pre">Generic[T]</span></code> makes a class generic for a single parameter (when <code class="docutils literal notranslate"><span class="pre">T</span></code> is a <code class="docutils literal notranslate"><span class="pre">TypeVar</span></code>), defining a class as inheriting from <code class="docutils literal notranslate"><span class="pre">Generic[P]</span></code> makes a class generic on <code class="docutils literal notranslate"><span class="pre">parameters_expression</span></code>s (when <code class="docutils literal notranslate"><span class="pre">P</span></code> is a <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code>).</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">"T"</span><span class="p">)</span> <span class="n">P_2</span> <span class="o">=</span> <span class="n">ParamSpec</span><span class="p">(</span><span class="s2">"P_2"</span><span class="p">)</span> <span class="k">class</span> <span class="nc">X</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">,</span> <span class="n">P</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="n">P</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">T</span> <span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">X</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">P_2</span><span class="p">])</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="o">...</span> <span class="c1"># Accepted</span> <span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">X</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Concatenate</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">P_2</span><span class="p">]])</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="o">...</span> <span class="c1"># Accepted</span> <span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">X</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">bool</span><span class="p">]])</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="o">...</span> <span class="c1"># Accepted</span> <span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">X</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="nb">str</span><span class="p">:</span> <span class="o">...</span> <span class="c1"># Accepted</span> <span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">X</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">int</span><span class="p">])</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="o">...</span> <span class="c1"># Rejected</span> </pre></div> </div> <p>By the rules defined above, spelling a concrete instance of a class generic with respect to only a single <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code> would require unsightly double brackets. For aesthetic purposes we allow these to be omitted.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Z</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">P</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="n">P</span><span class="p">,</span> <span class="nb">int</span><span class="p">]</span> <span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Z</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="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="o">...</span> <span class="c1"># Accepted</span> <span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Z</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="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="o">...</span> <span class="c1"># Equivalent</span> <span class="c1"># Both Z[[int, str, bool]] and Z[int, str, bool] express this:</span> <span class="k">class</span> <span class="nc">Z_instantiated</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="nb">str</span><span class="p">,</span> <span class="nb">bool</span><span class="p">],</span> <span class="nb">int</span><span class="p">]</span> </pre></div> </div> </section> <section id="semantics"> <h4><a class="toc-backref" href="#semantics" role="doc-backlink">Semantics</a></h4> <p>The inference rules for the return type of a function invocation whose signature contains a <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code> variable are analogous to those around evaluating ones with <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="k">def</span> <span class="nf">changes_return_type_to_str</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="nb">int</span><span class="p">])</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span> <span class="o">...</span> <span class="k">def</span> <span class="nf">returns_int</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nb">bool</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="o">...</span> <span class="n">f</span> <span class="o">=</span> <span class="n">changes_return_type_to_str</span><span class="p">(</span><span class="n">returns_int</span><span class="p">)</span> <span class="c1"># f should have the type:</span> <span class="c1"># (a: str, b: bool) -> str</span> <span class="n">f</span><span class="p">(</span><span class="s2">"A"</span><span class="p">,</span> <span class="kc">True</span><span class="p">)</span> <span class="c1"># Accepted</span> <span class="n">f</span><span class="p">(</span><span class="n">a</span><span class="o">=</span><span class="s2">"A"</span><span class="p">,</span> <span class="n">b</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> <span class="c1"># Accepted</span> <span class="n">f</span><span class="p">(</span><span class="s2">"A"</span><span class="p">,</span> <span class="s2">"A"</span><span class="p">)</span> <span class="c1"># Rejected</span> <span class="n">expects_str</span><span class="p">(</span><span class="n">f</span><span class="p">(</span><span class="s2">"A"</span><span class="p">,</span> <span class="kc">True</span><span class="p">))</span> <span class="c1"># Accepted</span> <span class="n">expects_int</span><span class="p">(</span><span class="n">f</span><span class="p">(</span><span class="s2">"A"</span><span class="p">,</span> <span class="kc">True</span><span class="p">))</span> <span class="c1"># Rejected</span> </pre></div> </div> <p>Just as with traditional <code class="docutils literal notranslate"><span class="pre">TypeVars</span></code>, a user may include the same <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code> multiple times in the arguments of the same function, to indicate a dependency between multiple arguments. In these cases a type checker may choose to solve to a common behavioral supertype (i.e. a set of parameters for which all of the valid calls are valid in both of the subtypes), but is not obligated to do so.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">P</span> <span class="o">=</span> <span class="n">ParamSpec</span><span class="p">(</span><span class="s2">"P"</span><span class="p">)</span> <span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</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">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="nb">int</span><span class="p">])</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="nb">bool</span><span class="p">]:</span> <span class="o">...</span> <span class="k">def</span> <span class="nf">x_y</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="n">y</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="o">...</span> <span class="k">def</span> <span class="nf">y_x</span><span class="p">(</span><span class="n">y</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="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="o">...</span> <span class="n">foo</span><span class="p">(</span><span class="n">x_y</span><span class="p">,</span> <span class="n">x_y</span><span class="p">)</span> <span class="c1"># Should return (x: int, y: str) -> bool</span> <span class="n">foo</span><span class="p">(</span><span class="n">x_y</span><span class="p">,</span> <span class="n">y_x</span><span class="p">)</span> <span class="c1"># Could return (__a: int, __b: str) -> bool</span> <span class="c1"># This works because both callables have types that are</span> <span class="c1"># behavioral subtypes of Callable[[int, str], int]</span> <span class="k">def</span> <span class="nf">keyword_only_x</span><span class="p">(</span><span class="o">*</span><span class="p">,</span> <span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="o">...</span> <span class="k">def</span> <span class="nf">keyword_only_y</span><span class="p">(</span><span class="o">*</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="o">...</span> <span class="n">foo</span><span class="p">(</span><span class="n">keyword_only_x</span><span class="p">,</span> <span class="n">keyword_only_y</span><span class="p">)</span> <span class="c1"># Rejected</span> </pre></div> </div> <p>The constructors of user-defined classes generic on <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code>s should be evaluated in the same way.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">U</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">"U"</span><span class="p">)</span> <span class="k">class</span> <span class="nc">Y</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">U</span><span class="p">,</span> <span class="n">P</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="n">P</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="n">prop</span><span class="p">:</span> <span class="n">U</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">f</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="nb">str</span><span class="p">],</span> <span class="n">prop</span><span class="p">:</span> <span class="n">U</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">f</span> <span class="o">=</span> <span class="n">f</span> <span class="bp">self</span><span class="o">.</span><span class="n">prop</span> <span class="o">=</span> <span class="n">prop</span> <span class="k">def</span> <span class="nf">a</span><span class="p">(</span><span class="n">q</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-></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">a</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="c1"># Should resolve to Y[(q: int), int]</span> <span class="n">Y</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">f</span> <span class="c1"># Should resolve to (q: int) -> str</span> </pre></div> </div> <p>The semantics of <code class="docutils literal notranslate"><span class="pre">Concatenate[X,</span> <span class="pre">Y,</span> <span class="pre">P]</span></code> are that it represents the parameters represented by <code class="docutils literal notranslate"><span class="pre">P</span></code> with two positional-only parameters prepended. This means that we can use it to represent higher order functions that add, remove or transform a finite number of parameters of a callable.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">bar</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="nb">bool</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="o">...</span> <span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="nb">int</span><span class="p">])</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">Concatenate</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">P</span><span class="p">],</span> <span class="nb">bool</span><span class="p">]:</span> <span class="o">...</span> <span class="n">add</span><span class="p">(</span><span class="n">bar</span><span class="p">)</span> <span class="c1"># Should return (__a: str, x: int, *args: bool) -> bool</span> <span class="k">def</span> <span class="nf">remove</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="n">Concatenate</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">P</span><span class="p">],</span> <span class="nb">int</span><span class="p">])</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="nb">bool</span><span class="p">]:</span> <span class="o">...</span> <span class="n">remove</span><span class="p">(</span><span class="n">bar</span><span class="p">)</span> <span class="c1"># Should return (*args: bool) -> bool</span> <span class="k">def</span> <span class="nf">transform</span><span class="p">(</span> <span class="n">x</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="n">Concatenate</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">P</span><span class="p">],</span> <span class="nb">int</span><span class="p">]</span> <span class="p">)</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">Concatenate</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">P</span><span class="p">],</span> <span class="nb">bool</span><span class="p">]:</span> <span class="o">...</span> <span class="n">transform</span><span class="p">(</span><span class="n">bar</span><span class="p">)</span> <span class="c1"># Should return (__a: str, *args: bool) -> bool</span> </pre></div> </div> <p>This also means that while any function that returns an <code class="docutils literal notranslate"><span class="pre">R</span></code> can satisfy <code class="docutils literal notranslate"><span class="pre">typing.Callable[P,</span> <span class="pre">R]</span></code>, only functions that can be called positionally in their first position with a <code class="docutils literal notranslate"><span class="pre">X</span></code> can satisfy <code class="docutils literal notranslate"><span class="pre">typing.Callable[Concatenate[X,</span> <span class="pre">P],</span> <span class="pre">R]</span></code>.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">expects_int_first</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="n">Concatenate</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">P</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> <span class="nd">@expects_int_first</span> <span class="c1"># Rejected</span> <span class="k">def</span> <span class="nf">one</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="o">...</span> <span class="nd">@expects_int_first</span> <span class="c1"># Rejected</span> <span class="k">def</span> <span class="nf">two</span><span class="p">(</span><span class="o">*</span><span class="p">,</span> <span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="o">...</span> <span class="nd">@expects_int_first</span> <span class="c1"># Rejected</span> <span class="k">def</span> <span class="nf">three</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="o">...</span> <span class="nd">@expects_int_first</span> <span class="c1"># Accepted</span> <span class="k">def</span> <span class="nf">four</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="nb">int</span><span class="p">:</span> <span class="o">...</span> </pre></div> </div> <p>There are still some classes of decorators still not supported with these features:</p> <ul class="simple"> <li>those that add/remove/change a <strong>variable</strong> number of parameters (for example, <code class="docutils literal notranslate"><span class="pre">functools.partial</span></code> will remain untypable even after this PEP)</li> <li>those that add/remove/change keyword-only parameters (See <a class="reference internal" href="#concatenating-keyword-parameters">Concatenating Keyword Parameters</a> for more details).</li> </ul> </section> </section> <section id="the-components-of-a-paramspec"> <h3><a class="toc-backref" href="#the-components-of-a-paramspec" role="doc-backlink">The components of a <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code></a></h3> <p>A <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code> captures both positional and keyword accessible parameters, but there unfortunately is no object in the runtime that captures both of these together. Instead, we are forced to separate them into <code class="docutils literal notranslate"><span class="pre">*args</span></code> and <code class="docutils literal notranslate"><span class="pre">**kwargs</span></code>, respectively. This means we need to be able to split apart a single <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code> into these two components, and then bring them back together into a call. To do this, we introduce <code class="docutils literal notranslate"><span class="pre">P.args</span></code> to represent the tuple of positional arguments in a given call and <code class="docutils literal notranslate"><span class="pre">P.kwargs</span></code> to represent the corresponding <code class="docutils literal notranslate"><span class="pre">Mapping</span></code> of keywords to values.</p> <section id="id1"> <h4><a class="toc-backref" href="#id1" role="doc-backlink">Valid use locations</a></h4> <p>These “properties” can only be used as the annotated types for <code class="docutils literal notranslate"><span class="pre">*args</span></code> and <code class="docutils literal notranslate"><span class="pre">**kwargs</span></code>, accessed from a ParamSpec already in scope.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">puts_p_into_scope</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="n">P</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="k">def</span> <span class="nf">inner</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="c1"># Accepted</span> <span class="k">pass</span> <span class="k">def</span> <span class="nf">mixed_up</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="c1"># Rejected</span> <span class="k">pass</span> <span class="k">def</span> <span class="nf">misplaced</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="c1"># Rejected</span> <span class="k">pass</span> <span class="k">def</span> <span class="nf">out_of_scope</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="c1"># Rejected</span> <span class="k">pass</span> </pre></div> </div> <p>Furthermore, because the default kind of parameter in Python (<code class="docutils literal notranslate"><span class="pre">(x:</span> <span class="pre">int)</span></code>) may be addressed both positionally and through its name, two valid invocations of a <code class="docutils literal notranslate"><span class="pre">(*args:</span> <span class="pre">P.args,</span> <span class="pre">**kwargs:</span> <span class="pre">P.kwargs)</span></code> function may give different partitions of the same set of parameters. Therefore, we need to make sure that these special types are only brought into the world together, and are used together, so that our usage is valid for all possible partitions.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">puts_p_into_scope</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="n">P</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="n">stored_args</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">args</span> <span class="c1"># Rejected</span> <span class="n">stored_kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span> <span class="c1"># Rejected</span> <span class="k">def</span> <span class="nf">just_args</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="c1"># Rejected</span> <span class="k">pass</span> <span class="k">def</span> <span class="nf">just_kwargs</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="c1"># Rejected</span> <span class="k">pass</span> </pre></div> </div> </section> <section id="id2"> <h4><a class="toc-backref" href="#id2" role="doc-backlink">Semantics</a></h4> <p>With those requirements met, we can now take advantage of the unique properties afforded to us by this set up:</p> <ul class="simple"> <li>Inside the function, <code class="docutils literal notranslate"><span class="pre">args</span></code> has the type <code class="docutils literal notranslate"><span class="pre">P.args</span></code>, not <code class="docutils literal notranslate"><span class="pre">Tuple[P.args,</span> <span class="pre">...]</span></code> as would be with a normal annotation (and likewise with the <code class="docutils literal notranslate"><span class="pre">**kwargs</span></code>)<ul> <li>This special case is necessary to encapsulate the heterogeneous contents of the <code class="docutils literal notranslate"><span class="pre">args</span></code>/<code class="docutils literal notranslate"><span class="pre">kwargs</span></code> of a given call, which cannot be expressed by an indefinite tuple/dictionary type.</li> </ul> </li> <li>A function of type <code class="docutils literal notranslate"><span class="pre">Callable[P,</span> <span class="pre">R]</span></code> can be called with <code class="docutils literal notranslate"><span class="pre">(*args,</span> <span class="pre">**kwargs)</span></code> if and only if <code class="docutils literal notranslate"><span class="pre">args</span></code> has the type <code class="docutils literal notranslate"><span class="pre">P.args</span></code> and <code class="docutils literal notranslate"><span class="pre">kwargs</span></code> has the type <code class="docutils literal notranslate"><span class="pre">P.kwargs</span></code>, and that those types both originated from the same function declaration.</li> <li>A function declared as <code class="docutils literal notranslate"><span class="pre">def</span> <span class="pre">inner(*args:</span> <span class="pre">P.args,</span> <span class="pre">**kwargs:</span> <span class="pre">P.kwargs)</span> <span class="pre">-></span> <span class="pre">X</span></code> has type <code class="docutils literal notranslate"><span class="pre">Callable[P,</span> <span class="pre">X]</span></code>.</li> </ul> <p>With these three properties, we now have the ability to fully type check parameter preserving decorators.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">decorator</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="n">P</span><span class="p">,</span> <span class="nb">int</span><span class="p">])</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="kc">None</span><span class="p">]:</span> <span class="k">def</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">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="n">f</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">kwargs</span><span class="p">)</span> <span class="c1"># Accepted, should resolve to int</span> <span class="n">f</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">args</span><span class="p">)</span> <span class="c1"># Rejected</span> <span class="n">f</span><span class="p">(</span><span class="mi">1</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">kwargs</span><span class="p">)</span> <span class="c1"># Rejected</span> <span class="k">return</span> <span class="n">foo</span> <span class="c1"># Accepted</span> </pre></div> </div> <p>To extend this to include <code class="docutils literal notranslate"><span class="pre">Concatenate</span></code>, we declare the following properties:</p> <ul class="simple"> <li>A function of type <code class="docutils literal notranslate"><span class="pre">Callable[Concatenate[A,</span> <span class="pre">B,</span> <span class="pre">P],</span> <span class="pre">R]</span></code> can only be called with <code class="docutils literal notranslate"><span class="pre">(a,</span> <span class="pre">b,</span> <span class="pre">*args,</span> <span class="pre">**kwargs)</span></code> when <code class="docutils literal notranslate"><span class="pre">args</span></code> and <code class="docutils literal notranslate"><span class="pre">kwargs</span></code> are the respective components of <code class="docutils literal notranslate"><span class="pre">P</span></code>, <code class="docutils literal notranslate"><span class="pre">a</span></code> is of type <code class="docutils literal notranslate"><span class="pre">A</span></code> and <code class="docutils literal notranslate"><span class="pre">b</span></code> is of type <code class="docutils literal notranslate"><span class="pre">B</span></code>.</li> <li>A function declared as <code class="docutils literal notranslate"><span class="pre">def</span> <span class="pre">inner(a:</span> <span class="pre">A,</span> <span class="pre">b:</span> <span class="pre">B,</span> <span class="pre">*args:</span> <span class="pre">P.args,</span> <span class="pre">**kwargs:</span> <span class="pre">P.kwargs)</span> <span class="pre">-></span> <span class="pre">R</span></code> has type <code class="docutils literal notranslate"><span class="pre">Callable[Concatenate[A,</span> <span class="pre">B,</span> <span class="pre">P],</span> <span class="pre">R]</span></code>. Placing keyword-only parameters between the <code class="docutils literal notranslate"><span class="pre">*args</span></code> and <code class="docutils literal notranslate"><span class="pre">**kwargs</span></code> is forbidden.</li> </ul> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">add</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="n">P</span><span class="p">,</span> <span class="nb">int</span><span class="p">])</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">Concatenate</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">P</span><span class="p">],</span> <span class="kc">None</span><span class="p">]:</span> <span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">s</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="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="c1"># Accepted</span> <span class="k">pass</span> <span class="k">def</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="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="c1"># Rejected</span> <span class="k">pass</span> <span class="k">return</span> <span class="n">foo</span> <span class="c1"># Accepted</span> <span class="k">def</span> <span class="nf">remove</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="n">Concatenate</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">P</span><span class="p">],</span> <span class="nb">int</span><span class="p">])</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="kc">None</span><span class="p">]:</span> <span class="k">def</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">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="n">f</span><span class="p">(</span><span class="mi">1</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">kwargs</span><span class="p">)</span> <span class="c1"># Accepted</span> <span class="n">f</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="c1"># Rejected</span> <span class="n">f</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">kwargs</span><span class="p">)</span> <span class="c1"># Rejected</span> <span class="k">return</span> <span class="n">foo</span> </pre></div> </div> <p>Note that the names of the parameters preceding the <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code> components are not mentioned in the resulting <code class="docutils literal notranslate"><span class="pre">Concatenate</span></code>. This means that these parameters can not be addressed via a named argument:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">outer</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="n">P</span><span class="p">,</span> <span class="kc">None</span><span class="p">])</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="kc">None</span><span class="p">]:</span> <span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="n">f</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">kwargs</span><span class="p">)</span> <span class="k">def</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="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="n">foo</span><span class="p">(</span><span class="mi">1</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">kwargs</span><span class="p">)</span> <span class="c1"># Accepted</span> <span class="n">foo</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="c1"># Rejected</span> <span class="k">return</span> <span class="n">bar</span> </pre></div> </div> <p id="above">This is not an implementation convenience, but a soundness requirement. If we were to allow that second calling style, then the following snippet would be problematic.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@outer</span> <span class="k">def</span> <span class="nf">problem</span><span class="p">(</span><span class="o">*</span><span class="p">,</span> <span class="n">x</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="k">pass</span> <span class="n">problem</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="s2">"uh-oh"</span><span class="p">)</span> </pre></div> </div> <p>Inside of <code class="docutils literal notranslate"><span class="pre">bar</span></code>, we would get <code class="docutils literal notranslate"><span class="pre">TypeError:</span> <span class="pre">foo()</span> <span class="pre">got</span> <span class="pre">multiple</span> <span class="pre">values</span> <span class="pre">for</span> <span class="pre">argument</span> <span class="pre">'x'</span></code>. Requiring these concatenated arguments to be addressed positionally avoids this kind of problem, and simplifies the syntax for spelling these types. Note that this also why we have to reject signatures of the form <code class="docutils literal notranslate"><span class="pre">(*args:</span> <span class="pre">P.args,</span> <span class="pre">s:</span> <span class="pre">str,</span> <span class="pre">**kwargs:</span> <span class="pre">P.kwargs)</span></code> (See <a class="reference internal" href="#concatenating-keyword-parameters">Concatenating Keyword Parameters</a> for more details).</p> <p>If one of these prepended positional parameters contains a free <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code>, we consider that variable in scope for the purposes of extracting the components of that <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code>. That allows us to spell things like this:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">twice</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="n">P</span><span class="p">,</span> <span class="nb">int</span><span class="p">],</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="k">return</span> <span class="n">f</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">kwargs</span><span class="p">)</span> <span class="o">+</span> <span class="n">f</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">kwargs</span><span class="p">)</span> </pre></div> </div> <p>The type of <code class="docutils literal notranslate"><span class="pre">twice</span></code> in the above example is <code class="docutils literal notranslate"><span class="pre">Callable[Concatenate[Callable[P,</span> <span class="pre">int],</span> <span class="pre">P],</span> <span class="pre">int]</span></code>, where <code class="docutils literal notranslate"><span class="pre">P</span></code> is bound by the outer <code class="docutils literal notranslate"><span class="pre">Callable</span></code>. This has the following semantics:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">a_int_b_str</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="k">pass</span> <span class="n">twice</span><span class="p">(</span><span class="n">a_int_b_str</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">"A"</span><span class="p">)</span> <span class="c1"># Accepted</span> <span class="n">twice</span><span class="p">(</span><span class="n">a_int_b_str</span><span class="p">,</span> <span class="n">b</span><span class="o">=</span><span class="s2">"A"</span><span class="p">,</span> <span class="n">a</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># Accepted</span> <span class="n">twice</span><span class="p">(</span><span class="n">a_int_b_str</span><span class="p">,</span> <span class="s2">"A"</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="c1"># Rejected</span> </pre></div> </div> </section> </section> </section> <section id="backwards-compatibility"> <h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2> <p>The only changes necessary to existing features in <code class="docutils literal notranslate"><span class="pre">typing</span></code> is allowing these <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code> and <code class="docutils literal notranslate"><span class="pre">Concatenate</span></code> objects to be the first parameter to <code class="docutils literal notranslate"><span class="pre">Callable</span></code> and to be a parameter to <code class="docutils literal notranslate"><span class="pre">Generic</span></code>. Currently <code class="docutils literal notranslate"><span class="pre">Callable</span></code> expects a list of types there and <code class="docutils literal notranslate"><span class="pre">Generic</span></code> expects single types, so they are currently mutually exclusive. Otherwise, existing code that doesn’t reference the new interfaces will be unaffected.</p> </section> <section id="reference-implementation"> <h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2> <p>The <a class="reference external" href="https://pyre-check.org/">Pyre</a> type checker supports all of the behavior described above. A reference implementation of the runtime components needed for those uses is provided in the <code class="docutils literal notranslate"><span class="pre">pyre_extensions</span></code> module. A reference implementation for CPython can be found <a class="reference external" href="https://github.com/python/cpython/pull/23702">here</a>.</p> </section> <section id="rejected-alternatives"> <h2><a class="toc-backref" href="#rejected-alternatives" role="doc-backlink">Rejected Alternatives</a></h2> <section id="using-list-variadics-and-map-variadics"> <h3><a class="toc-backref" href="#using-list-variadics-and-map-variadics" role="doc-backlink">Using List Variadics and Map Variadics</a></h3> <p>We considered just trying to make something like this with a callback protocol which was parameterized on a list-type variadic, and a map-type variadic like so:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span>R = typing.TypeVar(“R”) Tpositionals = ... Tkeywords = ... class BetterCallable(typing.Protocol[Tpositionals, Tkeywords, R]): def __call__(*args: Tpositionals, **kwargs: Tkeywords) -> R: ... </pre></div> </div> <p>However, there are some problems with trying to come up with a consistent solution for those type variables for a given callable. This problem comes up with even the simplest of callables:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span>def simple(x: int) -> None: ... simple <: BetterCallable[[int], [], None] simple <: BetterCallable[[], {“x”: int}, None] BetterCallable[[int], [], None] </: BetterCallable[[], {“x”: int}, None] </pre></div> </div> <p>Any time where a type can implement a protocol in more than one way that aren’t mutually compatible, we can run into situations where we lose information. If we were to make a decorator using this protocol, we would have to pick one calling convention to prefer.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span> <span class="n">f</span><span class="p">:</span> <span class="n">BetterCallable</span><span class="p">[[</span><span class="n">Ts</span><span class="p">],</span> <span class="p">[</span><span class="n">Tmap</span><span class="p">],</span> <span class="nb">int</span><span class="p">],</span> <span class="p">)</span> <span class="o">-></span> <span class="n">BetterCallable</span><span class="p">[[</span><span class="n">Ts</span><span class="p">],</span> <span class="p">[</span><span class="n">Tmap</span><span class="p">],</span> <span class="nb">str</span><span class="p">]:</span> <span class="k">def</span> <span class="nf">decorated</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="n">kwargs</span><span class="p">:</span> <span class="n">Tmap</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="n">x</span> <span class="o">=</span> <span class="n">f</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">kwargs</span><span class="p">)</span> <span class="k">return</span> <span class="n">int_to_str</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="k">return</span> <span class="n">decorated</span> <span class="nd">@decorator</span> <span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="k">return</span> <span class="n">x</span> <span class="n">reveal_type</span><span class="p">(</span><span class="n">foo</span><span class="p">)</span> <span class="c1"># Option A: BetterCallable[[int], {}, str]</span> <span class="c1"># Option B: BetterCallable[[], {x: int}, str]</span> <span class="n">foo</span><span class="p">(</span><span class="mi">7</span><span class="p">)</span> <span class="c1"># fails under option B</span> <span class="n">foo</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">7</span><span class="p">)</span> <span class="c1"># fails under option A</span> </pre></div> </div> <p>The core problem here is that, by default, parameters in Python can either be called positionally or as a keyword argument. This means we really have three categories (positional-only, positional-or-keyword, keyword-only) we’re trying to jam into two categories. This is the same problem that we briefly mentioned when discussing <code class="docutils literal notranslate"><span class="pre">.args</span></code> and <code class="docutils literal notranslate"><span class="pre">.kwargs</span></code>. Fundamentally, in order to capture two categories when there are some things that can be in either category, we need a higher level primitive (<code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code>) to capture all three, and then split them out afterward.</p> </section> <section id="defining-parametersof"> <h3><a class="toc-backref" href="#defining-parametersof" role="doc-backlink">Defining ParametersOf</a></h3> <p>Another proposal we considered was defining <code class="docutils literal notranslate"><span class="pre">ParametersOf</span></code> and <code class="docutils literal notranslate"><span class="pre">ReturnType</span></code> operators which would operate on a domain of a newly defined <code class="docutils literal notranslate"><span class="pre">Function</span></code> type. <code class="docutils literal notranslate"><span class="pre">Function</span></code> would be callable with, and only with <code class="docutils literal notranslate"><span class="pre">ParametersOf[F]</span></code>. <code class="docutils literal notranslate"><span class="pre">ParametersOf</span></code> and <code class="docutils literal notranslate"><span class="pre">ReturnType</span></code> would only operate on type variables with precisely this bound. The combination of these three features could express everything that we can express with <code class="docutils literal notranslate"><span class="pre">ParamSpecs</span></code>.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">F</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">"F"</span><span class="p">,</span> <span class="n">bound</span><span class="o">=</span><span class="n">Function</span><span class="p">)</span> <span class="k">def</span> <span class="nf">no_change</span><span class="p">(</span><span class="n">f</span><span class="p">:</span> <span class="n">F</span><span class="p">)</span> <span class="o">-></span> <span class="n">F</span><span class="p">:</span> <span class="k">def</span> <span class="nf">inner</span><span class="p">(</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">ParametersOf</span><span class="p">[</span><span class="n">F</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">kwargs</span><span class="p">:</span> <span class="n">ParametersOf</span><span class="p">[</span><span class="n">F</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">ReturnType</span><span class="p">[</span><span class="n">F</span><span class="p">]:</span> <span class="k">return</span> <span class="n">f</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">kwargs</span><span class="p">)</span> <span class="k">return</span> <span class="n">inner</span> <span class="k">def</span> <span class="nf">wrapping</span><span class="p">(</span><span class="n">f</span><span class="p">:</span> <span class="n">F</span><span class="p">)</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">ParametersOf</span><span class="p">[</span><span class="n">F</span><span class="p">],</span> <span class="n">List</span><span class="p">[</span><span class="n">ReturnType</span><span class="p">[</span><span class="n">F</span><span class="p">]]]:</span> <span class="k">def</span> <span class="nf">inner</span><span class="p">(</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">ParametersOf</span><span class="p">[</span><span class="n">F</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">kwargs</span><span class="p">:</span> <span class="n">ParametersOf</span><span class="p">[</span><span class="n">F</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">List</span><span class="p">[</span><span class="n">ReturnType</span><span class="p">[</span><span class="n">F</span><span class="p">]]:</span> <span class="k">return</span> <span class="p">[</span><span class="n">f</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">kwargs</span><span class="p">)]</span> <span class="k">return</span> <span class="n">inner</span> <span class="k">def</span> <span class="nf">unwrapping</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="n">ParametersOf</span><span class="p">[</span><span class="n">F</span><span class="p">],</span> <span class="n">List</span><span class="p">[</span><span class="n">R</span><span class="p">]]</span> <span class="p">)</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">ParametersOf</span><span class="p">[</span><span class="n">F</span><span class="p">],</span> <span class="n">R</span><span class="p">]:</span> <span class="k">def</span> <span class="nf">inner</span><span class="p">(</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">ParametersOf</span><span class="p">[</span><span class="n">F</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">kwargs</span><span class="p">:</span> <span class="n">ParametersOf</span><span class="p">[</span><span class="n">F</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">R</span><span class="p">:</span> <span class="k">return</span> <span class="n">f</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">kwargs</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="k">return</span> <span class="n">inner</span> </pre></div> </div> <p>We decided to go with <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code>s over this approach for several reasons:</p> <ul class="simple"> <li>The footprint of this change would be larger, as we would need two new operators, and a new type, while <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code> just introduces a new variable.</li> <li>Python typing has so far has avoided supporting operators, whether user-defined or built-in, in favor of destructuring. Accordingly, <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code> based signatures look much more like existing Python.</li> <li>The lack of user-defined operators makes common patterns hard to spell. <code class="docutils literal notranslate"><span class="pre">unwrapping</span></code> is odd to read because <code class="docutils literal notranslate"><span class="pre">F</span></code> is not actually referring to any callable. It’s just being used as a container for the parameters we wish to propagate. It would read better if we could define an operator <code class="docutils literal notranslate"><span class="pre">RemoveList[List[X]]</span> <span class="pre">=</span> <span class="pre">X</span></code> and then <code class="docutils literal notranslate"><span class="pre">unwrapping</span></code> could take <code class="docutils literal notranslate"><span class="pre">F</span></code> and return <code class="docutils literal notranslate"><span class="pre">Callable[ParametersOf[F],</span> <span class="pre">RemoveList[ReturnType[F]]]</span></code>. Without that, we unfortunately get into a situation where we have to use a <code class="docutils literal notranslate"><span class="pre">Function</span></code>-variable as an improvised <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code>, in that we never actually bind the return type.</li> </ul> <p>In summary, between these two equivalently powerful syntaxes, <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code> fits much more naturally into the status quo.</p> </section> <section id="concatenating-keyword-parameters"> <h3><a class="toc-backref" href="#concatenating-keyword-parameters" role="doc-backlink">Concatenating Keyword Parameters</a></h3> <p>In principle the idea of concatenation as a means to modify a finite number of positional parameters could be expanded to include keyword parameters.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">add_n</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="n">P</span><span class="p">,</span> <span class="n">R</span><span class="p">])</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">Concatenate</span><span class="p">[(</span><span class="s2">"n"</span><span class="p">,</span> <span class="nb">int</span><span class="p">),</span> <span class="n">P</span><span class="p">],</span> <span class="n">R</span><span class="p">]:</span> <span class="k">def</span> <span class="nf">inner</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="n">R</span><span class="p">:</span> <span class="c1"># use n</span> <span class="k">return</span> <span class="n">f</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">kwargs</span><span class="p">)</span> <span class="k">return</span> <span class="n">inner</span> </pre></div> </div> <p>However, the key distinction is that while prepending positional-only parameters to a valid callable type always yields another valid callable type, the same cannot be said for adding keyword-only parameters. As alluded to <a class="reference internal" href="#above">above</a> , the issue is name collisions. The parameters <code class="docutils literal notranslate"><span class="pre">Concatenate[("n",</span> <span class="pre">int),</span> <span class="pre">P]</span></code> are only valid when <code class="docutils literal notranslate"><span class="pre">P</span></code> itself does not already have a parameter named <code class="docutils literal notranslate"><span class="pre">n</span></code>.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">innocent_wrapper</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="n">P</span><span class="p">,</span> <span class="n">R</span><span class="p">])</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="n">R</span><span class="p">]:</span> <span class="k">def</span> <span class="nf">inner</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></span> <span class="n">R</span><span class="p">:</span> <span class="n">added</span> <span class="o">=</span> <span class="n">add_n</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="k">return</span> <span class="n">added</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="n">n</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="k">return</span> <span class="n">inner</span> <span class="nd">@innocent_wrapper</span> <span class="k">def</span> <span class="nf">problem</span><span class="p">(</span><span class="n">n</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="k">pass</span> </pre></div> </div> <p>Calling <code class="docutils literal notranslate"><span class="pre">problem(2)</span></code> works fine, but calling <code class="docutils literal notranslate"><span class="pre">problem(n=2)</span></code> leads to a <code class="docutils literal notranslate"><span class="pre">TypeError:</span> <span class="pre">problem()</span> <span class="pre">got</span> <span class="pre">multiple</span> <span class="pre">values</span> <span class="pre">for</span> <span class="pre">argument</span> <span class="pre">'n'</span></code> from the call to <code class="docutils literal notranslate"><span class="pre">added</span></code> inside of <code class="docutils literal notranslate"><span class="pre">innocent_wrapper</span></code>.</p> <p>This kind of situation could be avoided, and this kind of decorator could be typed if we could reify the constraint that a set of parameters <strong>not</strong> contain a certain name, with something like:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">P_without_n</span> <span class="o">=</span> <span class="n">ParamSpec</span><span class="p">(</span><span class="s2">"P_without_n"</span><span class="p">,</span> <span class="n">banned_names</span><span class="o">=</span><span class="p">[</span><span class="s2">"n"</span><span class="p">])</span> <span class="k">def</span> <span class="nf">add_n</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="n">P_without_n</span><span class="p">,</span> <span class="n">R</span><span class="p">]</span> <span class="p">)</span> <span class="o">-></span> <span class="n">Callable</span><span class="p">[</span><span class="n">Concatenate</span><span class="p">[(</span><span class="s2">"n"</span><span class="p">,</span> <span class="nb">int</span><span class="p">),</span> <span class="n">P_without_n</span><span class="p">],</span> <span class="n">R</span><span class="p">]:</span> <span class="o">...</span> </pre></div> </div> <p>The call to <code class="docutils literal notranslate"><span class="pre">add_n</span></code> inside of <code class="docutils literal notranslate"><span class="pre">innocent_wrapper</span></code> could then be rejected since the callable was not guaranteed not to already have a parameter named <code class="docutils literal notranslate"><span class="pre">n</span></code>.</p> <p>However, enforcing these constraints would require enough additional implementation work that we judged this extension to be out of scope of this PEP. Fortunately the design of <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code>s are such that we can return to this idea later if there is sufficient demand.</p> </section> <section id="naming-this-a-parameterspecification"> <h3><a class="toc-backref" href="#naming-this-a-parameterspecification" role="doc-backlink">Naming this a <code class="docutils literal notranslate"><span class="pre">ParameterSpecification</span></code></a></h3> <p>We decided that ParameterSpecification was a little too long-winded for use here, and that this style of abbreviated name made it look more like TypeVar.</p> </section> <section id="naming-this-an-argspec"> <h3><a class="toc-backref" href="#naming-this-an-argspec" role="doc-backlink">Naming this an <code class="docutils literal notranslate"><span class="pre">ArgSpec</span></code></a></h3> <p>We think that calling this a ParamSpec is more correct than referring to it as an ArgSpec, since callables have parameters, which are distinct from the arguments which are passed to them in a given call site. A given binding for a ParamSpec is a set of function parameters, not a call-site’s arguments.</p> </section> </section> <section id="acknowledgements"> <h2><a class="toc-backref" href="#acknowledgements" role="doc-backlink">Acknowledgements</a></h2> <p>Thanks to all of the members of the Pyre team for their comments on early drafts of this PEP, and for their help with the reference implementation.</p> <p>Thanks are also due to the whole Python typing community for their early feedback on this idea at a Python typing meetup, leading directly to the much more compact <code class="docutils literal notranslate"><span class="pre">.args</span></code>/<code class="docutils literal notranslate"><span class="pre">.kwargs</span></code> syntax.</p> </section> <section id="copyright"> <h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2> <p>This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.</p> </section> </section> <hr class="docutils" /> <p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0612.rst">https://github.com/python/peps/blob/main/peps/pep-0612.rst</a></p> <p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0612.rst">2024-06-11 22:12:09 GMT</a></p> </article> <nav id="pep-sidebar"> <h2>Contents</h2> <ul> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#motivation">Motivation</a></li> <li><a class="reference internal" href="#specification">Specification</a><ul> <li><a class="reference internal" href="#paramspec-variables"><code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code> Variables</a><ul> <li><a class="reference internal" href="#declaration">Declaration</a></li> <li><a class="reference internal" href="#valid-use-locations">Valid use locations</a></li> <li><a class="reference internal" href="#user-defined-generic-classes">User-Defined Generic Classes</a></li> <li><a class="reference internal" href="#semantics">Semantics</a></li> </ul> </li> <li><a class="reference internal" href="#the-components-of-a-paramspec">The components of a <code class="docutils literal notranslate"><span class="pre">ParamSpec</span></code></a><ul> <li><a class="reference internal" href="#id1">Valid use locations</a></li> <li><a class="reference internal" href="#id2">Semantics</a></li> </ul> </li> </ul> </li> <li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li> <li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li> <li><a class="reference internal" href="#rejected-alternatives">Rejected Alternatives</a><ul> <li><a class="reference internal" href="#using-list-variadics-and-map-variadics">Using List Variadics and Map Variadics</a></li> <li><a class="reference internal" href="#defining-parametersof">Defining ParametersOf</a></li> <li><a class="reference internal" href="#concatenating-keyword-parameters">Concatenating Keyword Parameters</a></li> <li><a class="reference internal" href="#naming-this-a-parameterspecification">Naming this a <code class="docutils literal notranslate"><span class="pre">ParameterSpecification</span></code></a></li> <li><a class="reference internal" href="#naming-this-an-argspec">Naming this an <code class="docutils literal notranslate"><span class="pre">ArgSpec</span></code></a></li> </ul> </li> <li><a class="reference internal" href="#acknowledgements">Acknowledgements</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-0612.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>