CINXE.COM

PEP 671 – Syntax for late-bound function argument defaults | 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 671 – Syntax for late-bound function argument defaults | peps.python.org</title> <link rel="shortcut icon" href="../_static/py.png"> <link rel="canonical" href="https://peps.python.org/pep-0671/"> <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 671 – Syntax for late-bound function argument defaults | peps.python.org'> <meta property="og:description" content="Function parameters can have default values which are calculated during function definition and saved. This proposal introduces a new form of argument default, defined by an expression to be evaluated at function call time."> <meta property="og:type" content="website"> <meta property="og:url" content="https://peps.python.org/pep-0671/"> <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="Function parameters can have default values which are calculated during function definition and saved. This proposal introduces a new form of argument default, defined by an expression to be evaluated at function call time."> <meta name="theme-color" content="#3776ab"> </head> <body> <svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all"> <title>Following system colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="9"></circle> <path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path> </svg> </symbol> <symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all"> <title>Selected dark colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path> </svg> </symbol> <symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all"> <title>Selected light colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="5"></circle> <line x1="12" y1="1" x2="12" y2="3"></line> <line x1="12" y1="21" x2="12" y2="23"></line> <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> <line x1="1" y1="12" x2="3" y2="12"></line> <line x1="21" y1="12" x2="23" y2="12"></line> <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line> <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> </svg> </symbol> </svg> <script> document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto" </script> <section id="pep-page-section"> <header> <h1>Python Enhancement Proposals</h1> <ul class="breadcrumbs"> <li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> &raquo; </li> <li><a href="../pep-0000/">PEP Index</a> &raquo; </li> <li>PEP 671</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 671 – Syntax for late-bound function argument defaults</h1> <dl class="rfc2822 field-list simple"> <dt class="field-odd">Author<span class="colon">:</span></dt> <dd class="field-odd">Chris Angelico &lt;rosuav&#32;&#97;t&#32;gmail.com&gt;</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/python-ideas&#64;python.org/thread/UVOQEK7IRFSCBOH734T5GFJOEJXFCR6A/">Python-Ideas thread</a></dd> <dt class="field-odd">Status<span class="colon">:</span></dt> <dd class="field-odd"><abbr title="Proposal under active discussion and revision">Draft</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">Created<span class="colon">:</span></dt> <dd class="field-odd">24-Oct-2021</dd> <dt class="field-even">Python-Version<span class="colon">:</span></dt> <dd class="field-even">3.12</dd> <dt class="field-odd">Post-History<span class="colon">:</span></dt> <dd class="field-odd"><a class="reference external" href="https://mail.python.org/archives/list/python-ideas&#64;python.org/thread/KR2TMLPFR7NHDZCDOS6VTNWDKZQQJN3V/" title="Python-Ideas thread">24-Oct-2021</a>, <a class="reference external" href="https://mail.python.org/archives/list/python-ideas&#64;python.org/thread/UVOQEK7IRFSCBOH734T5GFJOEJXFCR6A/" title="Python-Ideas thread">01-Dec-2021</a></dd> </dl> <hr class="docutils" /> <section id="contents"> <details><summary>Table of Contents</summary><ul class="simple"> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#motivation">Motivation</a></li> <li><a class="reference internal" href="#specification">Specification</a><ul> <li><a class="reference internal" href="#rejected-choices-of-spelling">Rejected choices of spelling</a></li> </ul> </li> <li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li> <li><a class="reference internal" href="#interaction-with-other-proposals">Interaction with other proposals</a></li> <li><a class="reference internal" href="#implementation-details">Implementation details</a><ul> <li><a class="reference internal" href="#costs">Costs</a></li> <li><a class="reference internal" href="#backward-incompatibility">Backward incompatibility</a></li> </ul> </li> <li><a class="reference internal" href="#references">References</a></li> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> </details></section> <section id="abstract"> <h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2> <p>Function parameters can have default values which are calculated during function definition and saved. This proposal introduces a new form of argument default, defined by an expression to be evaluated at function call time.</p> </section> <section id="motivation"> <h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2> <p>Optional function arguments, if omitted, often have some sort of logical default value. When this value depends on other arguments, or needs to be reevaluated each function call, there is currently no clean way to state this in the function header.</p> <p>Currently-legal idioms for this include:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Very common: Use None and replace it in the function</span> <span class="k">def</span><span class="w"> </span><span class="nf">bisect_right</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">lo</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">hi</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="k">if</span> <span class="n">hi</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="n">hi</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="c1"># Also well known: Use a unique custom sentinel object</span> <span class="n">_USE_GLOBAL_DEFAULT</span> <span class="o">=</span> <span class="nb">object</span><span class="p">()</span> <span class="k">def</span><span class="w"> </span><span class="nf">connect</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="n">_USE_GLOBAL_DEFAULT</span><span class="p">):</span> <span class="k">if</span> <span class="n">timeout</span> <span class="ow">is</span> <span class="n">_USE_GLOBAL_DEFAULT</span><span class="p">:</span> <span class="n">timeout</span> <span class="o">=</span> <span class="n">default_timeout</span> <span class="c1"># Unusual: Accept star-args and then validate</span> <span class="k">def</span><span class="w"> </span><span class="nf">add_item</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="o">*</span><span class="n">optional_target</span><span class="p">):</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">optional_target</span><span class="p">:</span> <span class="n">target</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">else</span><span class="p">:</span> <span class="n">target</span> <span class="o">=</span> <span class="n">optional_target</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> </pre></div> </div> <p>In each form, <code class="docutils literal notranslate"><span class="pre">help(function)</span></code> fails to show the true default value. Each one has additional problems, too; using <code class="docutils literal notranslate"><span class="pre">None</span></code> is only valid if None is not itself a plausible function parameter, the custom sentinel requires a global constant; and use of star-args implies that more than one argument could be given.</p> </section> <section id="specification"> <h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2> <p>Function default arguments can be defined using the new <code class="docutils literal notranslate"><span class="pre">=&gt;</span></code> notation:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">bisect_right</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">lo</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">hi</span><span class="o">=&gt;</span><span class="nb">len</span><span class="p">(</span><span class="n">a</span><span class="p">),</span> <span class="o">*</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="k">def</span><span class="w"> </span><span class="nf">connect</span><span class="p">(</span><span class="n">timeout</span><span class="o">=&gt;</span><span class="n">default_timeout</span><span class="p">):</span> <span class="k">def</span><span class="w"> </span><span class="nf">add_item</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">target</span><span class="o">=&gt;</span><span class="p">[]):</span> <span class="k">def</span><span class="w"> </span><span class="nf">format_time</span><span class="p">(</span><span class="n">fmt</span><span class="p">,</span> <span class="n">time_t</span><span class="o">=&gt;</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()):</span> </pre></div> </div> <p>The expression is saved in its source code form for the purpose of inspection, and bytecode to evaluate it is prepended to the function’s body.</p> <p>Notably, the expression is evaluated in the function’s run-time scope, NOT the scope in which the function was defined (as are early-bound defaults). This allows the expression to refer to other arguments.</p> <p>Multiple late-bound arguments are evaluated from left to right, and can refer to previously-defined values. Order is defined by the function, regardless of the order in which keyword arguments may be passed.</p> <blockquote> <div>def prevref(word=”foo”, a=&gt;len(word), b=&gt;a//2): # Valid def selfref(spam=&gt;spam): # UnboundLocalError def spaminate(sausage=&gt;eggs + 1, eggs=&gt;sausage - 1): # Confusing, don’t do this def frob(n=&gt;len(items), items=[]): # See below</div></blockquote> <p>Evaluation order is left-to-right; however, implementations MAY choose to do so in two separate passes, first for all passed arguments and early-bound defaults, and then a second pass for late-bound defaults. Otherwise, all arguments will be assigned strictly left-to-right.</p> <section id="rejected-choices-of-spelling"> <h3><a class="toc-backref" href="#rejected-choices-of-spelling" role="doc-backlink">Rejected choices of spelling</a></h3> <p>While this document specifies a single syntax <code class="docutils literal notranslate"><span class="pre">name=&gt;expression</span></code>, alternate spellings are similarly plausible. The following spellings were considered:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span>def bisect(a, hi=&gt;len(a)): def bisect(a, hi:=len(a)): def bisect(a, hi?=len(a)): def bisect(a, @hi=len(a)): </pre></div> </div> <p>Since default arguments behave largely the same whether they’re early or late bound, the chosen syntax <code class="docutils literal notranslate"><span class="pre">hi=&gt;len(a)</span></code> is deliberately similar to the existing early-bind syntax.</p> <p>One reason for rejection of the <code class="docutils literal notranslate"><span class="pre">:=</span></code> syntax is its behaviour with annotations. Annotations go before the default, so in all syntax options, it must be unambiguous (both to the human and the parser) whether this is an annotation, a default, or both. The alternate syntax <code class="docutils literal notranslate"><span class="pre">target:=expr</span></code> runs the risk of being misinterpreted as <code class="docutils literal notranslate"><span class="pre">target:int=expr</span></code> with the annotation omitted in error, and may thus mask bugs. The chosen syntax <code class="docutils literal notranslate"><span class="pre">target=&gt;expr</span></code> does not have this problem.</p> </section> </section> <section id="how-to-teach-this"> <h2><a class="toc-backref" href="#how-to-teach-this" role="doc-backlink">How to Teach This</a></h2> <p>Early-bound default arguments should always be taught first, as they are the simpler and more efficient way to evaluate arguments. Building on them, late bound arguments are broadly equivalent to code at the top of the function:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">add_item</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">target</span><span class="o">=&gt;</span><span class="p">[]):</span> <span class="c1"># Equivalent pseudocode:</span> <span class="k">def</span><span class="w"> </span><span class="nf">add_item</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">target</span><span class="o">=&lt;</span><span class="n">OPTIONAL</span><span class="o">&gt;</span><span class="p">):</span> <span class="k">if</span> <span class="n">target</span> <span class="n">was</span> <span class="n">omitted</span><span class="p">:</span> <span class="n">target</span> <span class="o">=</span> <span class="p">[]</span> </pre></div> </div> <p>A simple rule of thumb is: “target=expression” is evaluated when the function is defined, and “target=&gt;expression” is evaluated when the function is called. Either way, if the argument is provided at call time, the default is ignored. While this does not completely explain all the subtleties, it is sufficient to cover the important distinction here (and the fact that they are similar).</p> </section> <section id="interaction-with-other-proposals"> <h2><a class="toc-backref" href="#interaction-with-other-proposals" role="doc-backlink">Interaction with other proposals</a></h2> <p><a class="pep reference internal" href="../pep-0661/" title="PEP 661 – Sentinel Values">PEP 661</a> attempts to solve one of the same problems as this does. It seeks to improve the documentation of sentinel values in default arguments, where this proposal seeks to remove the need for sentinels in many common cases. <a class="pep reference internal" href="../pep-0661/" title="PEP 661 – Sentinel Values">PEP 661</a> is able to improve documentation in arbitrarily complicated functions (it cites <code class="docutils literal notranslate"><span class="pre">traceback.print_exception</span></code> as its primary motivation, which has two arguments which must both-or-neither be specified); on the other hand, many of the common cases would no longer need sentinels if the true default could be defined by the function. Additionally, dedicated sentinel objects can be used as dictionary lookup keys, where <a class="pep reference internal" href="../pep-0671/" title="PEP 671 – Syntax for late-bound function argument defaults">PEP 671</a> does not apply.</p> <p>A generic system for deferred evaluation has been proposed at times (not to be confused with <a class="pep reference internal" href="../pep-0563/" title="PEP 563 – Postponed Evaluation of Annotations">PEP 563</a> and <a class="pep reference internal" href="../pep-0649/" title="PEP 649 – Deferred Evaluation Of Annotations Using Descriptors">PEP 649</a> which are specific to annotations). While it may seem, on the surface, that late-bound argument defaults are of a similar nature, they are in fact unrelated and orthogonal ideas, and both could be of value to the language. The acceptance or rejection of this proposal would not affect the viability of a deferred evaluation proposal, and vice versa. (A key difference between generalized deferred evaluation and argument defaults is that argument defaults will always and only be evaluated as the function begins executing, whereas deferred expressions would only be realized upon reference.)</p> </section> <section id="implementation-details"> <h2><a class="toc-backref" href="#implementation-details" role="doc-backlink">Implementation details</a></h2> <p>The following relates to the reference implementation, and is not necessarily part of the specification.</p> <p>Argument defaults (positional or keyword) have both their values, as already retained, and an extra piece of information. For positional arguments, the extras are stored in a tuple in <code class="docutils literal notranslate"><span class="pre">__defaults_extra__</span></code>, and for keyword-only, a dict in <code class="docutils literal notranslate"><span class="pre">__kwdefaults_extra__</span></code>. If this attribute is <code class="docutils literal notranslate"><span class="pre">None</span></code>, it is equivalent to having <code class="docutils literal notranslate"><span class="pre">None</span></code> for every argument default.</p> <p>For each parameter with a late-bound default, the special value <code class="docutils literal notranslate"><span class="pre">Ellipsis</span></code> is stored as the value placeholder, and the corresponding extra information needs to be queried. If it is <code class="docutils literal notranslate"><span class="pre">None</span></code>, then the default is indeed the value <code class="docutils literal notranslate"><span class="pre">Ellipsis</span></code>; otherwise, it is a descriptive string and the true value is calculated as the function begins.</p> <p>When a parameter with a late-bound default is omitted, the function will begin with the parameter unbound. The function begins by testing for each parameter with a late-bound default using a new opcode QUERY_FAST/QUERY_DEREF, and if unbound, evaluates the original expression. This opcode (available only for fast locals and closure variables) pushes True onto the stack if the given local has a value, and False if not - meaning that it pushes False if LOAD_FAST or LOAD_DEREF would raise UnboundLocalError, and True if it would succeed.</p> <p>Out-of-order variable references are permitted as long as the referent has a value from an argument or early-bound default.</p> <section id="costs"> <h3><a class="toc-backref" href="#costs" role="doc-backlink">Costs</a></h3> <p>When no late-bound argument defaults are used, the following costs should be all that are incurred:</p> <ul class="simple"> <li>Function objects require two additional pointers, which will be NULL</li> <li>Compiling code and constructing functions have additional flag checks</li> <li>Using <code class="docutils literal notranslate"><span class="pre">Ellipsis</span></code> as a default value will require run-time verification to see if late-bound defaults exist.</li> </ul> <p>These costs are expected to be minimal (on 64-bit Linux, this increases all function objects from 152 bytes to 168), with virtually no run-time cost when late-bound defaults are not used.</p> </section> <section id="backward-incompatibility"> <h3><a class="toc-backref" href="#backward-incompatibility" role="doc-backlink">Backward incompatibility</a></h3> <p>Where late-bound defaults are not used, behaviour should be identical. Care should be taken if Ellipsis is found, as it may not represent itself, but beyond that, tools should see existing code unchanged.</p> </section> </section> <section id="references"> <h2><a class="toc-backref" href="#references" role="doc-backlink">References</a></h2> <p><a class="reference external" href="https://github.com/rosuav/cpython/tree/pep-671">https://github.com/rosuav/cpython/tree/pep-671</a></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-0671.rst">https://github.com/python/peps/blob/main/peps/pep-0671.rst</a></p> <p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0671.rst">2025-02-01 08:55:40 GMT</a></p> </article> <nav id="pep-sidebar"> <h2>Contents</h2> <ul> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#motivation">Motivation</a></li> <li><a class="reference internal" href="#specification">Specification</a><ul> <li><a class="reference internal" href="#rejected-choices-of-spelling">Rejected choices of spelling</a></li> </ul> </li> <li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li> <li><a class="reference internal" href="#interaction-with-other-proposals">Interaction with other proposals</a></li> <li><a class="reference internal" href="#implementation-details">Implementation details</a><ul> <li><a class="reference internal" href="#costs">Costs</a></li> <li><a class="reference internal" href="#backward-incompatibility">Backward incompatibility</a></li> </ul> </li> <li><a class="reference internal" href="#references">References</a></li> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> <br> <a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0671.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>

Pages: 1 2 3 4 5 6 7 8 9 10