python-peps/pep-0726/index.html

405 lines
36 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>PEP 726 Module __setattr__ and __delattr__ | peps.python.org</title>
<link rel="shortcut icon" href="../_static/py.png">
<link rel="canonical" href="https://peps.python.org/pep-0726/">
<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 726 Module __setattr__ and __delattr__ | peps.python.org'>
<meta property="og:description" content="This PEP proposes supporting user-defined __setattr__ and __delattr__ methods on modules to extend customization of module attribute access beyond PEP 562.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://peps.python.org/pep-0726/">
<meta property="og:site_name" content="Python Enhancement Proposals (PEPs)">
<meta property="og:image" content="https://peps.python.org/_static/og-image.png">
<meta property="og:image:alt" content="Python PEPs">
<meta property="og:image:width" content="200">
<meta property="og:image:height" content="200">
<meta name="description" content="This PEP proposes supporting user-defined __setattr__ and __delattr__ methods on modules to extend customization of module attribute access beyond PEP 562.">
<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 726</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 726 Module <code class="docutils literal notranslate"><span class="pre">__setattr__</span></code> and <code class="docutils literal notranslate"><span class="pre">__delattr__</span></code></h1>
<dl class="rfc2822 field-list simple">
<dt class="field-odd">Author<span class="colon">:</span></dt>
<dd class="field-odd">Sergey B Kirpichev &lt;skirpichev&#32;&#97;t&#32;gmail.com&gt;</dd>
<dt class="field-even">Sponsor<span class="colon">:</span></dt>
<dd class="field-even">Adam Turner &lt;python&#32;&#97;t&#32;quite.org.uk&gt;</dd>
<dt class="field-odd">Discussions-To<span class="colon">:</span></dt>
<dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/32640/">Discourse thread</a></dd>
<dt class="field-even">Status<span class="colon">:</span></dt>
<dd class="field-even"><abbr title="Formally declined and will not be accepted">Rejected</abbr></dd>
<dt class="field-odd">Type<span class="colon">:</span></dt>
<dd class="field-odd"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd>
<dt class="field-even">Created<span class="colon">:</span></dt>
<dd class="field-even">24-Aug-2023</dd>
<dt class="field-odd">Python-Version<span class="colon">:</span></dt>
<dd class="field-odd">3.13</dd>
<dt class="field-even">Post-History<span class="colon">:</span></dt>
<dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/25506/" title="Discourse thread">06-Apr-2023</a>,
<a class="reference external" href="https://discuss.python.org/t/32640/" title="Discourse thread">31-Aug-2023</a></dd>
<dt class="field-odd">Resolution<span class="colon">:</span></dt>
<dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/32640/32">Discourse message</a></dd>
</dl>
<hr class="docutils" />
<section id="contents">
<details><summary>Table of Contents</summary><ul class="simple">
<li><a class="reference internal" href="#abstract">Abstract</a></li>
<li><a class="reference internal" href="#motivation">Motivation</a></li>
<li><a class="reference internal" href="#existing-options">Existing Options</a></li>
<li><a class="reference internal" href="#specification">Specification</a></li>
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li>
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
<li><a class="reference internal" href="#backwards-compatibility">Backwards compatibility</a></li>
<li><a class="reference internal" href="#discussion">Discussion</a></li>
<li><a class="reference internal" href="#footnotes">Footnotes</a></li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
</details></section>
<section id="abstract">
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
<p>This PEP proposes supporting user-defined <code class="docutils literal notranslate"><span class="pre">__setattr__</span></code>
and <code class="docutils literal notranslate"><span class="pre">__delattr__</span></code> methods on modules to extend customization
of module attribute access beyond <a class="pep reference internal" href="../pep-0562/" title="PEP 562 Module __getattr__ and __dir__">PEP 562</a>.</p>
</section>
<section id="motivation">
<h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
<p>There are several potential uses of a module <code class="docutils literal notranslate"><span class="pre">__setattr__</span></code>:</p>
<ol class="arabic simple">
<li>To prevent setting an attribute at all (i.e. make it read-only)</li>
<li>To validate the value to be assigned</li>
<li>To intercept setting an attribute and update some other state</li>
</ol>
<p>Proper support for read-only attributes would also require adding the
<code class="docutils literal notranslate"><span class="pre">__delattr__</span></code> function to prevent their deletion.</p>
<p>It would be convenient to directly support such customization, by recognizing
<code class="docutils literal notranslate"><span class="pre">__setattr__</span></code> and <code class="docutils literal notranslate"><span class="pre">__delattr__</span></code> methods defined in a module that would act
like normal <a class="reference external" href="https://docs.python.org/3/reference/datamodel.html#object.__setattr__" title="(in Python v3.12)"><code class="docutils literal notranslate"><span class="pre">object.__setattr__()</span></code></a> and
<a class="reference external" href="https://docs.python.org/3/reference/datamodel.html#object.__delattr__" title="(in Python v3.12)"><code class="docutils literal notranslate"><span class="pre">object.__delattr__()</span></code></a> methods, except that they will be defined
on module <em>instances</em>. Together with existing <code class="docutils literal notranslate"><span class="pre">__getattr__</span></code> and <code class="docutils literal notranslate"><span class="pre">__dir__</span></code>
methods this will streamline all variants of customizing module attribute access.</p>
<p>For example</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># mplib.py</span>
<span class="n">CONSTANT</span> <span class="o">=</span> <span class="mf">3.14</span>
<span class="n">prec</span> <span class="o">=</span> <span class="mi">53</span>
<span class="n">dps</span> <span class="o">=</span> <span class="mi">15</span>
<span class="k">def</span> <span class="nf">dps_to_prec</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;Return the number of bits required to represent n decimals accurately.&quot;&quot;&quot;</span>
<span class="k">return</span> <span class="nb">max</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="nb">round</span><span class="p">((</span><span class="nb">int</span><span class="p">(</span><span class="n">n</span><span class="p">)</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="o">*</span><span class="mf">3.3219280948873626</span><span class="p">)))</span>
<span class="k">def</span> <span class="nf">prec_to_dps</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;Return the number of accurate decimals that can be represented with n bits.&quot;&quot;&quot;</span>
<span class="k">return</span> <span class="nb">max</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="nb">round</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">n</span><span class="p">)</span><span class="o">/</span><span class="mf">3.3219280948873626</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="n">n</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
<span class="k">if</span> <span class="n">n</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">&#39;Positive integer expected&#39;</span><span class="p">)</span>
<span class="k">return</span> <span class="n">n</span>
<span class="k">def</span> <span class="fm">__setattr__</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="k">if</span> <span class="n">name</span> <span class="o">==</span> <span class="s1">&#39;CONSTANT&#39;</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span><span class="s1">&#39;Read-only attribute!&#39;</span><span class="p">)</span>
<span class="k">if</span> <span class="n">name</span> <span class="o">==</span> <span class="s1">&#39;dps&#39;</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="nb">globals</span><span class="p">()[</span><span class="s1">&#39;dps&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
<span class="nb">globals</span><span class="p">()[</span><span class="s1">&#39;prec&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">dps_to_prec</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="k">return</span>
<span class="k">if</span> <span class="n">name</span> <span class="o">==</span> <span class="s1">&#39;prec&#39;</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="nb">globals</span><span class="p">()[</span><span class="s1">&#39;prec&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
<span class="nb">globals</span><span class="p">()[</span><span class="s1">&#39;dps&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">prec_to_dps</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="k">return</span>
<span class="nb">globals</span><span class="p">()[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
<span class="k">def</span> <span class="fm">__delattr__</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
<span class="k">if</span> <span class="n">name</span> <span class="ow">in</span> <span class="p">(</span><span class="s1">&#39;CONSTANT&#39;</span><span class="p">,</span> <span class="s1">&#39;dps&#39;</span><span class="p">,</span> <span class="s1">&#39;prec&#39;</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span><span class="s1">&#39;Read-only attribute!&#39;</span><span class="p">)</span>
<span class="k">del</span> <span class="nb">globals</span><span class="p">()[</span><span class="n">name</span><span class="p">]</span>
</pre></div>
</div>
<div class="highlight-pycon notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="kn">import</span> <span class="nn">mplib</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">mplib</span><span class="o">.</span><span class="n">foo</span> <span class="o">=</span> <span class="s1">&#39;spam&#39;</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">mplib</span><span class="o">.</span><span class="n">CONSTANT</span> <span class="o">=</span> <span class="mi">42</span>
<span class="gt">Traceback (most recent call last):</span>
<span class="w"> </span><span class="c">...</span>
<span class="gr">AttributeError</span>: <span class="n">Read-only attribute!</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">del</span> <span class="n">mplib</span><span class="o">.</span><span class="n">foo</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">del</span> <span class="n">mplib</span><span class="o">.</span><span class="n">CONSTANT</span>
<span class="gt">Traceback (most recent call last):</span>
<span class="w"> </span><span class="c">...</span>
<span class="gr">AttributeError</span>: <span class="n">Read-only attribute!</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">mplib</span><span class="o">.</span><span class="n">prec</span>
<span class="go">53</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">mplib</span><span class="o">.</span><span class="n">dps</span>
<span class="go">15</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">mplib</span><span class="o">.</span><span class="n">dps</span> <span class="o">=</span> <span class="mi">5</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">mplib</span><span class="o">.</span><span class="n">prec</span>
<span class="go">20</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">mplib</span><span class="o">.</span><span class="n">dps</span> <span class="o">=</span> <span class="mi">0</span>
<span class="gt">Traceback (most recent call last):</span>
<span class="w"> </span><span class="c">...</span>
<span class="gr">ValueError</span>: <span class="n">Positive integer expected</span>
</pre></div>
</div>
</section>
<section id="existing-options">
<h2><a class="toc-backref" href="#existing-options" role="doc-backlink">Existing Options</a></h2>
<p>The current workaround is assigning the <code class="docutils literal notranslate"><span class="pre">__class__</span></code> of a module object to a
custom subclass of <a class="reference external" href="https://docs.python.org/3/library/types.html#types.ModuleType" title="(in Python v3.12)"><code class="docutils literal notranslate"><span class="pre">types.ModuleType</span></code></a> (see <a class="footnote-reference brackets" href="#id4" id="id1">[1]</a>).</p>
<p>For example, to prevent modification or deletion of an attribute we could use:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># mod.py</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">types</span> <span class="kn">import</span> <span class="n">ModuleType</span>
<span class="n">CONSTANT</span> <span class="o">=</span> <span class="mf">3.14</span>
<span class="k">class</span> <span class="nc">ImmutableModule</span><span class="p">(</span><span class="n">ModuleType</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__setattr__</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span><span class="s1">&#39;Read-only attribute!&#39;</span><span class="p">)</span>
<span class="k">def</span> <span class="fm">__delattr__</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span><span class="s1">&#39;Read-only attribute!&#39;</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="p">[</span><span class="vm">__name__</span><span class="p">]</span><span class="o">.</span><span class="vm">__class__</span> <span class="o">=</span> <span class="n">ImmutableModule</span>
</pre></div>
</div>
<p>But this variant is slower (~2x) than the proposed solution. More
importantly, it also brings a noticeable speed regression (~2-3x) for
attribute <em>access</em>.</p>
</section>
<section id="specification">
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
<p>The <code class="docutils literal notranslate"><span class="pre">__setattr__</span></code> function at the module level should accept two
arguments, the name of an attribute and the value to be assigned,
and return <a class="reference external" href="https://docs.python.org/3/library/constants.html#None" title="(in Python v3.12)"><code class="xref py py-obj docutils literal notranslate"><span class="pre">None</span></code></a> or raise an <a class="reference external" href="https://docs.python.org/3/library/exceptions.html#AttributeError" title="(in Python v3.12)"><code class="xref py py-exc docutils literal notranslate"><span class="pre">AttributeError</span></code></a>.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="fm">__setattr__</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">typing</span><span class="o">.</span><span class="n">Any</span><span class="p">,</span> <span class="o">/</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span>
</pre></div>
</div>
<p>The <code class="docutils literal notranslate"><span class="pre">__delattr__</span></code> function should accept one argument,
the name of an attribute, and return <a class="reference external" href="https://docs.python.org/3/library/constants.html#None" title="(in Python v3.12)"><code class="xref py py-obj docutils literal notranslate"><span class="pre">None</span></code></a> or raise an
<a class="reference external" href="https://docs.python.org/3/library/exceptions.html#AttributeError" title="(in Python v3.12)"><code class="xref py py-exc docutils literal notranslate"><span class="pre">AttributeError</span></code></a>:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="fm">__delattr__</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="o">/</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span>
</pre></div>
</div>
<p>The <code class="docutils literal notranslate"><span class="pre">__setattr__</span></code> and <code class="docutils literal notranslate"><span class="pre">__delattr__</span></code> functions are looked up in the
module <code class="docutils literal notranslate"><span class="pre">__dict__</span></code>. If present, the appropriate function is called to
customize setting the attribute or its deletion, else the normal
mechanism (storing/deleting the value in the module dictionary) will work.</p>
<p>Defining module <code class="docutils literal notranslate"><span class="pre">__setattr__</span></code> or <code class="docutils literal notranslate"><span class="pre">__delattr__</span></code> only affects lookups made
using the attribute access syntax — directly accessing the module globals
(whether by <code class="docutils literal notranslate"><span class="pre">globals()</span></code> within the module, or via a reference to the modules
globals dictionary) is unaffected. For example:</p>
<div class="highlight-pycon notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="kn">import</span> <span class="nn">mod</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">mod</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">[</span><span class="s1">&#39;foo&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;spam&#39;</span> <span class="c1"># bypasses __setattr__, defined in mod.py</span>
</pre></div>
</div>
<p>or</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># mod.py</span>
<span class="k">def</span> <span class="fm">__setattr__</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="o">...</span>
<span class="n">foo</span> <span class="o">=</span> <span class="s1">&#39;spam&#39;</span> <span class="c1"># bypasses __setattr__</span>
<span class="nb">globals</span><span class="p">()[</span><span class="s1">&#39;bar&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;spam&#39;</span> <span class="c1"># here too</span>
<span class="k">def</span> <span class="nf">f</span><span class="p">():</span>
<span class="k">global</span> <span class="n">x</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">123</span>
<span class="n">f</span><span class="p">()</span> <span class="c1"># and here</span>
</pre></div>
</div>
<p>To use a module global and trigger <code class="docutils literal notranslate"><span class="pre">__setattr__</span></code> (or <code class="docutils literal notranslate"><span class="pre">__delattr__</span></code>),
one can access it via <code class="docutils literal notranslate"><span class="pre">sys.modules[__name__]</span></code> within the modules code:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># mod.py</span>
<span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="p">[</span><span class="vm">__name__</span><span class="p">]</span><span class="o">.</span><span class="n">foo</span> <span class="o">=</span> <span class="s1">&#39;spam&#39;</span> <span class="c1"># bypasses __setattr__</span>
<span class="k">def</span> <span class="fm">__setattr__</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="o">...</span>
<span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="p">[</span><span class="vm">__name__</span><span class="p">]</span><span class="o">.</span><span class="n">bar</span> <span class="o">=</span> <span class="s1">&#39;spam&#39;</span> <span class="c1"># triggers __setattr__</span>
</pre></div>
</div>
<p>This limitation is intentional (just as for the <a class="pep reference internal" href="../pep-0562/" title="PEP 562 Module __getattr__ and __dir__">PEP 562</a>), because the
interpreter highly optimizes access to module globals and disabling all that
and going through special methods written in Python would slow down the code
unacceptably.</p>
</section>
<section id="how-to-teach-this">
<h2><a class="toc-backref" href="#how-to-teach-this" role="doc-backlink">How to Teach This</a></h2>
<p>The “Customizing module attribute access” <a class="footnote-reference brackets" href="#id4" id="id2">[1]</a> section of the documentation
will be expanded to include new functions.</p>
</section>
<section id="reference-implementation">
<h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2>
<p>The reference implementation for this PEP can be found in <a class="reference external" href="https://github.com/python/cpython/pull/108261">CPython PR #108261</a>.</p>
</section>
<section id="backwards-compatibility">
<h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards compatibility</a></h2>
<p>This PEP may break code that uses module level (global) names
<code class="docutils literal notranslate"><span class="pre">__setattr__</span></code> and <code class="docutils literal notranslate"><span class="pre">__delattr__</span></code>, but the language reference
explicitly reserves <em>all</em> undocumented dunder names, and allows
“breakage without warning” <a class="footnote-reference brackets" href="#id5" id="id3">[2]</a>.</p>
<p>The performance implications of this PEP are small, since additional
dictionary lookup is much cheaper than storing/deleting the value in
the dictionary. Also it is hard to imagine a module that expects the
user to set (and/or delete) attributes enough times to be a
performance concern. On another hand, proposed mechanism allows to
override setting/deleting of attributes without affecting speed of
attribute access, which is much more likely scenario to get a
performance penalty.</p>
</section>
<section id="discussion">
<h2><a class="toc-backref" href="#discussion" role="doc-backlink">Discussion</a></h2>
<p>As pointed out by Victor Stinner, the proposed API could be useful already in
the stdlib, for example to ensure that <a class="reference external" href="https://docs.python.org/3/library/sys.html#sys.modules" title="(in Python v3.12)"><code class="xref py py-obj docutils literal notranslate"><span class="pre">sys.modules</span></code></a> type is always a
<a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#dict" title="(in Python v3.12)"><code class="xref py py-class docutils literal notranslate"><span class="pre">dict</span></code></a>:</p>
<div class="highlight-pycon notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="kn">import</span> <span class="nn">sys</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">sys</span><span class="o">.</span><span class="n">modules</span> <span class="o">=</span> <span class="mi">123</span>
<span class="gp">&gt;&gt;&gt; </span><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="gt">Traceback (most recent call last):</span>
File <span class="nb">&quot;&lt;stdin&gt;&quot;</span>, line <span class="m">1</span>, in <span class="n">&lt;module&gt;</span>
File <span class="nb">&quot;&lt;frozen importlib._bootstrap&gt;&quot;</span>, line <span class="m">1260</span>, in <span class="n">_find_and_load</span>
<span class="gr">AttributeError</span>: <span class="n">&#39;int&#39; object has no attribute &#39;get&#39;</span>
</pre></div>
</div>
<p>or to prevent deletion of critical <a class="reference external" href="https://docs.python.org/3/library/sys.html#module-sys" title="(in Python v3.12)"><code class="xref py py-mod docutils literal notranslate"><span class="pre">sys</span></code></a> attributes, which makes the
code more complicated. For example, code using <a class="reference external" href="https://docs.python.org/3/library/sys.html#sys.stderr" title="(in Python v3.12)"><code class="xref py py-obj docutils literal notranslate"><span class="pre">sys.stderr</span></code></a> has to
check if the attribute exists and if its not <a class="reference external" href="https://docs.python.org/3/library/constants.html#None" title="(in Python v3.12)"><code class="xref py py-obj docutils literal notranslate"><span class="pre">None</span></code></a>. Currently, its
possible to remove any <a class="reference external" href="https://docs.python.org/3/library/sys.html#module-sys" title="(in Python v3.12)"><code class="xref py py-mod docutils literal notranslate"><span class="pre">sys</span></code></a> attribute, including functions:</p>
<div class="highlight-pycon notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="kn">import</span> <span class="nn">sys</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">del</span> <span class="n">sys</span><span class="o">.</span><span class="n">excepthook</span>
<span class="gp">&gt;&gt;&gt; </span><span class="mi">1</span><span class="o">+</span> <span class="c1"># notice the next line</span>
<span class="go">sys.excepthook is missing</span>
<span class="go"> File &quot;&lt;stdin&gt;&quot;, line 1</span>
<span class="go"> 1+</span>
<span class="go"> ^</span>
<span class="go">SyntaxError: invalid syntax</span>
</pre></div>
</div>
<p>See <a class="reference external" href="https://github.com/python/cpython/issues/106016#issue-1771174774">related issue</a> for
other details.</p>
<p>Other stdlib modules also come with attributes which can be overridden (as a
feature) and some input validation here could be helpful. Examples:
<a class="reference external" href="https://docs.python.org/3/library/threading.html#threading.excepthook" title="(in Python v3.12)"><code class="xref py py-obj docutils literal notranslate"><span class="pre">threading.excepthook</span></code></a>, <a class="reference external" href="https://docs.python.org/3/library/warnings.html#warnings.showwarning" title="(in Python v3.12)"><code class="xref py py-obj docutils literal notranslate"><span class="pre">warnings.showwarning</span></code></a>,
<a class="reference external" href="https://docs.python.org/3/library/io.html#io.DEFAULT_BUFFER_SIZE" title="(in Python v3.12)"><code class="xref py py-obj docutils literal notranslate"><span class="pre">io.DEFAULT_BUFFER_SIZE</span></code></a> or <a class="reference external" href="https://docs.python.org/3/library/os.html#os.SEEK_SET" title="(in Python v3.12)"><code class="xref py py-obj docutils literal notranslate"><span class="pre">os.SEEK_SET</span></code></a>.</p>
<p>Also a typical use case for customizing module attribute access is managing
deprecation warnings. But the <a class="pep reference internal" href="../pep-0562/" title="PEP 562 Module __getattr__ and __dir__">PEP 562</a> accomplishes this scenario only
partially: e.g. its impossible to issue a warning during an attempt to
<em>change</em> a renamed attribute.</p>
</section>
<section id="footnotes">
<h2><a class="toc-backref" href="#footnotes" role="doc-backlink">Footnotes</a></h2>
<aside class="footnote-list brackets">
<aside class="footnote brackets" id="id4" role="doc-footnote">
<dt class="label" id="id4">[1]<em> (<a href='#id1'>1</a>, <a href='#id2'>2</a>) </em></dt>
<dd>Customizing module attribute access
(<a class="reference external" href="https://docs.python.org/3.11/reference/datamodel.html#customizing-module-attribute-access">https://docs.python.org/3.11/reference/datamodel.html#customizing-module-attribute-access</a>)</aside>
<aside class="footnote brackets" id="id5" role="doc-footnote">
<dt class="label" id="id5">[<a href="#id3">2</a>]</dt>
<dd>Reserved classes of identifiers
(<a class="reference external" href="https://docs.python.org/3.11/reference/lexical_analysis.html#reserved-classes-of-identifiers">https://docs.python.org/3.11/reference/lexical_analysis.html#reserved-classes-of-identifiers</a>)</aside>
</aside>
</section>
<section id="copyright">
<h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2>
<p>This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.</p>
</section>
</section>
<hr class="docutils" />
<p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0726.rst">https://github.com/python/peps/blob/main/peps/pep-0726.rst</a></p>
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0726.rst">2024-02-28 23:47:57 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="#existing-options">Existing Options</a></li>
<li><a class="reference internal" href="#specification">Specification</a></li>
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li>
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
<li><a class="reference internal" href="#backwards-compatibility">Backwards compatibility</a></li>
<li><a class="reference internal" href="#discussion">Discussion</a></li>
<li><a class="reference internal" href="#footnotes">Footnotes</a></li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
<br>
<a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0726.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>