547 lines
42 KiB
HTML
547 lines
42 KiB
HTML
|
||
<!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 520 – Preserving Class Attribute Definition Order | peps.python.org</title>
|
||
<link rel="shortcut icon" href="../_static/py.png">
|
||
<link rel="canonical" href="https://peps.python.org/pep-0520/">
|
||
<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 520 – Preserving Class Attribute Definition Order | peps.python.org'>
|
||
<meta property="og:description" content="The class definition syntax is ordered by its very nature. Class attributes defined there are thus ordered. Aside from helping with readability, that ordering is sometimes significant. If it were automatically available outside the class definition th...">
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:url" content="https://peps.python.org/pep-0520/">
|
||
<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="The class definition syntax is ordered by its very nature. Class attributes defined there are thus ordered. Aside from helping with readability, that ordering is sometimes significant. If it were automatically available outside the class definition th...">
|
||
<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 520</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 520 – Preserving Class Attribute Definition Order</h1>
|
||
<dl class="rfc2822 field-list simple">
|
||
<dt class="field-odd">Author<span class="colon">:</span></dt>
|
||
<dd class="field-odd">Eric Snow <ericsnowcurrently at gmail.com></dd>
|
||
<dt class="field-even">Status<span class="colon">:</span></dt>
|
||
<dd class="field-even"><abbr title="Accepted and implementation complete, or no longer active">Final</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">07-Jun-2016</dd>
|
||
<dt class="field-odd">Python-Version<span class="colon">:</span></dt>
|
||
<dd class="field-odd">3.6</dd>
|
||
<dt class="field-even">Post-History<span class="colon">:</span></dt>
|
||
<dd class="field-even">07-Jun-2016, 11-Jun-2016, 20-Jun-2016, 24-Jun-2016</dd>
|
||
<dt class="field-odd">Resolution<span class="colon">:</span></dt>
|
||
<dd class="field-odd"><a class="reference external" href="https://mail.python.org/pipermail/python-dev/2016-June/145442.html">Python-Dev message</a></dd>
|
||
</dl>
|
||
<hr class="docutils" />
|
||
<section id="contents">
|
||
<details><summary>Table of Contents</summary><ul class="simple">
|
||
<li><a class="reference internal" href="#abstract">Abstract</a></li>
|
||
<li><a class="reference internal" href="#motivation">Motivation</a></li>
|
||
<li><a class="reference internal" href="#background">Background</a></li>
|
||
<li><a class="reference internal" href="#specification">Specification</a><ul>
|
||
<li><a class="reference internal" href="#why-a-tuple">Why a tuple?</a></li>
|
||
<li><a class="reference internal" href="#why-not-a-read-only-attribute">Why not a read-only attribute?</a></li>
|
||
<li><a class="reference internal" href="#why-not-attribute-order">Why not “__attribute_order__”?</a></li>
|
||
<li><a class="reference internal" href="#why-not-ignore-dunder-names">Why not ignore “dunder” names?</a></li>
|
||
<li><a class="reference internal" href="#why-none-instead-of-an-empty-tuple">Why None instead of an empty tuple?</a></li>
|
||
<li><a class="reference internal" href="#why-none-instead-of-not-setting-the-attribute">Why None instead of not setting the attribute?</a></li>
|
||
<li><a class="reference internal" href="#why-constrain-manually-set-values">Why constrain manually set values?</a></li>
|
||
<li><a class="reference internal" href="#why-not-hide-definition-order-on-non-type-objects">Why not hide __definition_order__ on non-type objects?</a></li>
|
||
<li><a class="reference internal" href="#what-about-slots">What about __slots__?</a></li>
|
||
<li><a class="reference internal" href="#why-is-definition-order-even-necessary">Why is __definition_order__ even necessary?</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#support-for-c-api-types">Support for C-API Types</a></li>
|
||
<li><a class="reference internal" href="#compatibility">Compatibility</a></li>
|
||
<li><a class="reference internal" href="#changes">Changes</a></li>
|
||
<li><a class="reference internal" href="#other-python-implementations">Other Python Implementations</a></li>
|
||
<li><a class="reference internal" href="#implementation">Implementation</a></li>
|
||
<li><a class="reference internal" href="#alternatives">Alternatives</a><ul>
|
||
<li><a class="reference internal" href="#an-order-preserving-cls-dict">An Order-preserving cls.__dict__</a></li>
|
||
<li><a class="reference internal" href="#a-namespace-keyword-arg-for-class-definition">A “namespace” Keyword Arg for Class Definition</a></li>
|
||
<li><a class="reference internal" href="#a-stdlib-metaclass-that-implements-prepare-with-ordereddict">A stdlib Metaclass that Implements __prepare__() with OrderedDict</a></li>
|
||
<li><a class="reference internal" href="#set-definition-order-at-compile-time">Set __definition_order__ at Compile-time</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>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>Since compact dict has landed in 3.6, __definition_order__
|
||
has been removed. <code class="docutils literal notranslate"><span class="pre">cls.__dict__</span></code> now mostly accomplishes the same
|
||
thing instead.</p>
|
||
</div>
|
||
<section id="abstract">
|
||
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
|
||
<p>The class definition syntax is ordered by its very nature. Class
|
||
attributes defined there are thus ordered. Aside from helping with
|
||
readability, that ordering is sometimes significant. If it were
|
||
automatically available outside the class definition then the
|
||
attribute order could be used without the need for extra boilerplate
|
||
(such as metaclasses or manually enumerating the attribute order).
|
||
Given that this information already exists, access to the definition
|
||
order of attributes is a reasonable expectation. However, currently
|
||
Python does not preserve the attribute order from the class
|
||
definition.</p>
|
||
<p>This PEP changes that by preserving the order in which attributes
|
||
are introduced in the class definition body. That order will now be
|
||
preserved in the <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> attribute of the class.
|
||
This allows introspection of the original definition order, e.g. by
|
||
class decorators.</p>
|
||
<p>Additionally, this PEP requires that the default class definition
|
||
namespace be ordered (e.g. <code class="docutils literal notranslate"><span class="pre">OrderedDict</span></code>) by default. The
|
||
long-lived class namespace (<code class="docutils literal notranslate"><span class="pre">__dict__</span></code>) will remain a <code class="docutils literal notranslate"><span class="pre">dict</span></code>.</p>
|
||
</section>
|
||
<section id="motivation">
|
||
<h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
|
||
<p>The attribute order from a class definition may be useful to tools
|
||
that rely on name order. However, without the automatic availability
|
||
of the definition order, those tools must impose extra requirements on
|
||
users. For example, use of such a tool may require that your class use
|
||
a particular metaclass. Such requirements are often enough to
|
||
discourage use of the tool.</p>
|
||
<p>Some tools that could make use of this PEP include:</p>
|
||
<ul class="simple">
|
||
<li>documentation generators</li>
|
||
<li>testing frameworks</li>
|
||
<li>CLI frameworks</li>
|
||
<li>web frameworks</li>
|
||
<li>config generators</li>
|
||
<li>data serializers</li>
|
||
<li>enum factories (my original motivation)</li>
|
||
</ul>
|
||
</section>
|
||
<section id="background">
|
||
<h2><a class="toc-backref" href="#background" role="doc-backlink">Background</a></h2>
|
||
<p>When a class is defined using a <code class="docutils literal notranslate"><span class="pre">class</span></code> statement, the class body
|
||
is executed within a namespace. Currently that namespace defaults to
|
||
<code class="docutils literal notranslate"><span class="pre">dict</span></code>. If the metaclass defines <code class="docutils literal notranslate"><span class="pre">__prepare__()</span></code> then the result
|
||
of calling it is used for the class definition namespace.</p>
|
||
<p>After the execution completes, the definition namespace is
|
||
copied into a new <code class="docutils literal notranslate"><span class="pre">dict</span></code>. Then the original definition namespace is
|
||
discarded. The new copy is stored away as the class’s namespace and
|
||
is exposed as <code class="docutils literal notranslate"><span class="pre">__dict__</span></code> through a read-only proxy.</p>
|
||
<p>The class attribute definition order is represented by the insertion
|
||
order of names in the <em>definition</em> namespace. Thus, we can have
|
||
access to the definition order by switching the definition namespace
|
||
to an ordered mapping, such as <code class="docutils literal notranslate"><span class="pre">collections.OrderedDict</span></code>. This is
|
||
feasible using a metaclass and <code class="docutils literal notranslate"><span class="pre">__prepare__</span></code>, as described above.
|
||
In fact, exactly this is by far the most common use case for using
|
||
<code class="docutils literal notranslate"><span class="pre">__prepare__</span></code>.</p>
|
||
<p>At that point, the only missing thing for later access to the
|
||
definition order is storing it on the class before the definition
|
||
namespace is thrown away. Again, this may be done using a metaclass.
|
||
However, this means that the definition order is preserved only for
|
||
classes that use such a metaclass. There are two practical problems
|
||
with that:</p>
|
||
<p>First, it requires the use of a metaclass. Metaclasses introduce an
|
||
extra level of complexity to code and in some cases (e.g. conflicts)
|
||
are a problem. So reducing the need for them is worth doing when the
|
||
opportunity presents itself. <a class="pep reference internal" href="../pep-0422/" title="PEP 422 – Simpler customisation of class creation">PEP 422</a> and <a class="pep reference internal" href="../pep-0487/" title="PEP 487 – Simpler customisation of class creation">PEP 487</a> discuss this at
|
||
length. We have such an opportunity by using an ordered mapping (e.g.
|
||
<code class="docutils literal notranslate"><span class="pre">OrderedDict</span></code> for CPython at least) for the default class definition
|
||
namespace, virtually eliminating the need for <code class="docutils literal notranslate"><span class="pre">__prepare__()</span></code>.</p>
|
||
<p>Second, only classes that opt in to using the <code class="docutils literal notranslate"><span class="pre">OrderedDict</span></code>-based
|
||
metaclass will have access to the definition order. This is problematic
|
||
for cases where universal access to the definition order is important.</p>
|
||
</section>
|
||
<section id="specification">
|
||
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
|
||
<p>Part 1:</p>
|
||
<ul class="simple">
|
||
<li>all classes have a <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> attribute</li>
|
||
<li><code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> is a <code class="docutils literal notranslate"><span class="pre">tuple</span></code> of identifiers (or <code class="docutils literal notranslate"><span class="pre">None</span></code>)</li>
|
||
<li><code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> is always set:<ol class="arabic simple">
|
||
<li>during execution of the class body, the insertion order of names
|
||
into the class <em>definition</em> namespace is stored in a tuple</li>
|
||
<li>if <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> is defined in the class body then it
|
||
must be a <code class="docutils literal notranslate"><span class="pre">tuple</span></code> of identifiers or <code class="docutils literal notranslate"><span class="pre">None</span></code>; any other value
|
||
will result in <code class="docutils literal notranslate"><span class="pre">TypeError</span></code></li>
|
||
<li>classes that do not have a class definition (e.g. builtins) have
|
||
their <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> set to <code class="docutils literal notranslate"><span class="pre">None</span></code></li>
|
||
<li>classes for which <code class="docutils literal notranslate"><span class="pre">__prepare__()</span></code> returned something other than
|
||
<code class="docutils literal notranslate"><span class="pre">OrderedDict</span></code> (or a subclass) have their <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code>
|
||
set to <code class="docutils literal notranslate"><span class="pre">None</span></code> (except where #2 applies)</li>
|
||
</ol>
|
||
</li>
|
||
</ul>
|
||
<p>Not changing:</p>
|
||
<ul class="simple">
|
||
<li><code class="docutils literal notranslate"><span class="pre">dir()</span></code> will not depend on <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code></li>
|
||
<li>descriptors and custom <code class="docutils literal notranslate"><span class="pre">__getattribute__</span></code> methods are unconstrained
|
||
regarding <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code></li>
|
||
</ul>
|
||
<p>Part 2:</p>
|
||
<ul class="simple">
|
||
<li>the default class <em>definition</em> namespace is now an ordered mapping
|
||
(e.g. <code class="docutils literal notranslate"><span class="pre">OrderdDict</span></code>)</li>
|
||
<li><code class="docutils literal notranslate"><span class="pre">cls.__dict__</span></code> does not change, remaining a read-only proxy around
|
||
<code class="docutils literal notranslate"><span class="pre">dict</span></code></li>
|
||
</ul>
|
||
<p>Note that Python implementations which have an ordered <code class="docutils literal notranslate"><span class="pre">dict</span></code> won’t
|
||
need to change anything.</p>
|
||
<p>The following code demonstrates roughly equivalent semantics for both
|
||
parts 1 and 2:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Meta</span><span class="p">(</span><span class="nb">type</span><span class="p">):</span>
|
||
<span class="nd">@classmethod</span>
|
||
<span class="k">def</span> <span class="fm">__prepare__</span><span class="p">(</span><span class="bp">cls</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">OrderedDict</span><span class="p">()</span>
|
||
|
||
<span class="k">class</span> <span class="nc">Spam</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">Meta</span><span class="p">):</span>
|
||
<span class="n">ham</span> <span class="o">=</span> <span class="kc">None</span>
|
||
<span class="n">eggs</span> <span class="o">=</span> <span class="mi">5</span>
|
||
<span class="n">__definition_order__</span> <span class="o">=</span> <span class="nb">tuple</span><span class="p">(</span><span class="nb">locals</span><span class="p">())</span>
|
||
</pre></div>
|
||
</div>
|
||
<section id="why-a-tuple">
|
||
<h3><a class="toc-backref" href="#why-a-tuple" role="doc-backlink">Why a tuple?</a></h3>
|
||
<p>Use of a tuple reflects the fact that we are exposing the order in
|
||
which attributes on the class were <em>defined</em>. Since the definition
|
||
is already complete by the time <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> is set, the
|
||
content and order of the value won’t be changing. Thus we use a type
|
||
that communicates that state of immutability.</p>
|
||
</section>
|
||
<section id="why-not-a-read-only-attribute">
|
||
<h3><a class="toc-backref" href="#why-not-a-read-only-attribute" role="doc-backlink">Why not a read-only attribute?</a></h3>
|
||
<p>There are some valid arguments for making <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code>
|
||
a read-only attribute (like <code class="docutils literal notranslate"><span class="pre">cls.__dict__</span></code> is). Most notably, a
|
||
read-only attribute conveys the nature of the attribute as “complete”,
|
||
which is exactly correct for <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code>. Since it
|
||
represents the state of a particular one-time event (execution of
|
||
the class definition body), allowing the value to be replaced would
|
||
reduce confidence that the attribute corresponds to the original class
|
||
body. Furthermore, often an immutable-by-default approach helps to
|
||
make data easier to reason about.</p>
|
||
<p>However, in this case there still isn’t a <em>strong</em> reason to counter
|
||
the well-worn precedent found in Python. Per Guido:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">I</span> <span class="n">don</span><span class="s1">'t see why it needs to be a read-only attribute. There are</span>
|
||
<span class="n">very</span> <span class="n">few</span> <span class="n">of</span> <span class="n">those</span> <span class="o">--</span> <span class="ow">in</span> <span class="n">general</span> <span class="n">we</span> <span class="n">let</span> <span class="n">users</span> <span class="n">play</span> <span class="n">around</span> <span class="k">with</span>
|
||
<span class="n">things</span> <span class="n">unless</span> <span class="n">we</span> <span class="n">have</span> <span class="n">a</span> <span class="n">hard</span> <span class="n">reason</span> <span class="n">to</span> <span class="n">restrict</span> <span class="n">assignment</span> <span class="p">(</span><span class="n">e</span><span class="o">.</span><span class="n">g</span><span class="o">.</span>
|
||
<span class="n">the</span> <span class="n">interpreter</span><span class="s1">'s internal state could be compromised). I don'</span><span class="n">t</span>
|
||
<span class="n">see</span> <span class="n">such</span> <span class="n">a</span> <span class="n">hard</span> <span class="n">reason</span> <span class="n">here</span><span class="o">.</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Also, note that a writeable <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> allows dynamically
|
||
created classes (e.g. by Cython) to still have <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code>
|
||
properly set. That could certainly be handled through specific
|
||
class-creation tools, such as <code class="docutils literal notranslate"><span class="pre">type()</span></code> or the C-API, without the need
|
||
to lose the semantics of a read-only attribute. However, with a
|
||
writeable attribute it’s a moot point.</p>
|
||
</section>
|
||
<section id="why-not-attribute-order">
|
||
<h3><a class="toc-backref" href="#why-not-attribute-order" role="doc-backlink">Why not “__attribute_order__”?</a></h3>
|
||
<p><code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> is centered on the class definition
|
||
body. The use cases for dealing with the class namespace (<code class="docutils literal notranslate"><span class="pre">__dict__</span></code>)
|
||
post-definition are a separate matter. <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> would
|
||
be a significantly misleading name for a feature focused on more than
|
||
class definition.</p>
|
||
</section>
|
||
<section id="why-not-ignore-dunder-names">
|
||
<h3><a class="toc-backref" href="#why-not-ignore-dunder-names" role="doc-backlink">Why not ignore “dunder” names?</a></h3>
|
||
<p>Names starting and ending with “__” are reserved for use by the
|
||
interpreter. In practice they should not be relevant to the users of
|
||
<code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code>. Instead, for nearly everyone they would only
|
||
be clutter, causing the same extra work (filtering out the dunder
|
||
names) for the majority. In cases where a dunder name is significant,
|
||
the class definition <em>could</em> manually set <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code>,
|
||
making the common case simpler.</p>
|
||
<p>However, leaving dunder names out of <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> means
|
||
that their place in the definition order would be unrecoverably lost.
|
||
Dropping dunder names by default may inadvertently cause problems for
|
||
classes that use dunder names unconventionally. In this case it’s
|
||
better to play it safe and preserve <em>all</em> the names from the class
|
||
definition. This isn’t a big problem since it is easy to filter out
|
||
dunder names:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">(</span><span class="n">name</span> <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="bp">cls</span><span class="o">.</span><span class="n">__definition_order__</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="p">(</span><span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">'__'</span><span class="p">)</span> <span class="ow">and</span> <span class="n">name</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s1">'__'</span><span class="p">)))</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>In fact, in some application contexts there may be other criteria on
|
||
which similar filtering would be applied, such as ignoring any name
|
||
starting with “_”, leaving out all methods, or including only
|
||
descriptors. Ultimately dunder names aren’t a special enough case to
|
||
be treated exceptionally.</p>
|
||
<p>Note that a couple of dunder names (<code class="docutils literal notranslate"><span class="pre">__name__</span></code> and <code class="docutils literal notranslate"><span class="pre">__qualname__</span></code>)
|
||
are injected by default by the compiler. So they will be included even
|
||
though they are not strictly part of the class definition body.</p>
|
||
</section>
|
||
<section id="why-none-instead-of-an-empty-tuple">
|
||
<h3><a class="toc-backref" href="#why-none-instead-of-an-empty-tuple" role="doc-backlink">Why None instead of an empty tuple?</a></h3>
|
||
<p>A key objective of adding <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> is to preserve
|
||
information in class definitions which was lost prior to this PEP.
|
||
One consequence is that <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> implies an original
|
||
class definition. Using <code class="docutils literal notranslate"><span class="pre">None</span></code> allows us to clearly distinguish
|
||
classes that do not have a definition order. An empty tuple clearly
|
||
indicates a class that came from a definition statement but did not
|
||
define any attributes there.</p>
|
||
</section>
|
||
<section id="why-none-instead-of-not-setting-the-attribute">
|
||
<h3><a class="toc-backref" href="#why-none-instead-of-not-setting-the-attribute" role="doc-backlink">Why None instead of not setting the attribute?</a></h3>
|
||
<p>The absence of an attribute requires more complex handling than <code class="docutils literal notranslate"><span class="pre">None</span></code>
|
||
does for consumers of <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code>.</p>
|
||
</section>
|
||
<section id="why-constrain-manually-set-values">
|
||
<h3><a class="toc-backref" href="#why-constrain-manually-set-values" role="doc-backlink">Why constrain manually set values?</a></h3>
|
||
<p>If <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> is manually set in the class body then it
|
||
will be used. We require it to be a tuple of identifiers (or <code class="docutils literal notranslate"><span class="pre">None</span></code>)
|
||
so that consumers of <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> may have a consistent
|
||
expectation for the value. That helps maximize the feature’s
|
||
usefulness.</p>
|
||
<p>We could also allow an arbitrary iterable for a manually set
|
||
<code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> and convert it into a tuple. However, not
|
||
all iterables infer a definition order (e.g. <code class="docutils literal notranslate"><span class="pre">set</span></code>). So we opt in
|
||
favor of requiring a tuple.</p>
|
||
</section>
|
||
<section id="why-not-hide-definition-order-on-non-type-objects">
|
||
<h3><a class="toc-backref" href="#why-not-hide-definition-order-on-non-type-objects" role="doc-backlink">Why not hide __definition_order__ on non-type objects?</a></h3>
|
||
<p>Python doesn’t make much effort to hide class-specific attributes
|
||
during lookup on instances of classes. While it may make sense
|
||
to consider <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> a class-only attribute, hidden
|
||
during lookup on objects, setting precedent in that regard is
|
||
beyond the goals of this PEP.</p>
|
||
</section>
|
||
<section id="what-about-slots">
|
||
<h3><a class="toc-backref" href="#what-about-slots" role="doc-backlink">What about __slots__?</a></h3>
|
||
<p><code class="docutils literal notranslate"><span class="pre">__slots__</span></code> will be added to <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> like any
|
||
other name in the class definition body. The actual slot names
|
||
will not be added to <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> since they aren’t
|
||
set as names in the definition namespace.</p>
|
||
</section>
|
||
<section id="why-is-definition-order-even-necessary">
|
||
<h3><a class="toc-backref" href="#why-is-definition-order-even-necessary" role="doc-backlink">Why is __definition_order__ even necessary?</a></h3>
|
||
<p>Since the definition order is not preserved in <code class="docutils literal notranslate"><span class="pre">__dict__</span></code>, it is
|
||
lost once class definition execution completes. Classes <em>could</em>
|
||
explicitly set the attribute as the last thing in the body. However,
|
||
then independent decorators could only make use of classes that had done
|
||
so. Instead, <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> preserves this one bit of info
|
||
from the class body so that it is universally available.</p>
|
||
</section>
|
||
</section>
|
||
<section id="support-for-c-api-types">
|
||
<h2><a class="toc-backref" href="#support-for-c-api-types" role="doc-backlink">Support for C-API Types</a></h2>
|
||
<p>Arguably, most C-defined Python types (e.g. built-in, extension modules)
|
||
have a roughly equivalent concept of a definition order. So conceivably
|
||
<code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> could be set for such types automatically. This
|
||
PEP does not introduce any such support. However, it does not prohibit
|
||
it either. However, since <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> can be set at any
|
||
time through normal attribute assignment, it does not need any special
|
||
treatment in the C-API.</p>
|
||
<p>The specific cases:</p>
|
||
<ul class="simple">
|
||
<li>builtin types</li>
|
||
<li>PyType_Ready</li>
|
||
<li>PyType_FromSpec</li>
|
||
</ul>
|
||
</section>
|
||
<section id="compatibility">
|
||
<h2><a class="toc-backref" href="#compatibility" role="doc-backlink">Compatibility</a></h2>
|
||
<p>This PEP does not break backward compatibility, except in the case that
|
||
someone relies <em>strictly</em> on <code class="docutils literal notranslate"><span class="pre">dict</span></code> as the class definition namespace.
|
||
This shouldn’t be a problem since <code class="docutils literal notranslate"><span class="pre">issubclass(OrderedDict,</span> <span class="pre">dict)</span></code> is
|
||
true.</p>
|
||
</section>
|
||
<section id="changes">
|
||
<h2><a class="toc-backref" href="#changes" role="doc-backlink">Changes</a></h2>
|
||
<p>In addition to the class syntax, the following expose the new behavior:</p>
|
||
<ul class="simple">
|
||
<li>builtins.__build_class__</li>
|
||
<li>types.prepare_class</li>
|
||
<li>types.new_class</li>
|
||
</ul>
|
||
<p>Also, the 3-argument form of <code class="docutils literal notranslate"><span class="pre">builtins.type()</span></code> will allow inclusion
|
||
of <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> in the namespace that gets passed in. It
|
||
will be subject to the same constraints as when <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code>
|
||
is explicitly defined in the class body.</p>
|
||
</section>
|
||
<section id="other-python-implementations">
|
||
<h2><a class="toc-backref" href="#other-python-implementations" role="doc-backlink">Other Python Implementations</a></h2>
|
||
<p>Pending feedback, the impact on Python implementations is expected to
|
||
be minimal. All conforming implementations are expected to set
|
||
<code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> as described in this PEP.</p>
|
||
</section>
|
||
<section id="implementation">
|
||
<h2><a class="toc-backref" href="#implementation" role="doc-backlink">Implementation</a></h2>
|
||
<p>The implementation is found in the
|
||
<a class="reference external" href="https://github.com/python/cpython/issues/68442">tracker</a>.</p>
|
||
</section>
|
||
<section id="alternatives">
|
||
<h2><a class="toc-backref" href="#alternatives" role="doc-backlink">Alternatives</a></h2>
|
||
<section id="an-order-preserving-cls-dict">
|
||
<h3><a class="toc-backref" href="#an-order-preserving-cls-dict" role="doc-backlink">An Order-preserving cls.__dict__</a></h3>
|
||
<p>Instead of storing the definition order in <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code>,
|
||
the now-ordered definition namespace could be copied into a new
|
||
<code class="docutils literal notranslate"><span class="pre">OrderedDict</span></code>. This would then be used as the mapping proxied as
|
||
<code class="docutils literal notranslate"><span class="pre">__dict__</span></code>. Doing so would mostly provide the same semantics.</p>
|
||
<p>However, using <code class="docutils literal notranslate"><span class="pre">OrderedDict</span></code> for <code class="docutils literal notranslate"><span class="pre">__dict__</span></code> would obscure the
|
||
relationship with the definition namespace, making it less useful.</p>
|
||
<p>Additionally, (in the case of <code class="docutils literal notranslate"><span class="pre">OrderedDict</span></code> specifically) doing
|
||
this would require significant changes to the semantics of the
|
||
concrete <code class="docutils literal notranslate"><span class="pre">dict</span></code> C-API.</p>
|
||
<p>There has been some discussion about moving to a compact dict
|
||
implementation which would (mostly) preserve insertion order. However
|
||
the lack of an explicit <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> would still remain
|
||
as a pain point.</p>
|
||
</section>
|
||
<section id="a-namespace-keyword-arg-for-class-definition">
|
||
<h3><a class="toc-backref" href="#a-namespace-keyword-arg-for-class-definition" role="doc-backlink">A “namespace” Keyword Arg for Class Definition</a></h3>
|
||
<p><a class="pep reference internal" href="../pep-0422/#order-preserving-classes" title="PEP 422 – Simpler customisation of class creation § Order preserving classes">PEP 422</a>
|
||
introduced a new “namespace” keyword arg to class definitions
|
||
that effectively replaces the need to <code class="docutils literal notranslate"><span class="pre">__prepare__()</span></code>.
|
||
However, the proposal was withdrawn in favor of the simpler <a class="pep reference internal" href="../pep-0487/" title="PEP 487 – Simpler customisation of class creation">PEP 487</a>.</p>
|
||
</section>
|
||
<section id="a-stdlib-metaclass-that-implements-prepare-with-ordereddict">
|
||
<h3><a class="toc-backref" href="#a-stdlib-metaclass-that-implements-prepare-with-ordereddict" role="doc-backlink">A stdlib Metaclass that Implements __prepare__() with OrderedDict</a></h3>
|
||
<p>This has all the same problems as writing your own metaclass. The
|
||
only advantage is that you don’t have to actually write this
|
||
metaclass. So it doesn’t offer any benefit in the context of this
|
||
PEP.</p>
|
||
</section>
|
||
<section id="set-definition-order-at-compile-time">
|
||
<h3><a class="toc-backref" href="#set-definition-order-at-compile-time" role="doc-backlink">Set __definition_order__ at Compile-time</a></h3>
|
||
<p>Each class’s <code class="docutils literal notranslate"><span class="pre">__qualname__</span></code> is determined at compile-time.
|
||
This same concept could be applied to <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code>.
|
||
The result of composing <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> at compile-time
|
||
would be nearly the same as doing so at run-time.</p>
|
||
<p>Comparative implementation difficulty aside, the key difference
|
||
would be that at compile-time it would not be practical to
|
||
preserve definition order for attributes that are set dynamically
|
||
in the class body (e.g. <code class="docutils literal notranslate"><span class="pre">locals()[name]</span> <span class="pre">=</span> <span class="pre">value</span></code>). However,
|
||
they should still be reflected in the definition order. One
|
||
possible resolution would be to require class authors to manually
|
||
set <code class="docutils literal notranslate"><span class="pre">__definition_order__</span></code> if they define any class attributes
|
||
dynamically.</p>
|
||
<p>Ultimately, the use of <code class="docutils literal notranslate"><span class="pre">OrderedDict</span></code> at run-time or compile-time
|
||
discovery is almost entirely an implementation detail.</p>
|
||
</section>
|
||
</section>
|
||
<section id="references">
|
||
<h2><a class="toc-backref" href="#references" role="doc-backlink">References</a></h2>
|
||
<ul class="simple">
|
||
<li><a class="reference external" href="https://mail.python.org/pipermail/python-ideas/2013-February/019690.html">Original discussion</a></li>
|
||
<li><a class="reference external" href="https://mail.python.org/pipermail/python-dev/2013-June/127103.html">Follow-up 1</a></li>
|
||
<li><a class="reference external" href="https://mail.python.org/pipermail/python-dev/2015-May/140137.html">Follow-up 2</a></li>
|
||
<li><a class="reference external" href="https://mail.python.org/pipermail/python-dev/2016-June/144883.html">Alyssa (Nick) Coghlan’s concerns about mutability</a></li>
|
||
</ul>
|
||
</section>
|
||
<section id="copyright">
|
||
<h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2>
|
||
<p>This document has been placed in the public domain.</p>
|
||
</section>
|
||
</section>
|
||
<hr class="docutils" />
|
||
<p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0520.rst">https://github.com/python/peps/blob/main/peps/pep-0520.rst</a></p>
|
||
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0520.rst">2023-10-11 12:05:51 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="#background">Background</a></li>
|
||
<li><a class="reference internal" href="#specification">Specification</a><ul>
|
||
<li><a class="reference internal" href="#why-a-tuple">Why a tuple?</a></li>
|
||
<li><a class="reference internal" href="#why-not-a-read-only-attribute">Why not a read-only attribute?</a></li>
|
||
<li><a class="reference internal" href="#why-not-attribute-order">Why not “__attribute_order__”?</a></li>
|
||
<li><a class="reference internal" href="#why-not-ignore-dunder-names">Why not ignore “dunder” names?</a></li>
|
||
<li><a class="reference internal" href="#why-none-instead-of-an-empty-tuple">Why None instead of an empty tuple?</a></li>
|
||
<li><a class="reference internal" href="#why-none-instead-of-not-setting-the-attribute">Why None instead of not setting the attribute?</a></li>
|
||
<li><a class="reference internal" href="#why-constrain-manually-set-values">Why constrain manually set values?</a></li>
|
||
<li><a class="reference internal" href="#why-not-hide-definition-order-on-non-type-objects">Why not hide __definition_order__ on non-type objects?</a></li>
|
||
<li><a class="reference internal" href="#what-about-slots">What about __slots__?</a></li>
|
||
<li><a class="reference internal" href="#why-is-definition-order-even-necessary">Why is __definition_order__ even necessary?</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#support-for-c-api-types">Support for C-API Types</a></li>
|
||
<li><a class="reference internal" href="#compatibility">Compatibility</a></li>
|
||
<li><a class="reference internal" href="#changes">Changes</a></li>
|
||
<li><a class="reference internal" href="#other-python-implementations">Other Python Implementations</a></li>
|
||
<li><a class="reference internal" href="#implementation">Implementation</a></li>
|
||
<li><a class="reference internal" href="#alternatives">Alternatives</a><ul>
|
||
<li><a class="reference internal" href="#an-order-preserving-cls-dict">An Order-preserving cls.__dict__</a></li>
|
||
<li><a class="reference internal" href="#a-namespace-keyword-arg-for-class-definition">A “namespace” Keyword Arg for Class Definition</a></li>
|
||
<li><a class="reference internal" href="#a-stdlib-metaclass-that-implements-prepare-with-ordereddict">A stdlib Metaclass that Implements __prepare__() with OrderedDict</a></li>
|
||
<li><a class="reference internal" href="#set-definition-order-at-compile-time">Set __definition_order__ at Compile-time</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-0520.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> |