CINXE.COM
PEP 549 – Instance Descriptors | 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 549 – Instance Descriptors | peps.python.org</title> <link rel="shortcut icon" href="../_static/py.png"> <link rel="canonical" href="https://peps.python.org/pep-0549/"> <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 549 – Instance Descriptors | peps.python.org'> <meta property="og:description" content="Python’s descriptor protocol requires that descriptors be members of the type of an object. This PEP proposes an extension to the descriptor protocol allowing use of the descriptor protocol for members of instances. This would permit using properties ..."> <meta property="og:type" content="website"> <meta property="og:url" content="https://peps.python.org/pep-0549/"> <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="Python’s descriptor protocol requires that descriptors be members of the type of an object. This PEP proposes an extension to the descriptor protocol allowing use of the descriptor protocol for members of instances. This would permit using properties ..."> <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 549</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 549 – Instance Descriptors</h1> <dl class="rfc2822 field-list simple"> <dt class="field-odd">Author<span class="colon">:</span></dt> <dd class="field-odd">Larry Hastings <larry at hastings.org></dd> <dt class="field-even">Discussions-To<span class="colon">:</span></dt> <dd class="field-even"><a class="reference external" href="https://mail.python.org/archives/list/python-dev@python.org/">Python-Dev list</a></dd> <dt class="field-odd">Status<span class="colon">:</span></dt> <dd class="field-odd"><abbr title="Formally declined and will not be accepted">Rejected</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">04-Sep-2017</dd> <dt class="field-even">Python-Version<span class="colon">:</span></dt> <dd class="field-even">3.7</dd> <dt class="field-odd">Post-History<span class="colon">:</span></dt> <dd class="field-odd">04-Sep-2017</dd> </dl> <hr class="docutils" /> <section id="contents"> <details><summary>Table of Contents</summary><ul class="simple"> <li><a class="reference internal" href="#rejection-notice">Rejection Notice</a></li> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#rationale">Rationale</a></li> <li><a class="reference internal" href="#implementation">Implementation</a></li> <li><a class="reference internal" href="#prototype">Prototype</a></li> <li><a class="reference internal" href="#acknowledgements">Acknowledgements</a></li> <li><a class="reference internal" href="#references">References</a><ul> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> </li> </ul> </details></section> <section id="rejection-notice"> <h2><a class="toc-backref" href="#rejection-notice" role="doc-backlink">Rejection Notice</a></h2> <p><a class="reference external" href="https://mail.python.org/pipermail/python-dev/2017-November/150528.html">https://mail.python.org/pipermail/python-dev/2017-November/150528.html</a></p> </section> <section id="abstract"> <h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2> <p>Python’s descriptor protocol requires that descriptors be members of the <em>type</em> of an object. This PEP proposes an extension to the descriptor protocol allowing use of the descriptor protocol for members of <em>instances.</em> This would permit using properties in modules.</p> </section> <section id="rationale"> <h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2> <p>Python’s descriptor protocol guides programmers towards elegant API design. If your class supports a data-like member, and you <em>might</em> someday need to run code when changing the member’s value, you’re encouraged to simply declare it as a simple data member of the class for now. If in the future you do need to run code, you can change it to a “property”, and happily the API doesn’t change.</p> <p>But consider this second bit of best-practice Python API design: if you’re writing a singleton, don’t write a class, just build your code directly into a module. Don’t make your users instantiate a singleton class, don’t make your users have to dereference through a singleton object stored in a module, just have module-level functions and module-level data.</p> <p>Unfortunately these two best practices are in opposition. The problem is that properties aren’t supported on modules. Modules are instances of a single generic <code class="docutils literal notranslate"><span class="pre">module</span></code> type, and it’s not feasible to modify or subclass this type to add a property to one’s module. This means that programmers facing this API design decision, where the data-like member is a singleton stored in a module, must preemptively add ugly “getters” and “setters” for the data.</p> <p>Adding support for module properties in pure Python has recently become <em>possible;</em> as of Python 3.5, Python permits assigning to the <code class="docutils literal notranslate"><span class="pre">__class__</span></code> attribute of module objects, specifically for this purpose. Here’s an example of using this functionality to add a property to a module:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">sys</span><span class="o">,</span> <span class="nn">types</span> <span class="k">class</span> <span class="nc">_MyModuleType</span><span class="p">(</span><span class="n">types</span><span class="o">.</span><span class="n">ModuleType</span><span class="p">):</span> <span class="nd">@property</span> <span class="k">def</span> <span class="nf">prop</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">instance</span><span class="p">,</span> <span class="n">owner</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="vm">__class__</span> <span class="o">=</span> <span class="n">_MyModuleType</span> </pre></div> </div> <p>This works, and is supported behavior, but it’s clumsy and obscure.</p> <p>This PEP proposes a per-type opt-in extension to the descriptor protocol specifically designed to enable properties in modules. The mechanism is a way to honor the descriptor protocol for members of <em>instances</em> of a class without the member being declared as a class variable.</p> <p>Although this is being proposed as a general mechanism, the author currently only foresees this as being useful for module objects.</p> </section> <section id="implementation"> <h2><a class="toc-backref" href="#implementation" role="doc-backlink">Implementation</a></h2> <p>The basic idea is simple: modify the <code class="docutils literal notranslate"><span class="pre">tp_descr_get</span></code> and <code class="docutils literal notranslate"><span class="pre">tp_descr_set</span></code> functions exposed by <code class="docutils literal notranslate"><span class="pre">PyModule_Type</span></code> to inspect the attribute interacted with, and if it supports the descriptor protocol, call the relevant exposed function.</p> <p>Our implementation faces two challenges:</p> <ol class="arabic simple"> <li>Since this code will be run every time an attribute is looked up on a method, it needs to add very little overhead in the general case, where the object stored in the attribute is not a descriptor.</li> <li>Since functions are descriptors, we must take care to <em>not</em> honor the descriptor protocol for all objects. Otherwise, all module-level functions will suddenly become bound to the module instance as if they were method calls on the module object. The module handle would be passed in as a “self” argument to all module-level functions.</li> </ol> <p>Both challenges can be solved with the same approach: we define a new “fast subclass” flag that means “This object is a descriptor, and it should be honored directly when this object is looked up as an attribute of an instance”. So far this flag is only set on two types: <code class="docutils literal notranslate"><span class="pre">property</span></code> and <code class="docutils literal notranslate"><span class="pre">collections.abc.InstanceDescriptor</span></code>. The latter is an abstract base class, whose only purpose is to allow user classes to inherit this “fast subclass” flag.</p> </section> <section id="prototype"> <h2><a class="toc-backref" href="#prototype" role="doc-backlink">Prototype</a></h2> <p>A prototype of this functionality is under development at GitHub <a class="reference internal" href="#github" id="id1"><span>[github]</span></a>.</p> </section> <section id="acknowledgements"> <h2><a class="toc-backref" href="#acknowledgements" role="doc-backlink">Acknowledgements</a></h2> <p>Armin Rigo essentially proposed this mechanism when presented with the idea of “module properties”, and educated the author both on the complexities of the problem and the proper solution. Nathaniel J. Smith pointed out the 3.5 extension about assigning to <code class="docutils literal notranslate"><span class="pre">__class__</span></code> on module objects, and provided the example.</p> </section> <section id="references"> <h2><a class="toc-backref" href="#references" role="doc-backlink">References</a></h2> <div role="list" class="citation-list"> <div class="citation" id="github" role="doc-biblioentry"> <dt class="label" id="github">[<a href="#id1">github</a>]</dt> <dd><dl class="simple"> <dt>The branch is here:</dt><dd><a class="reference external" href="https://github.com/larryhastings/cpython/tree/module-properties">https://github.com/larryhastings/cpython/tree/module-properties</a></dd> <dt>A pull request against the main CPython repo is here:</dt><dd><a class="reference external" href="https://github.com/python/cpython/pull/3534">https://github.com/python/cpython/pull/3534</a></dd> </dl> </div> </div> <section id="copyright"> <h3><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h3> <p>This document has been placed in the public domain.</p> </section> </section> </section> <hr class="docutils" /> <p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0549.rst">https://github.com/python/peps/blob/main/peps/pep-0549.rst</a></p> <p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0549.rst">2023-09-09 17:39:29 GMT</a></p> </article> <nav id="pep-sidebar"> <h2>Contents</h2> <ul> <li><a class="reference internal" href="#rejection-notice">Rejection Notice</a></li> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#rationale">Rationale</a></li> <li><a class="reference internal" href="#implementation">Implementation</a></li> <li><a class="reference internal" href="#prototype">Prototype</a></li> <li><a class="reference internal" href="#acknowledgements">Acknowledgements</a></li> <li><a class="reference internal" href="#references">References</a><ul> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> </li> </ul> <br> <a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0549.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>