View file File name : descriptor.html Content : <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" /> <title>Descriptor HowTo Guide — Python 3.10.12 documentation</title><meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" type="text/css" href="../_static/pygments.css" /> <link rel="stylesheet" type="text/css" href="../_static/pydoctheme.css?2022.1" /> <script data-url_root="../" id="documentation_options" src="../_static/documentation_options.js"></script> <script src="../_static/jquery.js"></script> <script src="../_static/underscore.js"></script> <script src="../_static/doctools.js"></script> <script src="../_static/sidebar.js"></script> <link rel="search" type="application/opensearchdescription+xml" title="Search within Python 3.10.12 documentation" href="../_static/opensearch.xml"/> <link rel="author" title="About these documents" href="../about.html" /> <link rel="index" title="Index" href="../genindex.html" /> <link rel="search" title="Search" href="../search.html" /> <link rel="copyright" title="Copyright" href="../copyright.html" /> <link rel="next" title="Functional Programming HOWTO" href="functional.html" /> <link rel="prev" title="Curses Programming with Python" href="curses.html" /> <link rel="canonical" href="file:///usr/share/doc/python3.10/html/howto/descriptor.html" /> <style> @media only screen { table.full-width-table { width: 100%; } } </style> <link rel="shortcut icon" type="image/png" href="../_static/py.svg" /> <script type="text/javascript" src="../_static/copybutton.js"></script> <script type="text/javascript" src="../_static/menu.js"></script> </head> <body> <div class="mobile-nav"> <input type="checkbox" id="menuToggler" class="toggler__input" aria-controls="navigation" aria-pressed="false" aria-expanded="false" role="button" aria-label="Menu" /> <label for="menuToggler" class="toggler__label"> <span></span> </label> <nav class="nav-content" role="navigation"> <a href="https://www.python.org/" class="nav-logo"> <img src="../_static/py.svg" alt="Logo"/> </a> <div class="version_switcher_placeholder"></div> <form role="search" class="search" action="../search.html" method="get"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" class="search-icon"> <path fill-rule="nonzero" d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 001.48-5.34c-.47-2.78-2.79-5-5.59-5.34a6.505 6.505 0 00-7.27 7.27c.34 2.8 2.56 5.12 5.34 5.59a6.5 6.5 0 005.34-1.48l.27.28v.79l4.25 4.25c.41.41 1.08.41 1.49 0 .41-.41.41-1.08 0-1.49L15.5 14zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" fill="#444"></path> </svg> <input type="text" name="q" aria-label="Quick search"/> <input type="submit" value="Go"/> </form> </nav> <div class="menu-wrapper"> <nav class="menu" role="navigation" aria-label="main navigation"> <div class="language_switcher_placeholder"></div> <h3><a href="../contents.html">Table of Contents</a></h3> <ul> <li><a class="reference internal" href="#">Descriptor HowTo Guide</a><ul> <li><a class="reference internal" href="#primer">Primer</a><ul> <li><a class="reference internal" href="#simple-example-a-descriptor-that-returns-a-constant">Simple example: A descriptor that returns a constant</a></li> <li><a class="reference internal" href="#dynamic-lookups">Dynamic lookups</a></li> <li><a class="reference internal" href="#managed-attributes">Managed attributes</a></li> <li><a class="reference internal" href="#customized-names">Customized names</a></li> <li><a class="reference internal" href="#closing-thoughts">Closing thoughts</a></li> </ul> </li> <li><a class="reference internal" href="#complete-practical-example">Complete Practical Example</a><ul> <li><a class="reference internal" href="#validator-class">Validator class</a></li> <li><a class="reference internal" href="#custom-validators">Custom validators</a></li> <li><a class="reference internal" href="#practical-application">Practical application</a></li> </ul> </li> <li><a class="reference internal" href="#technical-tutorial">Technical Tutorial</a><ul> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#definition-and-introduction">Definition and introduction</a></li> <li><a class="reference internal" href="#descriptor-protocol">Descriptor protocol</a></li> <li><a class="reference internal" href="#overview-of-descriptor-invocation">Overview of descriptor invocation</a></li> <li><a class="reference internal" href="#invocation-from-an-instance">Invocation from an instance</a></li> <li><a class="reference internal" href="#invocation-from-a-class">Invocation from a class</a></li> <li><a class="reference internal" href="#invocation-from-super">Invocation from super</a></li> <li><a class="reference internal" href="#summary-of-invocation-logic">Summary of invocation logic</a></li> <li><a class="reference internal" href="#automatic-name-notification">Automatic name notification</a></li> <li><a class="reference internal" href="#orm-example">ORM example</a></li> </ul> </li> <li><a class="reference internal" href="#pure-python-equivalents">Pure Python Equivalents</a><ul> <li><a class="reference internal" href="#properties">Properties</a></li> <li><a class="reference internal" href="#functions-and-methods">Functions and methods</a></li> <li><a class="reference internal" href="#kinds-of-methods">Kinds of methods</a></li> <li><a class="reference internal" href="#static-methods">Static methods</a></li> <li><a class="reference internal" href="#class-methods">Class methods</a></li> <li><a class="reference internal" href="#member-objects-and-slots">Member objects and __slots__</a></li> </ul> </li> </ul> </li> </ul> <h4>Previous topic</h4> <p class="topless"><a href="curses.html" title="previous chapter">Curses Programming with Python</a></p> <h4>Next topic</h4> <p class="topless"><a href="functional.html" title="next chapter">Functional Programming HOWTO</a></p> <div role="note" aria-label="source link"> <h3>This Page</h3> <ul class="this-page-menu"> <li><a href="../bugs.html">Report a Bug</a></li> <li> <a href="https://github.com/python/cpython/blob/3.10/Doc/howto/descriptor.rst" rel="nofollow">Show Source </a> </li> </ul> </div> </nav> </div> </div> <div class="related" role="navigation" aria-label="related navigation"> <h3>Navigation</h3> <ul> <li class="right" style="margin-right: 10px"> <a href="../genindex.html" title="General Index" accesskey="I">index</a></li> <li class="right" > <a href="../py-modindex.html" title="Python Module Index" >modules</a> |</li> <li class="right" > <a href="functional.html" title="Functional Programming HOWTO" accesskey="N">next</a> |</li> <li class="right" > <a href="curses.html" title="Curses Programming with Python" accesskey="P">previous</a> |</li> <li><img src="../_static/py.svg" alt="python logo" style="vertical-align: middle; margin-top: -1px"/></li> <li><a href="https://www.python.org/">Python</a> »</li> <li class="switchers"> <div class="language_switcher_placeholder"></div> <div class="version_switcher_placeholder"></div> </li> <li> </li> <li id="cpython-language-and-version"> <a href="../index.html">3.10.12 Documentation</a> » </li> <li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Python HOWTOs</a> »</li> <li class="nav-item nav-item-this"><a href="">Descriptor HowTo Guide</a></li> <li class="right"> <div class="inline-search" role="search"> <form class="inline-search" action="../search.html" method="get"> <input placeholder="Quick search" aria-label="Quick search" type="text" name="q" /> <input type="submit" value="Go" /> <input type="hidden" name="check_keywords" value="yes" /> <input type="hidden" name="area" value="default" /> </form> </div> | </li> </ul> </div> <div class="document"> <div class="documentwrapper"> <div class="bodywrapper"> <div class="body" role="main"> <section id="descriptor-howto-guide"> <span id="descriptorhowto"></span><h1><a class="toc-backref" href="#id1">Descriptor HowTo Guide</a><a class="headerlink" href="#descriptor-howto-guide" title="Permalink to this headline">¶</a></h1> <dl class="field-list simple"> <dt class="field-odd">Author</dt> <dd class="field-odd"><p>Raymond Hettinger</p> </dd> <dt class="field-even">Contact</dt> <dd class="field-even"><p><python at rcn dot com></p> </dd> </dl> <div class="contents topic" id="contents"> <p class="topic-title">Contents</p> <ul class="simple"> <li><p><a class="reference internal" href="#descriptor-howto-guide" id="id1">Descriptor HowTo Guide</a></p> <ul> <li><p><a class="reference internal" href="#primer" id="id2">Primer</a></p> <ul> <li><p><a class="reference internal" href="#simple-example-a-descriptor-that-returns-a-constant" id="id3">Simple example: A descriptor that returns a constant</a></p></li> <li><p><a class="reference internal" href="#dynamic-lookups" id="id4">Dynamic lookups</a></p></li> <li><p><a class="reference internal" href="#managed-attributes" id="id5">Managed attributes</a></p></li> <li><p><a class="reference internal" href="#customized-names" id="id6">Customized names</a></p></li> <li><p><a class="reference internal" href="#closing-thoughts" id="id7">Closing thoughts</a></p></li> </ul> </li> <li><p><a class="reference internal" href="#complete-practical-example" id="id8">Complete Practical Example</a></p> <ul> <li><p><a class="reference internal" href="#validator-class" id="id9">Validator class</a></p></li> <li><p><a class="reference internal" href="#custom-validators" id="id10">Custom validators</a></p></li> <li><p><a class="reference internal" href="#practical-application" id="id11">Practical application</a></p></li> </ul> </li> <li><p><a class="reference internal" href="#technical-tutorial" id="id12">Technical Tutorial</a></p> <ul> <li><p><a class="reference internal" href="#abstract" id="id13">Abstract</a></p></li> <li><p><a class="reference internal" href="#definition-and-introduction" id="id14">Definition and introduction</a></p></li> <li><p><a class="reference internal" href="#descriptor-protocol" id="id15">Descriptor protocol</a></p></li> <li><p><a class="reference internal" href="#overview-of-descriptor-invocation" id="id16">Overview of descriptor invocation</a></p></li> <li><p><a class="reference internal" href="#invocation-from-an-instance" id="id17">Invocation from an instance</a></p></li> <li><p><a class="reference internal" href="#invocation-from-a-class" id="id18">Invocation from a class</a></p></li> <li><p><a class="reference internal" href="#invocation-from-super" id="id19">Invocation from super</a></p></li> <li><p><a class="reference internal" href="#summary-of-invocation-logic" id="id20">Summary of invocation logic</a></p></li> <li><p><a class="reference internal" href="#automatic-name-notification" id="id21">Automatic name notification</a></p></li> <li><p><a class="reference internal" href="#orm-example" id="id22">ORM example</a></p></li> </ul> </li> <li><p><a class="reference internal" href="#pure-python-equivalents" id="id23">Pure Python Equivalents</a></p> <ul> <li><p><a class="reference internal" href="#properties" id="id24">Properties</a></p></li> <li><p><a class="reference internal" href="#functions-and-methods" id="id25">Functions and methods</a></p></li> <li><p><a class="reference internal" href="#kinds-of-methods" id="id26">Kinds of methods</a></p></li> <li><p><a class="reference internal" href="#static-methods" id="id27">Static methods</a></p></li> <li><p><a class="reference internal" href="#class-methods" id="id28">Class methods</a></p></li> <li><p><a class="reference internal" href="#member-objects-and-slots" id="id29">Member objects and __slots__</a></p></li> </ul> </li> </ul> </li> </ul> </div> <p><a class="reference internal" href="../glossary.html#term-descriptor"><span class="xref std std-term">Descriptors</span></a> let objects customize attribute lookup, storage, and deletion.</p> <p>This guide has four major sections:</p> <ol class="arabic simple"> <li><p>The “primer” gives a basic overview, moving gently from simple examples, adding one feature at a time. Start here if you’re new to descriptors.</p></li> <li><p>The second section shows a complete, practical descriptor example. If you already know the basics, start there.</p></li> <li><p>The third section provides a more technical tutorial that goes into the detailed mechanics of how descriptors work. Most people don’t need this level of detail.</p></li> <li><p>The last section has pure Python equivalents for built-in descriptors that are written in C. Read this if you’re curious about how functions turn into bound methods or about the implementation of common tools like <a class="reference internal" href="../library/functions.html#classmethod" title="classmethod"><code class="xref py py-func docutils literal notranslate"><span class="pre">classmethod()</span></code></a>, <a class="reference internal" href="../library/functions.html#staticmethod" title="staticmethod"><code class="xref py py-func docutils literal notranslate"><span class="pre">staticmethod()</span></code></a>, <a class="reference internal" href="../library/functions.html#property" title="property"><code class="xref py py-func docutils literal notranslate"><span class="pre">property()</span></code></a>, and <a class="reference internal" href="../glossary.html#term-__slots__"><span class="xref std std-term">__slots__</span></a>.</p></li> </ol> <section id="primer"> <h2><a class="toc-backref" href="#id2">Primer</a><a class="headerlink" href="#primer" title="Permalink to this headline">¶</a></h2> <p>In this primer, we start with the most basic possible example and then we’ll add new capabilities one by one.</p> <section id="simple-example-a-descriptor-that-returns-a-constant"> <h3><a class="toc-backref" href="#id3">Simple example: A descriptor that returns a constant</a><a class="headerlink" href="#simple-example-a-descriptor-that-returns-a-constant" title="Permalink to this headline">¶</a></h3> <p>The <code class="xref py py-class docutils literal notranslate"><span class="pre">Ten</span></code> class is a descriptor whose <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code> method always returns the constant <code class="docutils literal notranslate"><span class="pre">10</span></code>:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Ten</span><span class="p">:</span> <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="k">return</span> <span class="mi">10</span> </pre></div> </div> <p>To use the descriptor, it must be stored as a class variable in another class:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">A</span><span class="p">:</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">5</span> <span class="c1"># Regular class attribute</span> <span class="n">y</span> <span class="o">=</span> <span class="n">Ten</span><span class="p">()</span> <span class="c1"># Descriptor instance</span> </pre></div> </div> <p>An interactive session shows the difference between normal attribute lookup and descriptor lookup:</p> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">a</span> <span class="o">=</span> <span class="n">A</span><span class="p">()</span> <span class="c1"># Make an instance of class A</span> <span class="gp">>>> </span><span class="n">a</span><span class="o">.</span><span class="n">x</span> <span class="c1"># Normal attribute lookup</span> <span class="go">5</span> <span class="gp">>>> </span><span class="n">a</span><span class="o">.</span><span class="n">y</span> <span class="c1"># Descriptor lookup</span> <span class="go">10</span> </pre></div> </div> <p>In the <code class="docutils literal notranslate"><span class="pre">a.x</span></code> attribute lookup, the dot operator finds <code class="docutils literal notranslate"><span class="pre">'x':</span> <span class="pre">5</span></code> in the class dictionary. In the <code class="docutils literal notranslate"><span class="pre">a.y</span></code> lookup, the dot operator finds a descriptor instance, recognized by its <code class="docutils literal notranslate"><span class="pre">__get__</span></code> method. Calling that method returns <code class="docutils literal notranslate"><span class="pre">10</span></code>.</p> <p>Note that the value <code class="docutils literal notranslate"><span class="pre">10</span></code> is not stored in either the class dictionary or the instance dictionary. Instead, the value <code class="docutils literal notranslate"><span class="pre">10</span></code> is computed on demand.</p> <p>This example shows how a simple descriptor works, but it isn’t very useful. For retrieving constants, normal attribute lookup would be better.</p> <p>In the next section, we’ll create something more useful, a dynamic lookup.</p> </section> <section id="dynamic-lookups"> <h3><a class="toc-backref" href="#id4">Dynamic lookups</a><a class="headerlink" href="#dynamic-lookups" title="Permalink to this headline">¶</a></h3> <p>Interesting descriptors typically run computations instead of returning constants:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">os</span> <span class="k">class</span> <span class="nc">DirectorySize</span><span class="p">:</span> <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">obj</span><span class="o">.</span><span class="n">dirname</span><span class="p">))</span> <span class="k">class</span> <span class="nc">Directory</span><span class="p">:</span> <span class="n">size</span> <span class="o">=</span> <span class="n">DirectorySize</span><span class="p">()</span> <span class="c1"># Descriptor instance</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dirname</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">dirname</span> <span class="o">=</span> <span class="n">dirname</span> <span class="c1"># Regular instance attribute</span> </pre></div> </div> <p>An interactive session shows that the lookup is dynamic — it computes different, updated answers each time:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">s</span> <span class="o">=</span> <span class="n">Directory</span><span class="p">(</span><span class="s1">'songs'</span><span class="p">)</span> <span class="gp">>>> </span><span class="n">g</span> <span class="o">=</span> <span class="n">Directory</span><span class="p">(</span><span class="s1">'games'</span><span class="p">)</span> <span class="gp">>>> </span><span class="n">s</span><span class="o">.</span><span class="n">size</span> <span class="c1"># The songs directory has twenty files</span> <span class="go">20</span> <span class="gp">>>> </span><span class="n">g</span><span class="o">.</span><span class="n">size</span> <span class="c1"># The games directory has three files</span> <span class="go">3</span> <span class="gp">>>> </span><span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="s1">'games/chess'</span><span class="p">)</span> <span class="c1"># Delete a game</span> <span class="gp">>>> </span><span class="n">g</span><span class="o">.</span><span class="n">size</span> <span class="c1"># File count is automatically updated</span> <span class="go">2</span> </pre></div> </div> <p>Besides showing how descriptors can run computations, this example also reveals the purpose of the parameters to <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code>. The <em>self</em> parameter is <em>size</em>, an instance of <em>DirectorySize</em>. The <em>obj</em> parameter is either <em>g</em> or <em>s</em>, an instance of <em>Directory</em>. It is the <em>obj</em> parameter that lets the <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code> method learn the target directory. The <em>objtype</em> parameter is the class <em>Directory</em>.</p> </section> <section id="managed-attributes"> <h3><a class="toc-backref" href="#id5">Managed attributes</a><a class="headerlink" href="#managed-attributes" title="Permalink to this headline">¶</a></h3> <p>A popular use for descriptors is managing access to instance data. The descriptor is assigned to a public attribute in the class dictionary while the actual data is stored as a private attribute in the instance dictionary. The descriptor’s <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code> and <code class="xref py py-meth docutils literal notranslate"><span class="pre">__set__()</span></code> methods are triggered when the public attribute is accessed.</p> <p>In the following example, <em>age</em> is the public attribute and <em>_age</em> is the private attribute. When the public attribute is accessed, the descriptor logs the lookup or update:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span> <span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span> <span class="k">class</span> <span class="nc">LoggedAgeAccess</span><span class="p">:</span> <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="n">value</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="n">_age</span> <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Accessing </span><span class="si">%r</span><span class="s1"> giving </span><span class="si">%r</span><span class="s1">'</span><span class="p">,</span> <span class="s1">'age'</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">return</span> <span class="n">value</span> <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Updating </span><span class="si">%r</span><span class="s1"> to </span><span class="si">%r</span><span class="s1">'</span><span class="p">,</span> <span class="s1">'age'</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="n">obj</span><span class="o">.</span><span class="n">_age</span> <span class="o">=</span> <span class="n">value</span> <span class="k">class</span> <span class="nc">Person</span><span class="p">:</span> <span class="n">age</span> <span class="o">=</span> <span class="n">LoggedAgeAccess</span><span class="p">()</span> <span class="c1"># Descriptor instance</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">age</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span> <span class="c1"># Regular instance attribute</span> <span class="bp">self</span><span class="o">.</span><span class="n">age</span> <span class="o">=</span> <span class="n">age</span> <span class="c1"># Calls __set__()</span> <span class="k">def</span> <span class="nf">birthday</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">age</span> <span class="o">+=</span> <span class="mi">1</span> <span class="c1"># Calls both __get__() and __set__()</span> </pre></div> </div> <p>An interactive session shows that all access to the managed attribute <em>age</em> is logged, but that the regular attribute <em>name</em> is not logged:</p> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">mary</span> <span class="o">=</span> <span class="n">Person</span><span class="p">(</span><span class="s1">'Mary M'</span><span class="p">,</span> <span class="mi">30</span><span class="p">)</span> <span class="c1"># The initial age update is logged</span> <span class="go">INFO:root:Updating 'age' to 30</span> <span class="gp">>>> </span><span class="n">dave</span> <span class="o">=</span> <span class="n">Person</span><span class="p">(</span><span class="s1">'David D'</span><span class="p">,</span> <span class="mi">40</span><span class="p">)</span> <span class="go">INFO:root:Updating 'age' to 40</span> <span class="gp">>>> </span><span class="nb">vars</span><span class="p">(</span><span class="n">mary</span><span class="p">)</span> <span class="c1"># The actual data is in a private attribute</span> <span class="go">{'name': 'Mary M', '_age': 30}</span> <span class="gp">>>> </span><span class="nb">vars</span><span class="p">(</span><span class="n">dave</span><span class="p">)</span> <span class="go">{'name': 'David D', '_age': 40}</span> <span class="gp">>>> </span><span class="n">mary</span><span class="o">.</span><span class="n">age</span> <span class="c1"># Access the data and log the lookup</span> <span class="go">INFO:root:Accessing 'age' giving 30</span> <span class="go">30</span> <span class="gp">>>> </span><span class="n">mary</span><span class="o">.</span><span class="n">birthday</span><span class="p">()</span> <span class="c1"># Updates are logged as well</span> <span class="go">INFO:root:Accessing 'age' giving 30</span> <span class="go">INFO:root:Updating 'age' to 31</span> <span class="gp">>>> </span><span class="n">dave</span><span class="o">.</span><span class="n">name</span> <span class="c1"># Regular attribute lookup isn't logged</span> <span class="go">'David D'</span> <span class="gp">>>> </span><span class="n">dave</span><span class="o">.</span><span class="n">age</span> <span class="c1"># Only the managed attribute is logged</span> <span class="go">INFO:root:Accessing 'age' giving 40</span> <span class="go">40</span> </pre></div> </div> <p>One major issue with this example is that the private name <em>_age</em> is hardwired in the <em>LoggedAgeAccess</em> class. That means that each instance can only have one logged attribute and that its name is unchangeable. In the next example, we’ll fix that problem.</p> </section> <section id="customized-names"> <h3><a class="toc-backref" href="#id6">Customized names</a><a class="headerlink" href="#customized-names" title="Permalink to this headline">¶</a></h3> <p>When a class uses descriptors, it can inform each descriptor about which variable name was used.</p> <p>In this example, the <code class="xref py py-class docutils literal notranslate"><span class="pre">Person</span></code> class has two descriptor instances, <em>name</em> and <em>age</em>. When the <code class="xref py py-class docutils literal notranslate"><span class="pre">Person</span></code> class is defined, it makes a callback to <code class="xref py py-meth docutils literal notranslate"><span class="pre">__set_name__()</span></code> in <em>LoggedAccess</em> so that the field names can be recorded, giving each descriptor its own <em>public_name</em> and <em>private_name</em>:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span> <span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span> <span class="k">class</span> <span class="nc">LoggedAccess</span><span class="p">:</span> <span class="k">def</span> <span class="nf">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">public_name</span> <span class="o">=</span> <span class="n">name</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span> <span class="o">=</span> <span class="s1">'_'</span> <span class="o">+</span> <span class="n">name</span> <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="n">value</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">)</span> <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Accessing </span><span class="si">%r</span><span class="s1"> giving </span><span class="si">%r</span><span class="s1">'</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">public_name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">return</span> <span class="n">value</span> <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Updating </span><span class="si">%r</span><span class="s1"> to </span><span class="si">%r</span><span class="s1">'</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">public_name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">class</span> <span class="nc">Person</span><span class="p">:</span> <span class="n">name</span> <span class="o">=</span> <span class="n">LoggedAccess</span><span class="p">()</span> <span class="c1"># First descriptor instance</span> <span class="n">age</span> <span class="o">=</span> <span class="n">LoggedAccess</span><span class="p">()</span> <span class="c1"># Second descriptor instance</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">age</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span> <span class="c1"># Calls the first descriptor</span> <span class="bp">self</span><span class="o">.</span><span class="n">age</span> <span class="o">=</span> <span class="n">age</span> <span class="c1"># Calls the second descriptor</span> <span class="k">def</span> <span class="nf">birthday</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">age</span> <span class="o">+=</span> <span class="mi">1</span> </pre></div> </div> <p>An interactive session shows that the <code class="xref py py-class docutils literal notranslate"><span class="pre">Person</span></code> class has called <code class="xref py py-meth docutils literal notranslate"><span class="pre">__set_name__()</span></code> so that the field names would be recorded. Here we call <a class="reference internal" href="../library/functions.html#vars" title="vars"><code class="xref py py-func docutils literal notranslate"><span class="pre">vars()</span></code></a> to look up the descriptor without triggering it:</p> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="nb">vars</span><span class="p">(</span><span class="nb">vars</span><span class="p">(</span><span class="n">Person</span><span class="p">)[</span><span class="s1">'name'</span><span class="p">])</span> <span class="go">{'public_name': 'name', 'private_name': '_name'}</span> <span class="gp">>>> </span><span class="nb">vars</span><span class="p">(</span><span class="nb">vars</span><span class="p">(</span><span class="n">Person</span><span class="p">)[</span><span class="s1">'age'</span><span class="p">])</span> <span class="go">{'public_name': 'age', 'private_name': '_age'}</span> </pre></div> </div> <p>The new class now logs access to both <em>name</em> and <em>age</em>:</p> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">pete</span> <span class="o">=</span> <span class="n">Person</span><span class="p">(</span><span class="s1">'Peter P'</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span> <span class="go">INFO:root:Updating 'name' to 'Peter P'</span> <span class="go">INFO:root:Updating 'age' to 10</span> <span class="gp">>>> </span><span class="n">kate</span> <span class="o">=</span> <span class="n">Person</span><span class="p">(</span><span class="s1">'Catherine C'</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span> <span class="go">INFO:root:Updating 'name' to 'Catherine C'</span> <span class="go">INFO:root:Updating 'age' to 20</span> </pre></div> </div> <p>The two <em>Person</em> instances contain only the private names:</p> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="nb">vars</span><span class="p">(</span><span class="n">pete</span><span class="p">)</span> <span class="go">{'_name': 'Peter P', '_age': 10}</span> <span class="gp">>>> </span><span class="nb">vars</span><span class="p">(</span><span class="n">kate</span><span class="p">)</span> <span class="go">{'_name': 'Catherine C', '_age': 20}</span> </pre></div> </div> </section> <section id="closing-thoughts"> <h3><a class="toc-backref" href="#id7">Closing thoughts</a><a class="headerlink" href="#closing-thoughts" title="Permalink to this headline">¶</a></h3> <p>A <a class="reference internal" href="../glossary.html#term-descriptor"><span class="xref std std-term">descriptor</span></a> is what we call any object that defines <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code>, <code class="xref py py-meth docutils literal notranslate"><span class="pre">__set__()</span></code>, or <code class="xref py py-meth docutils literal notranslate"><span class="pre">__delete__()</span></code>.</p> <p>Optionally, descriptors can have a <code class="xref py py-meth docutils literal notranslate"><span class="pre">__set_name__()</span></code> method. This is only used in cases where a descriptor needs to know either the class where it was created or the name of class variable it was assigned to. (This method, if present, is called even if the class is not a descriptor.)</p> <p>Descriptors get invoked by the dot operator during attribute lookup. If a descriptor is accessed indirectly with <code class="docutils literal notranslate"><span class="pre">vars(some_class)[descriptor_name]</span></code>, the descriptor instance is returned without invoking it.</p> <p>Descriptors only work when used as class variables. When put in instances, they have no effect.</p> <p>The main motivation for descriptors is to provide a hook allowing objects stored in class variables to control what happens during attribute lookup.</p> <p>Traditionally, the calling class controls what happens during lookup. Descriptors invert that relationship and allow the data being looked-up to have a say in the matter.</p> <p>Descriptors are used throughout the language. It is how functions turn into bound methods. Common tools like <a class="reference internal" href="../library/functions.html#classmethod" title="classmethod"><code class="xref py py-func docutils literal notranslate"><span class="pre">classmethod()</span></code></a>, <a class="reference internal" href="../library/functions.html#staticmethod" title="staticmethod"><code class="xref py py-func docutils literal notranslate"><span class="pre">staticmethod()</span></code></a>, <a class="reference internal" href="../library/functions.html#property" title="property"><code class="xref py py-func docutils literal notranslate"><span class="pre">property()</span></code></a>, and <a class="reference internal" href="../library/functools.html#functools.cached_property" title="functools.cached_property"><code class="xref py py-func docutils literal notranslate"><span class="pre">functools.cached_property()</span></code></a> are all implemented as descriptors.</p> </section> </section> <section id="complete-practical-example"> <h2><a class="toc-backref" href="#id8">Complete Practical Example</a><a class="headerlink" href="#complete-practical-example" title="Permalink to this headline">¶</a></h2> <p>In this example, we create a practical and powerful tool for locating notoriously hard to find data corruption bugs.</p> <section id="validator-class"> <h3><a class="toc-backref" href="#id9">Validator class</a><a class="headerlink" href="#validator-class" title="Permalink to this headline">¶</a></h3> <p>A validator is a descriptor for managed attribute access. Prior to storing any data, it verifies that the new value meets various type and range restrictions. If those restrictions aren’t met, it raises an exception to prevent data corruption at its source.</p> <p>This <code class="xref py py-class docutils literal notranslate"><span class="pre">Validator</span></code> class is both an <a class="reference internal" href="../glossary.html#term-abstract-base-class"><span class="xref std std-term">abstract base class</span></a> and a managed attribute descriptor:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span> <span class="k">class</span> <span class="nc">Validator</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span> <span class="k">def</span> <span class="nf">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span> <span class="o">=</span> <span class="s1">'_'</span> <span class="o">+</span> <span class="n">name</span> <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">)</span> <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="bp">self</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">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="nd">@abstractmethod</span> <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="k">pass</span> </pre></div> </div> <p>Custom validators need to inherit from <code class="xref py py-class docutils literal notranslate"><span class="pre">Validator</span></code> and must supply a <code class="xref py py-meth docutils literal notranslate"><span class="pre">validate()</span></code> method to test various restrictions as needed.</p> </section> <section id="custom-validators"> <h3><a class="toc-backref" href="#id10">Custom validators</a><a class="headerlink" href="#custom-validators" title="Permalink to this headline">¶</a></h3> <p>Here are three practical data validation utilities:</p> <ol class="arabic simple"> <li><p><code class="xref py py-class docutils literal notranslate"><span class="pre">OneOf</span></code> verifies that a value is one of a restricted set of options.</p></li> <li><p><code class="xref py py-class docutils literal notranslate"><span class="pre">Number</span></code> verifies that a value is either an <a class="reference internal" href="../library/functions.html#int" title="int"><code class="xref py py-class docutils literal notranslate"><span class="pre">int</span></code></a> or <a class="reference internal" href="../library/functions.html#float" title="float"><code class="xref py py-class docutils literal notranslate"><span class="pre">float</span></code></a>. Optionally, it verifies that a value is between a given minimum or maximum.</p></li> <li><p><code class="xref py py-class docutils literal notranslate"><span class="pre">String</span></code> verifies that a value is a <a class="reference internal" href="../library/stdtypes.html#str" title="str"><code class="xref py py-class docutils literal notranslate"><span class="pre">str</span></code></a>. Optionally, it validates a given minimum or maximum length. It can validate a user-defined <a class="reference external" href="https://en.wikipedia.org/wiki/Predicate_(mathematical_logic)">predicate</a> as well.</p></li> </ol> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">OneOf</span><span class="p">(</span><span class="n">Validator</span><span class="p">):</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">options</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">options</span><span class="p">)</span> <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="k">if</span> <span class="n">value</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s1">'Expected </span><span class="si">{</span><span class="n">value</span><span class="si">!r}</span><span class="s1"> to be one of </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="si">!r}</span><span class="s1">'</span><span class="p">)</span> <span class="k">class</span> <span class="nc">Number</span><span class="p">(</span><span class="n">Validator</span><span class="p">):</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">minvalue</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">maxvalue</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">minvalue</span> <span class="o">=</span> <span class="n">minvalue</span> <span class="bp">self</span><span class="o">.</span><span class="n">maxvalue</span> <span class="o">=</span> <span class="n">maxvalue</span> <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="p">(</span><span class="nb">int</span><span class="p">,</span> <span class="nb">float</span><span class="p">)):</span> <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s1">'Expected </span><span class="si">{</span><span class="n">value</span><span class="si">!r}</span><span class="s1"> to be an int or float'</span><span class="p">)</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">minvalue</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o"><</span> <span class="bp">self</span><span class="o">.</span><span class="n">minvalue</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span> <span class="sa">f</span><span class="s1">'Expected </span><span class="si">{</span><span class="n">value</span><span class="si">!r}</span><span class="s1"> to be at least </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">minvalue</span><span class="si">!r}</span><span class="s1">'</span> <span class="p">)</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">maxvalue</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">></span> <span class="bp">self</span><span class="o">.</span><span class="n">maxvalue</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span> <span class="sa">f</span><span class="s1">'Expected </span><span class="si">{</span><span class="n">value</span><span class="si">!r}</span><span class="s1"> to be no more than </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">maxvalue</span><span class="si">!r}</span><span class="s1">'</span> <span class="p">)</span> <span class="k">class</span> <span class="nc">String</span><span class="p">(</span><span class="n">Validator</span><span class="p">):</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">minsize</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">maxsize</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">predicate</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">minsize</span> <span class="o">=</span> <span class="n">minsize</span> <span class="bp">self</span><span class="o">.</span><span class="n">maxsize</span> <span class="o">=</span> <span class="n">maxsize</span> <span class="bp">self</span><span class="o">.</span><span class="n">predicate</span> <span class="o">=</span> <span class="n">predicate</span> <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span> <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s1">'Expected </span><span class="si">{</span><span class="n">value</span><span class="si">!r}</span><span class="s1"> to be an str'</span><span class="p">)</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">minsize</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o"><</span> <span class="bp">self</span><span class="o">.</span><span class="n">minsize</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span> <span class="sa">f</span><span class="s1">'Expected </span><span class="si">{</span><span class="n">value</span><span class="si">!r}</span><span class="s1"> to be no smaller than </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">minsize</span><span class="si">!r}</span><span class="s1">'</span> <span class="p">)</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">maxsize</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">></span> <span class="bp">self</span><span class="o">.</span><span class="n">maxsize</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span> <span class="sa">f</span><span class="s1">'Expected </span><span class="si">{</span><span class="n">value</span><span class="si">!r}</span><span class="s1"> to be no bigger than </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">maxsize</span><span class="si">!r}</span><span class="s1">'</span> <span class="p">)</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">predicate</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">predicate</span><span class="p">(</span><span class="n">value</span><span class="p">):</span> <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span> <span class="sa">f</span><span class="s1">'Expected </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">predicate</span><span class="si">}</span><span class="s1"> to be true for </span><span class="si">{</span><span class="n">value</span><span class="si">!r}</span><span class="s1">'</span> <span class="p">)</span> </pre></div> </div> </section> <section id="practical-application"> <h3><a class="toc-backref" href="#id11">Practical application</a><a class="headerlink" href="#practical-application" title="Permalink to this headline">¶</a></h3> <p>Here’s how the data validators can be used in a real class:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Component</span><span class="p">:</span> <span class="n">name</span> <span class="o">=</span> <span class="n">String</span><span class="p">(</span><span class="n">minsize</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">maxsize</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">predicate</span><span class="o">=</span><span class="nb">str</span><span class="o">.</span><span class="n">isupper</span><span class="p">)</span> <span class="n">kind</span> <span class="o">=</span> <span class="n">OneOf</span><span class="p">(</span><span class="s1">'wood'</span><span class="p">,</span> <span class="s1">'metal'</span><span class="p">,</span> <span class="s1">'plastic'</span><span class="p">)</span> <span class="n">quantity</span> <span class="o">=</span> <span class="n">Number</span><span class="p">(</span><span class="n">minvalue</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">kind</span><span class="p">,</span> <span class="n">quantity</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span> <span class="bp">self</span><span class="o">.</span><span class="n">kind</span> <span class="o">=</span> <span class="n">kind</span> <span class="bp">self</span><span class="o">.</span><span class="n">quantity</span> <span class="o">=</span> <span class="n">quantity</span> </pre></div> </div> <p>The descriptors prevent invalid instances from being created:</p> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">Component</span><span class="p">(</span><span class="s1">'Widget'</span><span class="p">,</span> <span class="s1">'metal'</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span> <span class="c1"># Blocked: 'Widget' is not all uppercase</span> <span class="gt">Traceback (most recent call last):</span> <span class="o">...</span> <span class="gr">ValueError</span>: <span class="n">Expected <method 'isupper' of 'str' objects> to be true for 'Widget'</span> <span class="gp">>>> </span><span class="n">Component</span><span class="p">(</span><span class="s1">'WIDGET'</span><span class="p">,</span> <span class="s1">'metle'</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span> <span class="c1"># Blocked: 'metle' is misspelled</span> <span class="gt">Traceback (most recent call last):</span> <span class="o">...</span> <span class="gr">ValueError</span>: <span class="n">Expected 'metle' to be one of {'metal', 'plastic', 'wood'}</span> <span class="gp">>>> </span><span class="n">Component</span><span class="p">(</span><span class="s1">'WIDGET'</span><span class="p">,</span> <span class="s1">'metal'</span><span class="p">,</span> <span class="o">-</span><span class="mi">5</span><span class="p">)</span> <span class="c1"># Blocked: -5 is negative</span> <span class="gt">Traceback (most recent call last):</span> <span class="o">...</span> <span class="gr">ValueError</span>: <span class="n">Expected -5 to be at least 0</span> <span class="gp">>>> </span><span class="n">Component</span><span class="p">(</span><span class="s1">'WIDGET'</span><span class="p">,</span> <span class="s1">'metal'</span><span class="p">,</span> <span class="s1">'V'</span><span class="p">)</span> <span class="c1"># Blocked: 'V' isn't a number</span> <span class="gt">Traceback (most recent call last):</span> <span class="o">...</span> <span class="gr">TypeError</span>: <span class="n">Expected 'V' to be an int or float</span> <span class="gp">>>> </span><span class="n">c</span> <span class="o">=</span> <span class="n">Component</span><span class="p">(</span><span class="s1">'WIDGET'</span><span class="p">,</span> <span class="s1">'metal'</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span> <span class="c1"># Allowed: The inputs are valid</span> </pre></div> </div> </section> </section> <section id="technical-tutorial"> <h2><a class="toc-backref" href="#id12">Technical Tutorial</a><a class="headerlink" href="#technical-tutorial" title="Permalink to this headline">¶</a></h2> <p>What follows is a more technical tutorial for the mechanics and details of how descriptors work.</p> <section id="abstract"> <h3><a class="toc-backref" href="#id13">Abstract</a><a class="headerlink" href="#abstract" title="Permalink to this headline">¶</a></h3> <p>Defines descriptors, summarizes the protocol, and shows how descriptors are called. Provides an example showing how object relational mappings work.</p> <p>Learning about descriptors not only provides access to a larger toolset, it creates a deeper understanding of how Python works.</p> </section> <section id="definition-and-introduction"> <h3><a class="toc-backref" href="#id14">Definition and introduction</a><a class="headerlink" href="#definition-and-introduction" title="Permalink to this headline">¶</a></h3> <p>In general, a descriptor is an attribute value that has one of the methods in the descriptor protocol. Those methods are <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code>, <code class="xref py py-meth docutils literal notranslate"><span class="pre">__set__()</span></code>, and <code class="xref py py-meth docutils literal notranslate"><span class="pre">__delete__()</span></code>. If any of those methods are defined for an attribute, it is said to be a <a class="reference internal" href="../glossary.html#term-descriptor"><span class="xref std std-term">descriptor</span></a>.</p> <p>The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, <code class="docutils literal notranslate"><span class="pre">a.x</span></code> has a lookup chain starting with <code class="docutils literal notranslate"><span class="pre">a.__dict__['x']</span></code>, then <code class="docutils literal notranslate"><span class="pre">type(a).__dict__['x']</span></code>, and continuing through the method resolution order of <code class="docutils literal notranslate"><span class="pre">type(a)</span></code>. If the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined.</p> <p>Descriptors are a powerful, general purpose protocol. They are the mechanism behind properties, methods, static methods, class methods, and <a class="reference internal" href="../library/functions.html#super" title="super"><code class="xref py py-func docutils literal notranslate"><span class="pre">super()</span></code></a>. They are used throughout Python itself. Descriptors simplify the underlying C code and offer a flexible set of new tools for everyday Python programs.</p> </section> <section id="descriptor-protocol"> <h3><a class="toc-backref" href="#id15">Descriptor protocol</a><a class="headerlink" href="#descriptor-protocol" title="Permalink to this headline">¶</a></h3> <p><code class="docutils literal notranslate"><span class="pre">descr.__get__(self,</span> <span class="pre">obj,</span> <span class="pre">type=None)</span> <span class="pre">-></span> <span class="pre">value</span></code></p> <p><code class="docutils literal notranslate"><span class="pre">descr.__set__(self,</span> <span class="pre">obj,</span> <span class="pre">value)</span> <span class="pre">-></span> <span class="pre">None</span></code></p> <p><code class="docutils literal notranslate"><span class="pre">descr.__delete__(self,</span> <span class="pre">obj)</span> <span class="pre">-></span> <span class="pre">None</span></code></p> <p>That is all there is to it. Define any of these methods and an object is considered a descriptor and can override default behavior upon being looked up as an attribute.</p> <p>If an object defines <code class="xref py py-meth docutils literal notranslate"><span class="pre">__set__()</span></code> or <code class="xref py py-meth docutils literal notranslate"><span class="pre">__delete__()</span></code>, it is considered a data descriptor. Descriptors that only define <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code> are called non-data descriptors (they are often used for methods but other uses are possible).</p> <p>Data and non-data descriptors differ in how overrides are calculated with respect to entries in an instance’s dictionary. If an instance’s dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence. If an instance’s dictionary has an entry with the same name as a non-data descriptor, the dictionary entry takes precedence.</p> <p>To make a read-only data descriptor, define both <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code> and <code class="xref py py-meth docutils literal notranslate"><span class="pre">__set__()</span></code> with the <code class="xref py py-meth docutils literal notranslate"><span class="pre">__set__()</span></code> raising an <a class="reference internal" href="../library/exceptions.html#AttributeError" title="AttributeError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">AttributeError</span></code></a> when called. Defining the <code class="xref py py-meth docutils literal notranslate"><span class="pre">__set__()</span></code> method with an exception raising placeholder is enough to make it a data descriptor.</p> </section> <section id="overview-of-descriptor-invocation"> <h3><a class="toc-backref" href="#id16">Overview of descriptor invocation</a><a class="headerlink" href="#overview-of-descriptor-invocation" title="Permalink to this headline">¶</a></h3> <p>A descriptor can be called directly with <code class="docutils literal notranslate"><span class="pre">desc.__get__(obj)</span></code> or <code class="docutils literal notranslate"><span class="pre">desc.__get__(None,</span> <span class="pre">cls)</span></code>.</p> <p>But it is more common for a descriptor to be invoked automatically from attribute access.</p> <p>The expression <code class="docutils literal notranslate"><span class="pre">obj.x</span></code> looks up the attribute <code class="docutils literal notranslate"><span class="pre">x</span></code> in the chain of namespaces for <code class="docutils literal notranslate"><span class="pre">obj</span></code>. If the search finds a descriptor outside of the instance <code class="docutils literal notranslate"><span class="pre">__dict__</span></code>, its <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code> method is invoked according to the precedence rules listed below.</p> <p>The details of invocation depend on whether <code class="docutils literal notranslate"><span class="pre">obj</span></code> is an object, class, or instance of super.</p> </section> <section id="invocation-from-an-instance"> <h3><a class="toc-backref" href="#id17">Invocation from an instance</a><a class="headerlink" href="#invocation-from-an-instance" title="Permalink to this headline">¶</a></h3> <p>Instance lookup scans through a chain of namespaces giving data descriptors the highest priority, followed by instance variables, then non-data descriptors, then class variables, and lastly <code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattr__()</span></code> if it is provided.</p> <p>If a descriptor is found for <code class="docutils literal notranslate"><span class="pre">a.x</span></code>, then it is invoked with: <code class="docutils literal notranslate"><span class="pre">desc.__get__(a,</span> <span class="pre">type(a))</span></code>.</p> <p>The logic for a dotted lookup is in <a class="reference internal" href="../reference/datamodel.html#object.__getattribute__" title="object.__getattribute__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">object.__getattribute__()</span></code></a>. Here is a pure Python equivalent:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">find_name_in_mro</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">default</span><span class="p">):</span> <span class="s2">"Emulate _PyType_Lookup() in Objects/typeobject.c"</span> <span class="k">for</span> <span class="n">base</span> <span class="ow">in</span> <span class="bp">cls</span><span class="o">.</span><span class="vm">__mro__</span><span class="p">:</span> <span class="k">if</span> <span class="n">name</span> <span class="ow">in</span> <span class="nb">vars</span><span class="p">(</span><span class="n">base</span><span class="p">):</span> <span class="k">return</span> <span class="nb">vars</span><span class="p">(</span><span class="n">base</span><span class="p">)[</span><span class="n">name</span><span class="p">]</span> <span class="k">return</span> <span class="n">default</span> <span class="k">def</span> <span class="nf">object_getattribute</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span> <span class="s2">"Emulate PyObject_GenericGetAttr() in Objects/object.c"</span> <span class="n">null</span> <span class="o">=</span> <span class="nb">object</span><span class="p">()</span> <span class="n">objtype</span> <span class="o">=</span> <span class="nb">type</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span> <span class="n">cls_var</span> <span class="o">=</span> <span class="n">find_name_in_mro</span><span class="p">(</span><span class="n">objtype</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">null</span><span class="p">)</span> <span class="n">descr_get</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">cls_var</span><span class="p">),</span> <span class="s1">'__get__'</span><span class="p">,</span> <span class="n">null</span><span class="p">)</span> <span class="k">if</span> <span class="n">descr_get</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">null</span><span class="p">:</span> <span class="k">if</span> <span class="p">(</span><span class="nb">hasattr</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">cls_var</span><span class="p">),</span> <span class="s1">'__set__'</span><span class="p">)</span> <span class="ow">or</span> <span class="nb">hasattr</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">cls_var</span><span class="p">),</span> <span class="s1">'__delete__'</span><span class="p">)):</span> <span class="k">return</span> <span class="n">descr_get</span><span class="p">(</span><span class="n">cls_var</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="p">)</span> <span class="c1"># data descriptor</span> <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="s1">'__dict__'</span><span class="p">)</span> <span class="ow">and</span> <span class="n">name</span> <span class="ow">in</span> <span class="nb">vars</span><span class="p">(</span><span class="n">obj</span><span class="p">):</span> <span class="k">return</span> <span class="nb">vars</span><span class="p">(</span><span class="n">obj</span><span class="p">)[</span><span class="n">name</span><span class="p">]</span> <span class="c1"># instance variable</span> <span class="k">if</span> <span class="n">descr_get</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">null</span><span class="p">:</span> <span class="k">return</span> <span class="n">descr_get</span><span class="p">(</span><span class="n">cls_var</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="p">)</span> <span class="c1"># non-data descriptor</span> <span class="k">if</span> <span class="n">cls_var</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">null</span><span class="p">:</span> <span class="k">return</span> <span class="n">cls_var</span> <span class="c1"># class variable</span> <span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> </pre></div> </div> <p>Note, there is no <code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattr__()</span></code> hook in the <code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattribute__()</span></code> code. That is why calling <code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattribute__()</span></code> directly or with <code class="docutils literal notranslate"><span class="pre">super().__getattribute__</span></code> will bypass <code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattr__()</span></code> entirely.</p> <p>Instead, it is the dot operator and the <a class="reference internal" href="../library/functions.html#getattr" title="getattr"><code class="xref py py-func docutils literal notranslate"><span class="pre">getattr()</span></code></a> function that are responsible for invoking <code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattr__()</span></code> whenever <code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattribute__()</span></code> raises an <a class="reference internal" href="../library/exceptions.html#AttributeError" title="AttributeError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">AttributeError</span></code></a>. Their logic is encapsulated in a helper function:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">getattr_hook</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span> <span class="s2">"Emulate slot_tp_getattr_hook() in Objects/typeobject.c"</span> <span class="k">try</span><span class="p">:</span> <span class="k">return</span> <span class="n">obj</span><span class="o">.</span><span class="fm">__getattribute__</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">hasattr</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">obj</span><span class="p">),</span> <span class="s1">'__getattr__'</span><span class="p">):</span> <span class="k">raise</span> <span class="k">return</span> <span class="nb">type</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span><span class="o">.</span><span class="fm">__getattr__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="c1"># __getattr__</span> </pre></div> </div> </section> <section id="invocation-from-a-class"> <h3><a class="toc-backref" href="#id18">Invocation from a class</a><a class="headerlink" href="#invocation-from-a-class" title="Permalink to this headline">¶</a></h3> <p>The logic for a dotted lookup such as <code class="docutils literal notranslate"><span class="pre">A.x</span></code> is in <code class="xref py py-meth docutils literal notranslate"><span class="pre">type.__getattribute__()</span></code>. The steps are similar to those for <a class="reference internal" href="../reference/datamodel.html#object.__getattribute__" title="object.__getattribute__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">object.__getattribute__()</span></code></a> but the instance dictionary lookup is replaced by a search through the class’s <a class="reference internal" href="../glossary.html#term-method-resolution-order"><span class="xref std std-term">method resolution order</span></a>.</p> <p>If a descriptor is found, it is invoked with <code class="docutils literal notranslate"><span class="pre">desc.__get__(None,</span> <span class="pre">A)</span></code>.</p> <p>The full C implementation can be found in <code class="xref c c-func docutils literal notranslate"><span class="pre">type_getattro()</span></code> and <code class="xref c c-func docutils literal notranslate"><span class="pre">_PyType_Lookup()</span></code> in <a class="reference external" href="https://github.com/python/cpython/tree/3.10/Objects/typeobject.c">Objects/typeobject.c</a>.</p> </section> <section id="invocation-from-super"> <h3><a class="toc-backref" href="#id19">Invocation from super</a><a class="headerlink" href="#invocation-from-super" title="Permalink to this headline">¶</a></h3> <p>The logic for super’s dotted lookup is in the <code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattribute__()</span></code> method for object returned by <a class="reference internal" href="../library/functions.html#super" title="super"><code class="xref py py-class docutils literal notranslate"><span class="pre">super()</span></code></a>.</p> <p>A dotted lookup such as <code class="docutils literal notranslate"><span class="pre">super(A,</span> <span class="pre">obj).m</span></code> searches <code class="docutils literal notranslate"><span class="pre">obj.__class__.__mro__</span></code> for the base class <code class="docutils literal notranslate"><span class="pre">B</span></code> immediately following <code class="docutils literal notranslate"><span class="pre">A</span></code> and then returns <code class="docutils literal notranslate"><span class="pre">B.__dict__['m'].__get__(obj,</span> <span class="pre">A)</span></code>. If not a descriptor, <code class="docutils literal notranslate"><span class="pre">m</span></code> is returned unchanged.</p> <p>The full C implementation can be found in <code class="xref c c-func docutils literal notranslate"><span class="pre">super_getattro()</span></code> in <a class="reference external" href="https://github.com/python/cpython/tree/3.10/Objects/typeobject.c">Objects/typeobject.c</a>. A pure Python equivalent can be found in <a class="reference external" href="https://www.python.org/download/releases/2.2.3/descrintro/#cooperation">Guido’s Tutorial</a>.</p> </section> <section id="summary-of-invocation-logic"> <h3><a class="toc-backref" href="#id20">Summary of invocation logic</a><a class="headerlink" href="#summary-of-invocation-logic" title="Permalink to this headline">¶</a></h3> <p>The mechanism for descriptors is embedded in the <code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattribute__()</span></code> methods for <a class="reference internal" href="../library/functions.html#object" title="object"><code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></a>, <a class="reference internal" href="../library/functions.html#type" title="type"><code class="xref py py-class docutils literal notranslate"><span class="pre">type</span></code></a>, and <a class="reference internal" href="../library/functions.html#super" title="super"><code class="xref py py-func docutils literal notranslate"><span class="pre">super()</span></code></a>.</p> <p>The important points to remember are:</p> <ul class="simple"> <li><p>Descriptors are invoked by the <code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattribute__()</span></code> method.</p></li> <li><p>Classes inherit this machinery from <a class="reference internal" href="../library/functions.html#object" title="object"><code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></a>, <a class="reference internal" href="../library/functions.html#type" title="type"><code class="xref py py-class docutils literal notranslate"><span class="pre">type</span></code></a>, or <a class="reference internal" href="../library/functions.html#super" title="super"><code class="xref py py-func docutils literal notranslate"><span class="pre">super()</span></code></a>.</p></li> <li><p>Overriding <code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattribute__()</span></code> prevents automatic descriptor calls because all the descriptor logic is in that method.</p></li> <li><p><a class="reference internal" href="../reference/datamodel.html#object.__getattribute__" title="object.__getattribute__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">object.__getattribute__()</span></code></a> and <code class="xref py py-meth docutils literal notranslate"><span class="pre">type.__getattribute__()</span></code> make different calls to <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code>. The first includes the instance and may include the class. The second puts in <code class="docutils literal notranslate"><span class="pre">None</span></code> for the instance and always includes the class.</p></li> <li><p>Data descriptors always override instance dictionaries.</p></li> <li><p>Non-data descriptors may be overridden by instance dictionaries.</p></li> </ul> </section> <section id="automatic-name-notification"> <h3><a class="toc-backref" href="#id21">Automatic name notification</a><a class="headerlink" href="#automatic-name-notification" title="Permalink to this headline">¶</a></h3> <p>Sometimes it is desirable for a descriptor to know what class variable name it was assigned to. When a new class is created, the <a class="reference internal" href="../library/functions.html#type" title="type"><code class="xref py py-class docutils literal notranslate"><span class="pre">type</span></code></a> metaclass scans the dictionary of the new class. If any of the entries are descriptors and if they define <code class="xref py py-meth docutils literal notranslate"><span class="pre">__set_name__()</span></code>, that method is called with two arguments. The <em>owner</em> is the class where the descriptor is used, and the <em>name</em> is the class variable the descriptor was assigned to.</p> <p>The implementation details are in <code class="xref c c-func docutils literal notranslate"><span class="pre">type_new()</span></code> and <code class="xref c c-func docutils literal notranslate"><span class="pre">set_names()</span></code> in <a class="reference external" href="https://github.com/python/cpython/tree/3.10/Objects/typeobject.c">Objects/typeobject.c</a>.</p> <p>Since the update logic is in <code class="xref py py-meth docutils literal notranslate"><span class="pre">type.__new__()</span></code>, notifications only take place at the time of class creation. If descriptors are added to the class afterwards, <code class="xref py py-meth docutils literal notranslate"><span class="pre">__set_name__()</span></code> will need to be called manually.</p> </section> <section id="orm-example"> <h3><a class="toc-backref" href="#id22">ORM example</a><a class="headerlink" href="#orm-example" title="Permalink to this headline">¶</a></h3> <p>The following code is a simplified skeleton showing how data descriptors could be used to implement an <a class="reference external" href="https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping">object relational mapping</a>.</p> <p>The essential idea is that the data is stored in an external database. The Python instances only hold keys to the database’s tables. Descriptors take care of lookups or updates:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Field</span><span class="p">:</span> <span class="k">def</span> <span class="nf">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">fetch</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'SELECT </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s1"> FROM </span><span class="si">{</span><span class="n">owner</span><span class="o">.</span><span class="n">table</span><span class="si">}</span><span class="s1"> WHERE </span><span class="si">{</span><span class="n">owner</span><span class="o">.</span><span class="n">key</span><span class="si">}</span><span class="s1">=?;'</span> <span class="bp">self</span><span class="o">.</span><span class="n">store</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'UPDATE </span><span class="si">{</span><span class="n">owner</span><span class="o">.</span><span class="n">table</span><span class="si">}</span><span class="s1"> SET </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s1">=? WHERE </span><span class="si">{</span><span class="n">owner</span><span class="o">.</span><span class="n">key</span><span class="si">}</span><span class="s1">=?;'</span> <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="k">return</span> <span class="n">conn</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">fetch</span><span class="p">,</span> <span class="p">[</span><span class="n">obj</span><span class="o">.</span><span class="n">key</span><span class="p">])</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span> <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="n">conn</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">store</span><span class="p">,</span> <span class="p">[</span><span class="n">value</span><span class="p">,</span> <span class="n">obj</span><span class="o">.</span><span class="n">key</span><span class="p">])</span> <span class="n">conn</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span> </pre></div> </div> <p>We can use the <code class="xref py py-class docutils literal notranslate"><span class="pre">Field</span></code> class to define <a class="reference external" href="https://en.wikipedia.org/wiki/Database_model">models</a> that describe the schema for each table in a database:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Movie</span><span class="p">:</span> <span class="n">table</span> <span class="o">=</span> <span class="s1">'Movies'</span> <span class="c1"># Table name</span> <span class="n">key</span> <span class="o">=</span> <span class="s1">'title'</span> <span class="c1"># Primary key</span> <span class="n">director</span> <span class="o">=</span> <span class="n">Field</span><span class="p">()</span> <span class="n">year</span> <span class="o">=</span> <span class="n">Field</span><span class="p">()</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span> <span class="k">class</span> <span class="nc">Song</span><span class="p">:</span> <span class="n">table</span> <span class="o">=</span> <span class="s1">'Music'</span> <span class="n">key</span> <span class="o">=</span> <span class="s1">'title'</span> <span class="n">artist</span> <span class="o">=</span> <span class="n">Field</span><span class="p">()</span> <span class="n">year</span> <span class="o">=</span> <span class="n">Field</span><span class="p">()</span> <span class="n">genre</span> <span class="o">=</span> <span class="n">Field</span><span class="p">()</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span> </pre></div> </div> <p>To use the models, first connect to the database:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="kn">import</span> <span class="nn">sqlite3</span> <span class="gp">>>> </span><span class="n">conn</span> <span class="o">=</span> <span class="n">sqlite3</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s1">'entertainment.db'</span><span class="p">)</span> </pre></div> </div> <p>An interactive session shows how data is retrieved from the database and how it can be updated:</p> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">Movie</span><span class="p">(</span><span class="s1">'Star Wars'</span><span class="p">)</span><span class="o">.</span><span class="n">director</span> <span class="go">'George Lucas'</span> <span class="gp">>>> </span><span class="n">jaws</span> <span class="o">=</span> <span class="n">Movie</span><span class="p">(</span><span class="s1">'Jaws'</span><span class="p">)</span> <span class="gp">>>> </span><span class="sa">f</span><span class="s1">'Released in </span><span class="si">{</span><span class="n">jaws</span><span class="o">.</span><span class="n">year</span><span class="si">}</span><span class="s1"> by </span><span class="si">{</span><span class="n">jaws</span><span class="o">.</span><span class="n">director</span><span class="si">}</span><span class="s1">'</span> <span class="go">'Released in 1975 by Steven Spielberg'</span> <span class="gp">>>> </span><span class="n">Song</span><span class="p">(</span><span class="s1">'Country Roads'</span><span class="p">)</span><span class="o">.</span><span class="n">artist</span> <span class="go">'John Denver'</span> <span class="gp">>>> </span><span class="n">Movie</span><span class="p">(</span><span class="s1">'Star Wars'</span><span class="p">)</span><span class="o">.</span><span class="n">director</span> <span class="o">=</span> <span class="s1">'J.J. Abrams'</span> <span class="gp">>>> </span><span class="n">Movie</span><span class="p">(</span><span class="s1">'Star Wars'</span><span class="p">)</span><span class="o">.</span><span class="n">director</span> <span class="go">'J.J. Abrams'</span> </pre></div> </div> </section> </section> <section id="pure-python-equivalents"> <h2><a class="toc-backref" href="#id23">Pure Python Equivalents</a><a class="headerlink" href="#pure-python-equivalents" title="Permalink to this headline">¶</a></h2> <p>The descriptor protocol is simple and offers exciting possibilities. Several use cases are so common that they have been prepackaged into built-in tools. Properties, bound methods, static methods, class methods, and __slots__ are all based on the descriptor protocol.</p> <section id="properties"> <h3><a class="toc-backref" href="#id24">Properties</a><a class="headerlink" href="#properties" title="Permalink to this headline">¶</a></h3> <p>Calling <a class="reference internal" href="../library/functions.html#property" title="property"><code class="xref py py-func docutils literal notranslate"><span class="pre">property()</span></code></a> is a succinct way of building a data descriptor that triggers a function call upon access to an attribute. Its signature is:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="nb">property</span><span class="p">(</span><span class="n">fget</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">fset</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">fdel</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">doc</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span> <span class="o">-></span> <span class="nb">property</span> </pre></div> </div> <p>The documentation shows a typical use to define a managed attribute <code class="docutils literal notranslate"><span class="pre">x</span></code>:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">C</span><span class="p">:</span> <span class="k">def</span> <span class="nf">getx</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">__x</span> <span class="k">def</span> <span class="nf">setx</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">__x</span> <span class="o">=</span> <span class="n">value</span> <span class="k">def</span> <span class="nf">delx</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">__x</span> <span class="n">x</span> <span class="o">=</span> <span class="nb">property</span><span class="p">(</span><span class="n">getx</span><span class="p">,</span> <span class="n">setx</span><span class="p">,</span> <span class="n">delx</span><span class="p">,</span> <span class="s2">"I'm the 'x' property."</span><span class="p">)</span> </pre></div> </div> <p>To see how <a class="reference internal" href="../library/functions.html#property" title="property"><code class="xref py py-func docutils literal notranslate"><span class="pre">property()</span></code></a> is implemented in terms of the descriptor protocol, here is a pure Python equivalent:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Property</span><span class="p">:</span> <span class="s2">"Emulate PyProperty_Type() in Objects/descrobject.c"</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fget</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">fset</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">fdel</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">doc</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">fget</span> <span class="o">=</span> <span class="n">fget</span> <span class="bp">self</span><span class="o">.</span><span class="n">fset</span> <span class="o">=</span> <span class="n">fset</span> <span class="bp">self</span><span class="o">.</span><span class="n">fdel</span> <span class="o">=</span> <span class="n">fdel</span> <span class="k">if</span> <span class="n">doc</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">fget</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span> <span class="n">doc</span> <span class="o">=</span> <span class="n">fget</span><span class="o">.</span><span class="vm">__doc__</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__doc__</span> <span class="o">=</span> <span class="n">doc</span> <span class="bp">self</span><span class="o">.</span><span class="n">_name</span> <span class="o">=</span> <span class="s1">''</span> <span class="k">def</span> <span class="nf">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">_name</span> <span class="o">=</span> <span class="n">name</span> <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="k">if</span> <span class="n">obj</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="k">return</span> <span class="bp">self</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">fget</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span><span class="sa">f</span><span class="s1">'unreadable attribute </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_name</span><span class="si">}</span><span class="s1">'</span><span class="p">)</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">fget</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span> <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">fset</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"can't set attribute </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_name</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="bp">self</span><span class="o">.</span><span class="n">fset</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">def</span> <span class="fm">__delete__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">):</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">fdel</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"can't delete attribute </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_name</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="bp">self</span><span class="o">.</span><span class="n">fdel</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span> <span class="k">def</span> <span class="nf">getter</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fget</span><span class="p">):</span> <span class="n">prop</span> <span class="o">=</span> <span class="nb">type</span><span class="p">(</span><span class="bp">self</span><span class="p">)(</span><span class="n">fget</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">fset</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">fdel</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__doc__</span><span class="p">)</span> <span class="n">prop</span><span class="o">.</span><span class="n">_name</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_name</span> <span class="k">return</span> <span class="n">prop</span> <span class="k">def</span> <span class="nf">setter</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fset</span><span class="p">):</span> <span class="n">prop</span> <span class="o">=</span> <span class="nb">type</span><span class="p">(</span><span class="bp">self</span><span class="p">)(</span><span class="bp">self</span><span class="o">.</span><span class="n">fget</span><span class="p">,</span> <span class="n">fset</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">fdel</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__doc__</span><span class="p">)</span> <span class="n">prop</span><span class="o">.</span><span class="n">_name</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_name</span> <span class="k">return</span> <span class="n">prop</span> <span class="k">def</span> <span class="nf">deleter</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fdel</span><span class="p">):</span> <span class="n">prop</span> <span class="o">=</span> <span class="nb">type</span><span class="p">(</span><span class="bp">self</span><span class="p">)(</span><span class="bp">self</span><span class="o">.</span><span class="n">fget</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">fset</span><span class="p">,</span> <span class="n">fdel</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__doc__</span><span class="p">)</span> <span class="n">prop</span><span class="o">.</span><span class="n">_name</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_name</span> <span class="k">return</span> <span class="n">prop</span> </pre></div> </div> <p>The <a class="reference internal" href="../library/functions.html#property" title="property"><code class="xref py py-func docutils literal notranslate"><span class="pre">property()</span></code></a> builtin helps whenever a user interface has granted attribute access and then subsequent changes require the intervention of a method.</p> <p>For instance, a spreadsheet class may grant access to a cell value through <code class="docutils literal notranslate"><span class="pre">Cell('b10').value</span></code>. Subsequent improvements to the program require the cell to be recalculated on every access; however, the programmer does not want to affect existing client code accessing the attribute directly. The solution is to wrap access to the value attribute in a property data descriptor:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Cell</span><span class="p">:</span> <span class="o">...</span> <span class="nd">@property</span> <span class="k">def</span> <span class="nf">value</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="s2">"Recalculate the cell before returning value"</span> <span class="bp">self</span><span class="o">.</span><span class="n">recalc</span><span class="p">()</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_value</span> </pre></div> </div> <p>Either the built-in <a class="reference internal" href="../library/functions.html#property" title="property"><code class="xref py py-func docutils literal notranslate"><span class="pre">property()</span></code></a> or our <code class="xref py py-func docutils literal notranslate"><span class="pre">Property()</span></code> equivalent would work in this example.</p> </section> <section id="functions-and-methods"> <h3><a class="toc-backref" href="#id25">Functions and methods</a><a class="headerlink" href="#functions-and-methods" title="Permalink to this headline">¶</a></h3> <p>Python’s object oriented features are built upon a function based environment. Using non-data descriptors, the two are merged seamlessly.</p> <p>Functions stored in class dictionaries get turned into methods when invoked. Methods only differ from regular functions in that the object instance is prepended to the other arguments. By convention, the instance is called <em>self</em> but could be called <em>this</em> or any other variable name.</p> <p>Methods can be created manually with <a class="reference internal" href="../library/types.html#types.MethodType" title="types.MethodType"><code class="xref py py-class docutils literal notranslate"><span class="pre">types.MethodType</span></code></a> which is roughly equivalent to:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">MethodType</span><span class="p">:</span> <span class="s2">"Emulate PyMethod_Type in Objects/classobject.c"</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="n">obj</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__func__</span> <span class="o">=</span> <span class="n">func</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__self__</span> <span class="o">=</span> <span class="n">obj</span> <span class="k">def</span> <span class="fm">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span> <span class="n">func</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__func__</span> <span class="n">obj</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__self__</span> <span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> </pre></div> </div> <p>To support automatic creation of methods, functions include the <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code> method for binding methods during attribute access. This means that functions are non-data descriptors that return bound methods during dotted lookup from an instance. Here’s how it works:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Function</span><span class="p">:</span> <span class="o">...</span> <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="s2">"Simulate func_descr_get() in Objects/funcobject.c"</span> <span class="k">if</span> <span class="n">obj</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="k">return</span> <span class="bp">self</span> <span class="k">return</span> <span class="n">MethodType</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">)</span> </pre></div> </div> <p>Running the following class in the interpreter shows how the function descriptor works in practice:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">D</span><span class="p">:</span> <span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span> <span class="k">return</span> <span class="n">x</span> </pre></div> </div> <p>The function has a <a class="reference internal" href="../glossary.html#term-qualified-name"><span class="xref std std-term">qualified name</span></a> attribute to support introspection:</p> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">D</span><span class="o">.</span><span class="n">f</span><span class="o">.</span><span class="vm">__qualname__</span> <span class="go">'D.f'</span> </pre></div> </div> <p>Accessing the function through the class dictionary does not invoke <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code>. Instead, it just returns the underlying function object:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">D</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">[</span><span class="s1">'f'</span><span class="p">]</span> <span class="go"><function D.f at 0x00C45070></span> </pre></div> </div> <p>Dotted access from a class calls <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code> which just returns the underlying function unchanged:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">D</span><span class="o">.</span><span class="n">f</span> <span class="go"><function D.f at 0x00C45070></span> </pre></div> </div> <p>The interesting behavior occurs during dotted access from an instance. The dotted lookup calls <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code> which returns a bound method object:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">d</span> <span class="o">=</span> <span class="n">D</span><span class="p">()</span> <span class="gp">>>> </span><span class="n">d</span><span class="o">.</span><span class="n">f</span> <span class="go"><bound method D.f of <__main__.D object at 0x00B18C90>></span> </pre></div> </div> <p>Internally, the bound method stores the underlying function and the bound instance:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">d</span><span class="o">.</span><span class="n">f</span><span class="o">.</span><span class="vm">__func__</span> <span class="go"><function D.f at 0x00C45070></span> <span class="gp">>>> </span><span class="n">d</span><span class="o">.</span><span class="n">f</span><span class="o">.</span><span class="vm">__self__</span> <span class="go"><__main__.D object at 0x1012e1f98></span> </pre></div> </div> <p>If you have ever wondered where <em>self</em> comes from in regular methods or where <em>cls</em> comes from in class methods, this is it!</p> </section> <section id="kinds-of-methods"> <h3><a class="toc-backref" href="#id26">Kinds of methods</a><a class="headerlink" href="#kinds-of-methods" title="Permalink to this headline">¶</a></h3> <p>Non-data descriptors provide a simple mechanism for variations on the usual patterns of binding functions into methods.</p> <p>To recap, functions have a <code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code> method so that they can be converted to a method when accessed as attributes. The non-data descriptor transforms an <code class="docutils literal notranslate"><span class="pre">obj.f(*args)</span></code> call into <code class="docutils literal notranslate"><span class="pre">f(obj,</span> <span class="pre">*args)</span></code>. Calling <code class="docutils literal notranslate"><span class="pre">cls.f(*args)</span></code> becomes <code class="docutils literal notranslate"><span class="pre">f(*args)</span></code>.</p> <p>This chart summarizes the binding and its two most useful variants:</p> <blockquote> <div><table class="docutils align-default"> <colgroup> <col style="width: 30%" /> <col style="width: 39%" /> <col style="width: 32%" /> </colgroup> <thead> <tr class="row-odd"><th class="head"><p>Transformation</p></th> <th class="head"><p>Called from an object</p></th> <th class="head"><p>Called from a class</p></th> </tr> </thead> <tbody> <tr class="row-even"><td><p>function</p></td> <td><p>f(obj, *args)</p></td> <td><p>f(*args)</p></td> </tr> <tr class="row-odd"><td><p>staticmethod</p></td> <td><p>f(*args)</p></td> <td><p>f(*args)</p></td> </tr> <tr class="row-even"><td><p>classmethod</p></td> <td><p>f(type(obj), *args)</p></td> <td><p>f(cls, *args)</p></td> </tr> </tbody> </table> </div></blockquote> </section> <section id="static-methods"> <h3><a class="toc-backref" href="#id27">Static methods</a><a class="headerlink" href="#static-methods" title="Permalink to this headline">¶</a></h3> <p>Static methods return the underlying function without changes. Calling either <code class="docutils literal notranslate"><span class="pre">c.f</span></code> or <code class="docutils literal notranslate"><span class="pre">C.f</span></code> is the equivalent of a direct lookup into <code class="docutils literal notranslate"><span class="pre">object.__getattribute__(c,</span> <span class="pre">"f")</span></code> or <code class="docutils literal notranslate"><span class="pre">object.__getattribute__(C,</span> <span class="pre">"f")</span></code>. As a result, the function becomes identically accessible from either an object or a class.</p> <p>Good candidates for static methods are methods that do not reference the <code class="docutils literal notranslate"><span class="pre">self</span></code> variable.</p> <p>For instance, a statistics package may include a container class for experimental data. The class provides normal methods for computing the average, mean, median, and other descriptive statistics that depend on the data. However, there may be useful functions which are conceptually related but do not depend on the data. For instance, <code class="docutils literal notranslate"><span class="pre">erf(x)</span></code> is handy conversion routine that comes up in statistical work but does not directly depend on a particular dataset. It can be called either from an object or the class: <code class="docutils literal notranslate"><span class="pre">s.erf(1.5)</span> <span class="pre">--></span> <span class="pre">.9332</span></code> or <code class="docutils literal notranslate"><span class="pre">Sample.erf(1.5)</span> <span class="pre">--></span> <span class="pre">.9332</span></code>.</p> <p>Since static methods return the underlying function with no changes, the example calls are unexciting:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">E</span><span class="p">:</span> <span class="nd">@staticmethod</span> <span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">):</span> <span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">10</span> </pre></div> </div> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">E</span><span class="o">.</span><span class="n">f</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="go">30</span> <span class="gp">>>> </span><span class="n">E</span><span class="p">()</span><span class="o">.</span><span class="n">f</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="go">30</span> </pre></div> </div> <p>Using the non-data descriptor protocol, a pure Python version of <a class="reference internal" href="../library/functions.html#staticmethod" title="staticmethod"><code class="xref py py-func docutils literal notranslate"><span class="pre">staticmethod()</span></code></a> would look like this:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">StaticMethod</span><span class="p">:</span> <span class="s2">"Emulate PyStaticMethod_Type() in Objects/funcobject.c"</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">f</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">f</span> <span class="o">=</span> <span class="n">f</span> <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">f</span> <span class="k">def</span> <span class="fm">__call__</span><span class="p">(</span><span class="bp">self</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">kwds</span><span class="p">):</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">f</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwds</span><span class="p">)</span> </pre></div> </div> </section> <section id="class-methods"> <h3><a class="toc-backref" href="#id28">Class methods</a><a class="headerlink" href="#class-methods" title="Permalink to this headline">¶</a></h3> <p>Unlike static methods, class methods prepend the class reference to the argument list before calling the function. This format is the same for whether the caller is an object or a class:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">F</span><span class="p">:</span> <span class="nd">@classmethod</span> <span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span> <span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span><span class="p">,</span> <span class="n">x</span> </pre></div> </div> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">F</span><span class="o">.</span><span class="n">f</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="go">('F', 3)</span> <span class="gp">>>> </span><span class="n">F</span><span class="p">()</span><span class="o">.</span><span class="n">f</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="go">('F', 3)</span> </pre></div> </div> <p>This behavior is useful whenever the method only needs to have a class reference and does not rely on data stored in a specific instance. One use for class methods is to create alternate class constructors. For example, the classmethod <a class="reference internal" href="../library/stdtypes.html#dict.fromkeys" title="dict.fromkeys"><code class="xref py py-func docutils literal notranslate"><span class="pre">dict.fromkeys()</span></code></a> creates a new dictionary from a list of keys. The pure Python equivalent is:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Dict</span><span class="p">(</span><span class="nb">dict</span><span class="p">):</span> <span class="nd">@classmethod</span> <span class="k">def</span> <span class="nf">fromkeys</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">iterable</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="s2">"Emulate dict_fromkeys() in Objects/dictobject.c"</span> <span class="n">d</span> <span class="o">=</span> <span class="bp">cls</span><span class="p">()</span> <span class="k">for</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">iterable</span><span class="p">:</span> <span class="n">d</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span> <span class="k">return</span> <span class="n">d</span> </pre></div> </div> <p>Now a new dictionary of unique keys can be constructed like this:</p> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">d</span> <span class="o">=</span> <span class="n">Dict</span><span class="o">.</span><span class="n">fromkeys</span><span class="p">(</span><span class="s1">'abracadabra'</span><span class="p">)</span> <span class="gp">>>> </span><span class="nb">type</span><span class="p">(</span><span class="n">d</span><span class="p">)</span> <span class="ow">is</span> <span class="n">Dict</span> <span class="go">True</span> <span class="gp">>>> </span><span class="n">d</span> <span class="go">{'a': None, 'b': None, 'r': None, 'c': None, 'd': None}</span> </pre></div> </div> <p>Using the non-data descriptor protocol, a pure Python version of <a class="reference internal" href="../library/functions.html#classmethod" title="classmethod"><code class="xref py py-func docutils literal notranslate"><span class="pre">classmethod()</span></code></a> would look like this:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">ClassMethod</span><span class="p">:</span> <span class="s2">"Emulate PyClassMethod_Type() in Objects/funcobject.c"</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">f</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">f</span> <span class="o">=</span> <span class="n">f</span> <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="bp">cls</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="k">if</span> <span class="bp">cls</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="bp">cls</span> <span class="o">=</span> <span class="nb">type</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span> <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">f</span><span class="p">),</span> <span class="s1">'__get__'</span><span class="p">):</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">f</span><span class="o">.</span><span class="fm">__get__</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="bp">cls</span><span class="p">)</span> <span class="k">return</span> <span class="n">MethodType</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">f</span><span class="p">,</span> <span class="bp">cls</span><span class="p">)</span> </pre></div> </div> <p>The code path for <code class="docutils literal notranslate"><span class="pre">hasattr(type(self.f),</span> <span class="pre">'__get__')</span></code> was added in Python 3.9 and makes it possible for <a class="reference internal" href="../library/functions.html#classmethod" title="classmethod"><code class="xref py py-func docutils literal notranslate"><span class="pre">classmethod()</span></code></a> to support chained decorators. For example, a classmethod and property could be chained together:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">G</span><span class="p">:</span> <span class="nd">@classmethod</span> <span class="nd">@property</span> <span class="k">def</span> <span class="nf">__doc__</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span> <span class="k">return</span> <span class="sa">f</span><span class="s1">'A doc for </span><span class="si">{</span><span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span><span class="si">!r}</span><span class="s1">'</span> </pre></div> </div> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">G</span><span class="o">.</span><span class="vm">__doc__</span> <span class="go">"A doc for 'G'"</span> </pre></div> </div> </section> <section id="member-objects-and-slots"> <h3><a class="toc-backref" href="#id29">Member objects and __slots__</a><a class="headerlink" href="#member-objects-and-slots" title="Permalink to this headline">¶</a></h3> <p>When a class defines <code class="docutils literal notranslate"><span class="pre">__slots__</span></code>, it replaces instance dictionaries with a fixed-length array of slot values. From a user point of view that has several effects:</p> <p>1. Provides immediate detection of bugs due to misspelled attribute assignments. Only attribute names specified in <code class="docutils literal notranslate"><span class="pre">__slots__</span></code> are allowed:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Vehicle</span><span class="p">:</span> <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'id_number'</span><span class="p">,</span> <span class="s1">'make'</span><span class="p">,</span> <span class="s1">'model'</span><span class="p">)</span> </pre></div> </div> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">auto</span> <span class="o">=</span> <span class="n">Vehicle</span><span class="p">()</span> <span class="gp">>>> </span><span class="n">auto</span><span class="o">.</span><span class="n">id_nubmer</span> <span class="o">=</span> <span class="s1">'VYE483814LQEX'</span> <span class="gt">Traceback (most recent call last):</span> <span class="o">...</span> <span class="gr">AttributeError</span>: <span class="n">'Vehicle' object has no attribute 'id_nubmer'</span> </pre></div> </div> <p>2. Helps create immutable objects where descriptors manage access to private attributes stored in <code class="docutils literal notranslate"><span class="pre">__slots__</span></code>:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Immutable</span><span class="p">:</span> <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'_dept'</span><span class="p">,</span> <span class="s1">'_name'</span><span class="p">)</span> <span class="c1"># Replace the instance dictionary</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dept</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dept</span> <span class="o">=</span> <span class="n">dept</span> <span class="c1"># Store to private attribute</span> <span class="bp">self</span><span class="o">.</span><span class="n">_name</span> <span class="o">=</span> <span class="n">name</span> <span class="c1"># Store to private attribute</span> <span class="nd">@property</span> <span class="c1"># Read-only descriptor</span> <span class="k">def</span> <span class="nf">dept</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dept</span> <span class="nd">@property</span> <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="c1"># Read-only descriptor</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_name</span> </pre></div> </div> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">mark</span> <span class="o">=</span> <span class="n">Immutable</span><span class="p">(</span><span class="s1">'Botany'</span><span class="p">,</span> <span class="s1">'Mark Watney'</span><span class="p">)</span> <span class="gp">>>> </span><span class="n">mark</span><span class="o">.</span><span class="n">dept</span> <span class="go">'Botany'</span> <span class="gp">>>> </span><span class="n">mark</span><span class="o">.</span><span class="n">dept</span> <span class="o">=</span> <span class="s1">'Space Pirate'</span> <span class="gt">Traceback (most recent call last):</span> <span class="o">...</span> <span class="gr">AttributeError</span>: <span class="n">can't set attribute</span> <span class="gp">>>> </span><span class="n">mark</span><span class="o">.</span><span class="n">location</span> <span class="o">=</span> <span class="s1">'Mars'</span> <span class="gt">Traceback (most recent call last):</span> <span class="o">...</span> <span class="gr">AttributeError</span>: <span class="n">'Immutable' object has no attribute 'location'</span> </pre></div> </div> <p>3. Saves memory. On a 64-bit Linux build, an instance with two attributes takes 48 bytes with <code class="docutils literal notranslate"><span class="pre">__slots__</span></code> and 152 bytes without. This <a class="reference external" href="https://en.wikipedia.org/wiki/Flyweight_pattern">flyweight design pattern</a> likely only matters when a large number of instances are going to be created.</p> <p>4. Improves speed. Reading instance variables is 35% faster with <code class="docutils literal notranslate"><span class="pre">__slots__</span></code> (as measured with Python 3.10 on an Apple M1 processor).</p> <p>5. Blocks tools like <a class="reference internal" href="../library/functools.html#functools.cached_property" title="functools.cached_property"><code class="xref py py-func docutils literal notranslate"><span class="pre">functools.cached_property()</span></code></a> which require an instance dictionary to function correctly:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">cached_property</span> <span class="k">class</span> <span class="nc">CP</span><span class="p">:</span> <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">()</span> <span class="c1"># Eliminates the instance dict</span> <span class="nd">@cached_property</span> <span class="c1"># Requires an instance dict</span> <span class="k">def</span> <span class="nf">pi</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">return</span> <span class="mi">4</span> <span class="o">*</span> <span class="nb">sum</span><span class="p">((</span><span class="o">-</span><span class="mf">1.0</span><span class="p">)</span><span class="o">**</span><span class="n">n</span> <span class="o">/</span> <span class="p">(</span><span class="mf">2.0</span><span class="o">*</span><span class="n">n</span> <span class="o">+</span> <span class="mf">1.0</span><span class="p">)</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">reversed</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">100_000</span><span class="p">)))</span> </pre></div> </div> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">CP</span><span class="p">()</span><span class="o">.</span><span class="n">pi</span> <span class="gt">Traceback (most recent call last):</span> <span class="c">...</span> <span class="gr">TypeError</span>: <span class="n">No '__dict__' attribute on 'CP' instance to cache 'pi' property.</span> </pre></div> </div> <p>It is not possible to create an exact drop-in pure Python version of <code class="docutils literal notranslate"><span class="pre">__slots__</span></code> because it requires direct access to C structures and control over object memory allocation. However, we can build a mostly faithful simulation where the actual C structure for slots is emulated by a private <code class="docutils literal notranslate"><span class="pre">_slotvalues</span></code> list. Reads and writes to that private structure are managed by member descriptors:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="n">null</span> <span class="o">=</span> <span class="nb">object</span><span class="p">()</span> <span class="k">class</span> <span class="nc">Member</span><span class="p">:</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">clsname</span><span class="p">,</span> <span class="n">offset</span><span class="p">):</span> <span class="s1">'Emulate PyMemberDef in Include/structmember.h'</span> <span class="c1"># Also see descr_new() in Objects/descrobject.c</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span> <span class="bp">self</span><span class="o">.</span><span class="n">clsname</span> <span class="o">=</span> <span class="n">clsname</span> <span class="bp">self</span><span class="o">.</span><span class="n">offset</span> <span class="o">=</span> <span class="n">offset</span> <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> <span class="s1">'Emulate member_get() in Objects/descrobject.c'</span> <span class="c1"># Also see PyMember_GetOne() in Python/structmember.c</span> <span class="k">if</span> <span class="n">obj</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="k">return</span> <span class="bp">self</span> <span class="n">value</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="n">_slotvalues</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">offset</span><span class="p">]</span> <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="n">null</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">)</span> <span class="k">return</span> <span class="n">value</span> <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="s1">'Emulate member_set() in Objects/descrobject.c'</span> <span class="n">obj</span><span class="o">.</span><span class="n">_slotvalues</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">offset</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span> <span class="k">def</span> <span class="fm">__delete__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">):</span> <span class="s1">'Emulate member_delete() in Objects/descrobject.c'</span> <span class="n">value</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="n">_slotvalues</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">offset</span><span class="p">]</span> <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="n">null</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">)</span> <span class="n">obj</span><span class="o">.</span><span class="n">_slotvalues</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">offset</span><span class="p">]</span> <span class="o">=</span> <span class="n">null</span> <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="s1">'Emulate member_repr() in Objects/descrobject.c'</span> <span class="k">return</span> <span class="sa">f</span><span class="s1">'<Member </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">!r}</span><span class="s1"> of </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">clsname</span><span class="si">!r}</span><span class="s1">>'</span> </pre></div> </div> <p>The <code class="xref py py-meth docutils literal notranslate"><span class="pre">type.__new__()</span></code> method takes care of adding member objects to class variables:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Type</span><span class="p">(</span><span class="nb">type</span><span class="p">):</span> <span class="s1">'Simulate how the type metaclass adds member objects for slots'</span> <span class="k">def</span> <span class="fm">__new__</span><span class="p">(</span><span class="n">mcls</span><span class="p">,</span> <span class="n">clsname</span><span class="p">,</span> <span class="n">bases</span><span class="p">,</span> <span class="n">mapping</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span> <span class="s1">'Emulate type_new() in Objects/typeobject.c'</span> <span class="c1"># type_new() calls PyTypeReady() which calls add_methods()</span> <span class="n">slot_names</span> <span class="o">=</span> <span class="n">mapping</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'slot_names'</span><span class="p">,</span> <span class="p">[])</span> <span class="k">for</span> <span class="n">offset</span><span class="p">,</span> <span class="n">name</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">slot_names</span><span class="p">):</span> <span class="n">mapping</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">Member</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">clsname</span><span class="p">,</span> <span class="n">offset</span><span class="p">)</span> <span class="k">return</span> <span class="nb">type</span><span class="o">.</span><span class="fm">__new__</span><span class="p">(</span><span class="n">mcls</span><span class="p">,</span> <span class="n">clsname</span><span class="p">,</span> <span class="n">bases</span><span class="p">,</span> <span class="n">mapping</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> </pre></div> </div> <p>The <a class="reference internal" href="../reference/datamodel.html#object.__new__" title="object.__new__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">object.__new__()</span></code></a> method takes care of creating instances that have slots instead of an instance dictionary. Here is a rough simulation in pure Python:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Object</span><span class="p">:</span> <span class="s1">'Simulate how object.__new__() allocates memory for __slots__'</span> <span class="k">def</span> <span class="fm">__new__</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="s1">'Emulate object_new() in Objects/typeobject.c'</span> <span class="n">inst</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">)</span> <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s1">'slot_names'</span><span class="p">):</span> <span class="n">empty_slots</span> <span class="o">=</span> <span class="p">[</span><span class="n">null</span><span class="p">]</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="bp">cls</span><span class="o">.</span><span class="n">slot_names</span><span class="p">)</span> <span class="nb">object</span><span class="o">.</span><span class="fm">__setattr__</span><span class="p">(</span><span class="n">inst</span><span class="p">,</span> <span class="s1">'_slotvalues'</span><span class="p">,</span> <span class="n">empty_slots</span><span class="p">)</span> <span class="k">return</span> <span class="n">inst</span> <span class="k">def</span> <span class="fm">__setattr__</span><span class="p">(</span><span class="bp">self</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="s1">'Emulate _PyObject_GenericSetAttrWithDict() Objects/object.c'</span> <span class="bp">cls</span> <span class="o">=</span> <span class="nb">type</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s1">'slot_names'</span><span class="p">)</span> <span class="ow">and</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">cls</span><span class="o">.</span><span class="n">slot_names</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span> <span class="sa">f</span><span class="s1">'</span><span class="si">{</span><span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span><span class="si">!r}</span><span class="s1"> object has no attribute </span><span class="si">{</span><span class="n">name</span><span class="si">!r}</span><span class="s1">'</span> <span class="p">)</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</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">def</span> <span class="fm">__delattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span> <span class="s1">'Emulate _PyObject_GenericSetAttrWithDict() Objects/object.c'</span> <span class="bp">cls</span> <span class="o">=</span> <span class="nb">type</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s1">'slot_names'</span><span class="p">)</span> <span class="ow">and</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">cls</span><span class="o">.</span><span class="n">slot_names</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span> <span class="sa">f</span><span class="s1">'</span><span class="si">{</span><span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span><span class="si">!r}</span><span class="s1"> object has no attribute </span><span class="si">{</span><span class="n">name</span><span class="si">!r}</span><span class="s1">'</span> <span class="p">)</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__delattr__</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> </pre></div> </div> <p>To use the simulation in a real class, just inherit from <code class="xref py py-class docutils literal notranslate"><span class="pre">Object</span></code> and set the <a class="reference internal" href="../glossary.html#term-metaclass"><span class="xref std std-term">metaclass</span></a> to <code class="xref py py-class docutils literal notranslate"><span class="pre">Type</span></code>:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">H</span><span class="p">(</span><span class="n">Object</span><span class="p">,</span> <span class="n">metaclass</span><span class="o">=</span><span class="n">Type</span><span class="p">):</span> <span class="s1">'Instance variables stored in slots'</span> <span class="n">slot_names</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'x'</span><span class="p">,</span> <span class="s1">'y'</span><span class="p">]</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span> <span class="bp">self</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span> </pre></div> </div> <p>At this point, the metaclass has loaded member objects for <em>x</em> and <em>y</em>:</p> <div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="kn">from</span> <span class="nn">pprint</span> <span class="kn">import</span> <span class="n">pp</span> <span class="gp">>>> </span><span class="n">pp</span><span class="p">(</span><span class="nb">dict</span><span class="p">(</span><span class="nb">vars</span><span class="p">(</span><span class="n">H</span><span class="p">)))</span> <span class="go">{'__module__': '__main__',</span> <span class="go"> '__doc__': 'Instance variables stored in slots',</span> <span class="go"> 'slot_names': ['x', 'y'],</span> <span class="go"> '__init__': <function H.__init__ at 0x7fb5d302f9d0>,</span> <span class="go"> 'x': <Member 'x' of 'H'>,</span> <span class="go"> 'y': <Member 'y' of 'H'>}</span> </pre></div> </div> <p>When instances are created, they have a <code class="docutils literal notranslate"><span class="pre">slot_values</span></code> list where the attributes are stored:</p> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">h</span> <span class="o">=</span> <span class="n">H</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span> <span class="gp">>>> </span><span class="nb">vars</span><span class="p">(</span><span class="n">h</span><span class="p">)</span> <span class="go">{'_slotvalues': [10, 20]}</span> <span class="gp">>>> </span><span class="n">h</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">55</span> <span class="gp">>>> </span><span class="nb">vars</span><span class="p">(</span><span class="n">h</span><span class="p">)</span> <span class="go">{'_slotvalues': [55, 20]}</span> </pre></div> </div> <p>Misspelled or unassigned attributes will raise an exception:</p> <div class="highlight-pycon3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">h</span><span class="o">.</span><span class="n">xz</span> <span class="gt">Traceback (most recent call last):</span> <span class="o">...</span> <span class="gr">AttributeError</span>: <span class="n">'H' object has no attribute 'xz'</span> </pre></div> </div> </section> </section> </section> <div class="clearer"></div> </div> </div> </div> <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> <div class="sphinxsidebarwrapper"> <h3><a href="../contents.html">Table of Contents</a></h3> <ul> <li><a class="reference internal" href="#">Descriptor HowTo Guide</a><ul> <li><a class="reference internal" href="#primer">Primer</a><ul> <li><a class="reference internal" href="#simple-example-a-descriptor-that-returns-a-constant">Simple example: A descriptor that returns a constant</a></li> <li><a class="reference internal" href="#dynamic-lookups">Dynamic lookups</a></li> <li><a class="reference internal" href="#managed-attributes">Managed attributes</a></li> <li><a class="reference internal" href="#customized-names">Customized names</a></li> <li><a class="reference internal" href="#closing-thoughts">Closing thoughts</a></li> </ul> </li> <li><a class="reference internal" href="#complete-practical-example">Complete Practical Example</a><ul> <li><a class="reference internal" href="#validator-class">Validator class</a></li> <li><a class="reference internal" href="#custom-validators">Custom validators</a></li> <li><a class="reference internal" href="#practical-application">Practical application</a></li> </ul> </li> <li><a class="reference internal" href="#technical-tutorial">Technical Tutorial</a><ul> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#definition-and-introduction">Definition and introduction</a></li> <li><a class="reference internal" href="#descriptor-protocol">Descriptor protocol</a></li> <li><a class="reference internal" href="#overview-of-descriptor-invocation">Overview of descriptor invocation</a></li> <li><a class="reference internal" href="#invocation-from-an-instance">Invocation from an instance</a></li> <li><a class="reference internal" href="#invocation-from-a-class">Invocation from a class</a></li> <li><a class="reference internal" href="#invocation-from-super">Invocation from super</a></li> <li><a class="reference internal" href="#summary-of-invocation-logic">Summary of invocation logic</a></li> <li><a class="reference internal" href="#automatic-name-notification">Automatic name notification</a></li> <li><a class="reference internal" href="#orm-example">ORM example</a></li> </ul> </li> <li><a class="reference internal" href="#pure-python-equivalents">Pure Python Equivalents</a><ul> <li><a class="reference internal" href="#properties">Properties</a></li> <li><a class="reference internal" href="#functions-and-methods">Functions and methods</a></li> <li><a class="reference internal" href="#kinds-of-methods">Kinds of methods</a></li> <li><a class="reference internal" href="#static-methods">Static methods</a></li> <li><a class="reference internal" href="#class-methods">Class methods</a></li> <li><a class="reference internal" href="#member-objects-and-slots">Member objects and __slots__</a></li> </ul> </li> </ul> </li> </ul> <h4>Previous topic</h4> <p class="topless"><a href="curses.html" title="previous chapter">Curses Programming with Python</a></p> <h4>Next topic</h4> <p class="topless"><a href="functional.html" title="next chapter">Functional Programming HOWTO</a></p> <div role="note" aria-label="source link"> <h3>This Page</h3> <ul class="this-page-menu"> <li><a href="../bugs.html">Report a Bug</a></li> <li> <a href="https://github.com/python/cpython/blob/3.10/Doc/howto/descriptor.rst" rel="nofollow">Show Source </a> </li> </ul> </div> </div> </div> <div class="clearer"></div> </div> <div class="related" role="navigation" aria-label="related navigation"> <h3>Navigation</h3> <ul> <li class="right" style="margin-right: 10px"> <a href="../genindex.html" title="General Index" >index</a></li> <li class="right" > <a href="../py-modindex.html" title="Python Module Index" >modules</a> |</li> <li class="right" > <a href="functional.html" title="Functional Programming HOWTO" >next</a> |</li> <li class="right" > <a href="curses.html" title="Curses Programming with Python" >previous</a> |</li> <li><img src="../_static/py.svg" alt="python logo" style="vertical-align: middle; margin-top: -1px"/></li> <li><a href="https://www.python.org/">Python</a> »</li> <li class="switchers"> <div class="language_switcher_placeholder"></div> <div class="version_switcher_placeholder"></div> </li> <li> </li> <li id="cpython-language-and-version"> <a href="../index.html">3.10.12 Documentation</a> » </li> <li class="nav-item nav-item-1"><a href="index.html" >Python HOWTOs</a> »</li> <li class="nav-item nav-item-this"><a href="">Descriptor HowTo Guide</a></li> <li class="right"> <div class="inline-search" role="search"> <form class="inline-search" action="../search.html" method="get"> <input placeholder="Quick search" aria-label="Quick search" type="text" name="q" /> <input type="submit" value="Go" /> <input type="hidden" name="check_keywords" value="yes" /> <input type="hidden" name="area" value="default" /> </form> </div> | </li> </ul> </div> <div class="footer"> © <a href="../copyright.html">Copyright</a> 2001-2025, Python Software Foundation. <br /> This page is licensed under the Python Software Foundation License Version 2. <br /> Examples, recipes, and other code in the documentation are additionally licensed under the Zero Clause BSD License. <br /> See <a href="/license.html">History and License</a> for more information.<br /> <br /> The Python Software Foundation is a non-profit corporation. <a href="https://www.python.org/psf/donations/">Please donate.</a> <br /> <br /> Last updated on February 04, 2025. <a href="/bugs.html">Found a bug</a>? <br /> Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 4.3.2. </div> </body> </html>