1636 lines
170 KiB
HTML
1636 lines
170 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<html lang="en" data-content_root="../">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<meta property="og:title" content="Descriptor Guide" />
|
||
<meta property="og:type" content="website" />
|
||
<meta property="og:url" content="https://docs.python.org/3/howto/descriptor.html" />
|
||
<meta property="og:site_name" content="Python documentation" />
|
||
<meta property="og:description" content="Author, Raymond Hettinger,, Contact,<python at rcn dot com>,. Contents: Descriptor Guide- Primer- Simple example: A descriptor that returns a constant, Dynamic lookups, Managed attributes, Customiz..." />
|
||
<meta property="og:image" content="https://docs.python.org/3/_static/og-image.png" />
|
||
<meta property="og:image:alt" content="Python documentation" />
|
||
<meta name="description" content="Author, Raymond Hettinger,, Contact,<python at rcn dot com>,. Contents: Descriptor Guide- Primer- Simple example: A descriptor that returns a constant, Dynamic lookups, Managed attributes, Customiz..." />
|
||
<meta property="og:image:width" content="200">
|
||
<meta property="og:image:height" content="200">
|
||
<meta name="theme-color" content="#3776ab">
|
||
|
||
<title>Descriptor Guide — Python 3.13.3 documentation</title><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
||
<link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=b86133f3" />
|
||
<link rel="stylesheet" type="text/css" href="../_static/pydoctheme.css?v=23252803" />
|
||
<link id="pygments_dark_css" media="(prefers-color-scheme: dark)" rel="stylesheet" type="text/css" href="../_static/pygments_dark.css?v=5349f25f" />
|
||
|
||
<script src="../_static/documentation_options.js?v=5d57ca2d"></script>
|
||
<script src="../_static/doctools.js?v=9bcbadda"></script>
|
||
<script src="../_static/sphinx_highlight.js?v=dc90522c"></script>
|
||
|
||
<script src="../_static/sidebar.js"></script>
|
||
|
||
<link rel="search" type="application/opensearchdescription+xml"
|
||
title="Search within Python 3.13.3 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="Debugging C API extensions and CPython Internals with GDB" href="gdb_helpers.html" />
|
||
<link rel="prev" title="Curses Programming with Python" href="curses.html" />
|
||
|
||
<link rel="canonical" href="https://docs.python.org/3/howto/descriptor.html">
|
||
|
||
|
||
|
||
|
||
|
||
<style>
|
||
@media only screen {
|
||
table.full-width-table {
|
||
width: 100%;
|
||
}
|
||
}
|
||
</style>
|
||
<link rel="stylesheet" href="../_static/pydoctheme_dark.css" media="(prefers-color-scheme: dark)" id="pydoctheme_dark_css">
|
||
<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>
|
||
<script type="text/javascript" src="../_static/search-focus.js"></script>
|
||
<script type="text/javascript" src="../_static/themetoggle.js"></script>
|
||
<script type="text/javascript" src="../_static/rtd_switcher.js"></script>
|
||
<meta name="readthedocs-addons-api-version" content="1">
|
||
|
||
</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" />
|
||
<nav class="nav-content" role="navigation">
|
||
<label for="menuToggler" class="toggler__label">
|
||
<span></span>
|
||
</label>
|
||
<span class="nav-items-wrapper">
|
||
<a href="https://www.python.org/" class="nav-logo">
|
||
<img src="../_static/py.svg" alt="Python logo"/>
|
||
</a>
|
||
<span class="version_switcher_placeholder"></span>
|
||
<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" fill="currentColor" 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"></path>
|
||
</svg>
|
||
<input placeholder="Quick search" aria-label="Quick search" type="search" name="q" />
|
||
<input type="submit" value="Go"/>
|
||
</form>
|
||
</span>
|
||
</nav>
|
||
<div class="menu-wrapper">
|
||
<nav class="menu" role="navigation" aria-label="main navigation">
|
||
<div class="language_switcher_placeholder"></div>
|
||
|
||
<label class="theme-selector-label">
|
||
Theme
|
||
<select class="theme-selector" oninput="activateTheme(this.value)">
|
||
<option value="auto" selected>Auto</option>
|
||
<option value="light">Light</option>
|
||
<option value="dark">Dark</option>
|
||
</select>
|
||
</label>
|
||
<div>
|
||
<h3><a href="../contents.html">Table of Contents</a></h3>
|
||
<ul>
|
||
<li><a class="reference internal" href="#">Descriptor 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>
|
||
|
||
</div>
|
||
<div>
|
||
<h4>Previous topic</h4>
|
||
<p class="topless"><a href="curses.html"
|
||
title="previous chapter">Curses Programming with Python</a></p>
|
||
</div>
|
||
<div>
|
||
<h4>Next topic</h4>
|
||
<p class="topless"><a href="gdb_helpers.html"
|
||
title="next chapter">Debugging C API extensions and CPython Internals with GDB</a></p>
|
||
</div>
|
||
<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/main/Doc/howto/descriptor.rst"
|
||
rel="nofollow">Show Source
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="related" role="navigation" aria-label="Related">
|
||
<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="gdb_helpers.html" title="Debugging C API extensions and CPython Internals with GDB"
|
||
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.13.3 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 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="search" name="q" id="search-box" />
|
||
<input type="submit" value="Go" />
|
||
</form>
|
||
</div>
|
||
|
|
||
</li>
|
||
<li class="right">
|
||
<label class="theme-selector-label">
|
||
Theme
|
||
<select class="theme-selector" oninput="activateTheme(this.value)">
|
||
<option value="auto" selected>Auto</option>
|
||
<option value="light">Light</option>
|
||
<option value="dark">Dark</option>
|
||
</select>
|
||
</label> |</li>
|
||
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="document">
|
||
<div class="documentwrapper">
|
||
<div class="bodywrapper">
|
||
<div class="body" role="main">
|
||
|
||
<section id="descriptor-guide">
|
||
<span id="descriptorhowto"></span><h1><a class="toc-backref" href="#id1" role="doc-backlink">Descriptor Guide</a><a class="headerlink" href="#descriptor-guide" title="Link to this heading">¶</a></h1>
|
||
<dl class="field-list simple">
|
||
<dt class="field-odd">Author<span class="colon">:</span></dt>
|
||
<dd class="field-odd"><p>Raymond Hettinger</p>
|
||
</dd>
|
||
<dt class="field-even">Contact<span class="colon">:</span></dt>
|
||
<dd class="field-even"><p><python at rcn dot com></p>
|
||
</dd>
|
||
</dl>
|
||
<nav class="contents" id="contents">
|
||
<p class="topic-title">Contents</p>
|
||
<ul class="simple">
|
||
<li><p><a class="reference internal" href="#descriptor-guide" id="id1">Descriptor 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>
|
||
</nav>
|
||
<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" role="doc-backlink">Primer</a><a class="headerlink" href="#primer" title="Link to this heading">¶</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" role="doc-backlink">Simple example: A descriptor that returns a constant</a><a class="headerlink" href="#simple-example-a-descriptor-that-returns-a-constant" title="Link to this heading">¶</a></h3>
|
||
<p>The <code class="xref py py-class docutils literal notranslate"><span class="pre">Ten</span></code> class is a descriptor whose <a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a> method always
|
||
returns the constant <code class="docutils literal notranslate"><span class="pre">10</span></code>:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Ten</span><span class="p">:</span>
|
||
<span class="k">def</span><span class="w"> </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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </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-pycon 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" role="doc-backlink">Dynamic lookups</a><a class="headerlink" href="#dynamic-lookups" title="Link to this heading">¶</a></h3>
|
||
<p>Interesting descriptors typically run computations instead of returning
|
||
constants:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">os</span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">DirectorySize</span><span class="p">:</span>
|
||
|
||
<span class="k">def</span><span class="w"> </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="w"> </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="w"> </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 <a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a>. 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 <a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a> 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" role="doc-backlink">Managed attributes</a><a class="headerlink" href="#managed-attributes" title="Link to this heading">¶</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 <a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a> and <a class="reference internal" href="../reference/datamodel.html#object.__set__" title="object.__set__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__set__()</span></code></a> 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-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </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="w"> </span><span class="nc">LoggedAgeAccess</span><span class="p">:</span>
|
||
|
||
<span class="k">def</span><span class="w"> </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="w"> </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="w"> </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="w"> </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="w"> </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-pycon 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" role="doc-backlink">Customized names</a><a class="headerlink" href="#customized-names" title="Link to this heading">¶</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 <a class="reference internal" href="../reference/datamodel.html#object.__set_name__" title="object.__set_name__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__set_name__()</span></code></a> 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-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </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="w"> </span><span class="nc">LoggedAccess</span><span class="p">:</span>
|
||
|
||
<span class="k">def</span><span class="w"> </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="w"> </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="w"> </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="w"> </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="w"> </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="w"> </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
|
||
<a class="reference internal" href="../reference/datamodel.html#object.__set_name__" title="object.__set_name__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__set_name__()</span></code></a> 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-pycon 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-pycon 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-pycon 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" role="doc-backlink">Closing thoughts</a><a class="headerlink" href="#closing-thoughts" title="Link to this heading">¶</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 <a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a>,
|
||
<a class="reference internal" href="../reference/datamodel.html#object.__set__" title="object.__set__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__set__()</span></code></a>, or <a class="reference internal" href="../reference/datamodel.html#object.__delete__" title="object.__delete__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__delete__()</span></code></a>.</p>
|
||
<p>Optionally, descriptors can have a <a class="reference internal" href="../reference/datamodel.html#object.__set_name__" title="object.__set_name__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__set_name__()</span></code></a> 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" role="doc-backlink">Complete Practical Example</a><a class="headerlink" href="#complete-practical-example" title="Link to this heading">¶</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" role="doc-backlink">Validator class</a><a class="headerlink" href="#validator-class" title="Link to this heading">¶</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-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">abc</span><span class="w"> </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="w"> </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="w"> </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="w"> </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="w"> </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="w"> </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" role="doc-backlink">Custom validators</a><a class="headerlink" href="#custom-validators" title="Link to this heading">¶</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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </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="w"> </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="w"> </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="w"> </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="w"> </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="w"> </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="w"> </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="w"> </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="w"> </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" role="doc-backlink">Practical application</a><a class="headerlink" href="#practical-application" title="Link to this heading">¶</a></h3>
|
||
<p>Here’s how the data validators can be used in a real class:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </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="w"> </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-pycon 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="w"> </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="w"> </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="w"> </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="w"> </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" role="doc-backlink">Technical Tutorial</a><a class="headerlink" href="#technical-tutorial" title="Link to this heading">¶</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" role="doc-backlink">Abstract</a><a class="headerlink" href="#abstract" title="Link to this heading">¶</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" role="doc-backlink">Definition and introduction</a><a class="headerlink" href="#definition-and-introduction" title="Link to this heading">¶</a></h3>
|
||
<p>In general, a descriptor is an attribute value that has one of the methods in
|
||
the descriptor protocol. Those methods are <a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a>, <a class="reference internal" href="../reference/datamodel.html#object.__set__" title="object.__set__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__set__()</span></code></a>,
|
||
and <a class="reference internal" href="../reference/datamodel.html#object.__delete__" title="object.__delete__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__delete__()</span></code></a>. 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" role="doc-backlink">Descriptor protocol</a><a class="headerlink" href="#descriptor-protocol" title="Link to this heading">¶</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></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></code></p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">descr.__delete__(self,</span> <span class="pre">obj)</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 <a class="reference internal" href="../reference/datamodel.html#object.__set__" title="object.__set__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__set__()</span></code></a> or <a class="reference internal" href="../reference/datamodel.html#object.__delete__" title="object.__delete__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__delete__()</span></code></a>, it is considered
|
||
a data descriptor. Descriptors that only define <a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a> 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 <a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a> and
|
||
<a class="reference internal" href="../reference/datamodel.html#object.__set__" title="object.__set__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__set__()</span></code></a> with the <a class="reference internal" href="../reference/datamodel.html#object.__set__" title="object.__set__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__set__()</span></code></a> 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 <a class="reference internal" href="../reference/datamodel.html#object.__set__" title="object.__set__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__set__()</span></code></a> 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" role="doc-backlink">Overview of descriptor invocation</a><a class="headerlink" href="#overview-of-descriptor-invocation" title="Link to this heading">¶</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 <a class="reference internal" href="../reference/datamodel.html#object.__dict__" title="object.__dict__"><code class="xref py py-attr docutils literal notranslate"><span class="pre">__dict__</span></code></a>, its <a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a> 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" role="doc-backlink">Invocation from an instance</a><a class="headerlink" href="#invocation-from-an-instance" title="Link to this heading">¶</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 <a class="reference internal" href="../reference/datamodel.html#object.__getattr__" title="object.__getattr__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattr__()</span></code></a> 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-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </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="w"> </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 <a class="reference internal" href="../reference/datamodel.html#object.__getattr__" title="object.__getattr__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattr__()</span></code></a> hook in the <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">__getattribute__()</span></code></a>
|
||
code. That is why calling <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">__getattribute__()</span></code></a> directly or with
|
||
<code class="docutils literal notranslate"><span class="pre">super().__getattribute__</span></code> will bypass <a class="reference internal" href="../reference/datamodel.html#object.__getattr__" title="object.__getattr__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattr__()</span></code></a> 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 <a class="reference internal" href="../reference/datamodel.html#object.__getattr__" title="object.__getattr__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__getattr__()</span></code></a> whenever <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">__getattribute__()</span></code></a>
|
||
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-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </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" role="doc-backlink">Invocation from a class</a><a class="headerlink" href="#invocation-from-a-class" title="Link to this heading">¶</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
|
||
<code class="xref py py-meth docutils literal notranslate"><span class="pre">object.__getattribute__()</span></code> 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="extlink-source reference external" href="https://github.com/python/cpython/tree/3.13/Objects/typeobject.c">Objects/typeobject.c</a>.</p>
|
||
</section>
|
||
<section id="invocation-from-super">
|
||
<h3><a class="toc-backref" href="#id19" role="doc-backlink">Invocation from super</a><a class="headerlink" href="#invocation-from-super" title="Link to this heading">¶</a></h3>
|
||
<p>The logic for super’s dotted lookup is in the <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">__getattribute__()</span></code></a> method for
|
||
object returned by <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>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="extlink-source reference external" href="https://github.com/python/cpython/tree/3.13/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" role="doc-backlink">Summary of invocation logic</a><a class="headerlink" href="#summary-of-invocation-logic" title="Link to this heading">¶</a></h3>
|
||
<p>The mechanism for descriptors is embedded in the <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">__getattribute__()</span></code></a>
|
||
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 <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">__getattribute__()</span></code></a> 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 <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">__getattribute__()</span></code></a> prevents automatic descriptor calls
|
||
because all the descriptor logic is in that method.</p></li>
|
||
<li><p><code class="xref py py-meth docutils literal notranslate"><span class="pre">object.__getattribute__()</span></code> and <code class="xref py py-meth docutils literal notranslate"><span class="pre">type.__getattribute__()</span></code> make
|
||
different calls to <a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a>. 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" role="doc-backlink">Automatic name notification</a><a class="headerlink" href="#automatic-name-notification" title="Link to this heading">¶</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 <a class="reference internal" href="../reference/datamodel.html#object.__set_name__" title="object.__set_name__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__set_name__()</span></code></a>, 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="extlink-source reference external" href="https://github.com/python/cpython/tree/3.13/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, <a class="reference internal" href="../reference/datamodel.html#object.__set_name__" title="object.__set_name__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__set_name__()</span></code></a> will need to be called manually.</p>
|
||
</section>
|
||
<section id="orm-example">
|
||
<h3><a class="toc-backref" href="#id22" role="doc-backlink">ORM example</a><a class="headerlink" href="#orm-example" title="Link to this heading">¶</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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Field</span><span class="p">:</span>
|
||
|
||
<span class="k">def</span><span class="w"> </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="w"> </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="w"> </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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </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="w"> </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="w"> </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="w"> </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="w"> </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-pycon 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" role="doc-backlink">Pure Python Equivalents</a><a class="headerlink" href="#pure-python-equivalents" title="Link to this heading">¶</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" role="doc-backlink">Properties</a><a class="headerlink" href="#properties" title="Link to this heading">¶</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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">C</span><span class="p">:</span>
|
||
<span class="k">def</span><span class="w"> </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="w"> </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="w"> </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 that implements most of the core functionality:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </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="w"> </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="k">def</span><span class="w"> </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="vm">__name__</span> <span class="o">=</span> <span class="n">name</span>
|
||
|
||
<span class="k">def</span><span class="w"> </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="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="w"> </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="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="w"> </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="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="w"> </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="k">return</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="k">def</span><span class="w"> </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="k">return</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="k">def</span><span class="w"> </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="k">return</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>
|
||
</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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </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="w"> </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" role="doc-backlink">Functions and methods</a><a class="headerlink" href="#functions-and-methods" title="Link to this heading">¶</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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </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="w"> </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="w"> </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>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="fm">__getattribute__</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="s2">"Emulate method_getset() in Objects/classobject.c"</span>
|
||
<span class="k">if</span> <span class="n">name</span> <span class="o">==</span> <span class="s1">'__doc__'</span><span class="p">:</span>
|
||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__func__</span><span class="o">.</span><span class="vm">__doc__</span>
|
||
<span class="k">return</span> <span class="nb">object</span><span class="o">.</span><span class="fm">__getattribute__</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="k">def</span><span class="w"> </span><span class="fm">__getattr__</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="s2">"Emulate method_getattro() in Objects/classobject.c"</span>
|
||
<span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="vm">__func__</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span><span class="w"> </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">"Emulate method_descr_get() in Objects/classobject.c"</span>
|
||
<span class="k">return</span> <span class="bp">self</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>To support automatic creation of methods, functions include the
|
||
<a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a> 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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Function</span><span class="p">:</span>
|
||
<span class="o">...</span>
|
||
|
||
<span class="k">def</span><span class="w"> </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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">D</span><span class="p">:</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">f</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="k">class</span><span class="w"> </span><span class="nc">D2</span><span class="p">:</span>
|
||
<span class="k">pass</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-pycon 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
|
||
<a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a>. 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 <a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a> 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 <a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a> 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 0x00B18C90></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" role="doc-backlink">Kinds of methods</a><a class="headerlink" href="#kinds-of-methods" title="Link to this heading">¶</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 <a class="reference internal" href="../reference/datamodel.html#object.__get__" title="object.__get__"><code class="xref py py-meth docutils literal notranslate"><span class="pre">__get__()</span></code></a> 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">
|
||
<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" role="doc-backlink">Static methods</a><a class="headerlink" href="#static-methods" title="Link to this heading">¶</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">0.9332</span></code>
|
||
or <code class="docutils literal notranslate"><span class="pre">Sample.erf(1.5)</span> <span class="pre">--></span> <span class="pre">0.9332</span></code>.</p>
|
||
<p>Since static methods return the underlying function with no changes, the
|
||
example calls are unexciting:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">E</span><span class="p">:</span>
|
||
<span class="nd">@staticmethod</span>
|
||
<span class="k">def</span><span class="w"> </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-pycon 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-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">functools</span>
|
||
|
||
<span class="k">class</span><span class="w"> </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="w"> </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="n">functools</span><span class="o">.</span><span class="n">update_wrapper</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="k">def</span><span class="w"> </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="w"> </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>
|
||
<p>The <a class="reference internal" href="../library/functools.html#functools.update_wrapper" title="functools.update_wrapper"><code class="xref py py-func docutils literal notranslate"><span class="pre">functools.update_wrapper()</span></code></a> call adds a <code class="docutils literal notranslate"><span class="pre">__wrapped__</span></code> attribute
|
||
that refers to the underlying function. Also it carries forward
|
||
the attributes necessary to make the wrapper look like the wrapped
|
||
function: <a class="reference internal" href="../reference/datamodel.html#function.__name__" title="function.__name__"><code class="xref py py-attr docutils literal notranslate"><span class="pre">__name__</span></code></a>, <a class="reference internal" href="../reference/datamodel.html#function.__qualname__" title="function.__qualname__"><code class="xref py py-attr docutils literal notranslate"><span class="pre">__qualname__</span></code></a>,
|
||
<a class="reference internal" href="../reference/datamodel.html#function.__doc__" title="function.__doc__"><code class="xref py py-attr docutils literal notranslate"><span class="pre">__doc__</span></code></a>, and <a class="reference internal" href="../reference/datamodel.html#function.__annotations__" title="function.__annotations__"><code class="xref py py-attr docutils literal notranslate"><span class="pre">__annotations__</span></code></a>.</p>
|
||
</section>
|
||
<section id="class-methods">
|
||
<h3><a class="toc-backref" href="#id28" role="doc-backlink">Class methods</a><a class="headerlink" href="#class-methods" title="Link to this heading">¶</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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">F</span><span class="p">:</span>
|
||
<span class="nd">@classmethod</span>
|
||
<span class="k">def</span><span class="w"> </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-pycon 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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </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="w"> </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-pycon 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-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">functools</span>
|
||
|
||
<span class="k">class</span><span class="w"> </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="w"> </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="n">functools</span><span class="o">.</span><span class="n">update_wrapper</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="k">def</span><span class="w"> </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">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 <a class="reference internal" href="../library/functools.html#functools.update_wrapper" title="functools.update_wrapper"><code class="xref py py-func docutils literal notranslate"><span class="pre">functools.update_wrapper()</span></code></a> call in <code class="docutils literal notranslate"><span class="pre">ClassMethod</span></code> adds a
|
||
<code class="docutils literal notranslate"><span class="pre">__wrapped__</span></code> attribute that refers to the underlying function. Also
|
||
it carries forward the attributes necessary to make the wrapper look
|
||
like the wrapped function: <a class="reference internal" href="../reference/datamodel.html#function.__name__" title="function.__name__"><code class="xref py py-attr docutils literal notranslate"><span class="pre">__name__</span></code></a>,
|
||
<a class="reference internal" href="../reference/datamodel.html#function.__qualname__" title="function.__qualname__"><code class="xref py py-attr docutils literal notranslate"><span class="pre">__qualname__</span></code></a>, <a class="reference internal" href="../reference/datamodel.html#function.__doc__" title="function.__doc__"><code class="xref py py-attr docutils literal notranslate"><span class="pre">__doc__</span></code></a>,
|
||
and <a class="reference internal" href="../reference/datamodel.html#function.__annotations__" title="function.__annotations__"><code class="xref py py-attr docutils literal notranslate"><span class="pre">__annotations__</span></code></a>.</p>
|
||
</section>
|
||
<section id="member-objects-and-slots">
|
||
<h3><a class="toc-backref" href="#id29" role="doc-backlink">Member objects and __slots__</a><a class="headerlink" href="#member-objects-and-slots" title="Link to this heading">¶</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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </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-pycon 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="w"> </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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </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="w"> </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="w"> </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="w"> </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-pycon 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="w"> </span><span class="o">...</span>
|
||
<span class="gr">AttributeError</span>: <span class="n">property 'dept' of 'Immutable' object has no setter</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="w"> </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-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">functools</span><span class="w"> </span><span class="kn">import</span> <span class="n">cached_property</span>
|
||
|
||
<span class="k">class</span><span class="w"> </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="w"> </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-pycon 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="w"> </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-python 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="w"> </span><span class="nc">Member</span><span class="p">:</span>
|
||
|
||
<span class="k">def</span><span class="w"> </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="w"> </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="w"> </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="w"> </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="w"> </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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </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="w"> </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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </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="w"> </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="w"> </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="w"> </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-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </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="w"> </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="w"> </span><span class="nn">pprint</span><span class="w"> </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-pycon 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-pycon 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="w"> </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">
|
||
<div class="sphinxsidebarwrapper">
|
||
<div>
|
||
<h3><a href="../contents.html">Table of Contents</a></h3>
|
||
<ul>
|
||
<li><a class="reference internal" href="#">Descriptor 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>
|
||
|
||
</div>
|
||
<div>
|
||
<h4>Previous topic</h4>
|
||
<p class="topless"><a href="curses.html"
|
||
title="previous chapter">Curses Programming with Python</a></p>
|
||
</div>
|
||
<div>
|
||
<h4>Next topic</h4>
|
||
<p class="topless"><a href="gdb_helpers.html"
|
||
title="next chapter">Debugging C API extensions and CPython Internals with GDB</a></p>
|
||
</div>
|
||
<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/main/Doc/howto/descriptor.rst"
|
||
rel="nofollow">Show Source
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div id="sidebarbutton" title="Collapse sidebar">
|
||
<span>«</span>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="clearer"></div>
|
||
</div>
|
||
<div class="related" role="navigation" aria-label="Related">
|
||
<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="gdb_helpers.html" title="Debugging C API extensions and CPython Internals with GDB"
|
||
>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.13.3 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 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="search" name="q" id="search-box" />
|
||
<input type="submit" value="Go" />
|
||
</form>
|
||
</div>
|
||
|
|
||
</li>
|
||
<li class="right">
|
||
<label class="theme-selector-label">
|
||
Theme
|
||
<select class="theme-selector" oninput="activateTheme(this.value)">
|
||
<option value="auto" selected>Auto</option>
|
||
<option value="light">Light</option>
|
||
<option value="dark">Dark</option>
|
||
</select>
|
||
</label> |</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 Apr 08, 2025 (14:33 UTC).
|
||
|
||
<a href="/bugs.html">Found a bug</a>?
|
||
|
||
<br />
|
||
|
||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.2.3.
|
||
</div>
|
||
|
||
</body>
|
||
</html> |