Python's Data Model Is an API. Here Is How to Use It Properly
shayan holak
·
2026-04-29
·
via DEV Community
<p>Most Python developers know that <code>__len__</code> makes <code>len()</code> work and <code>__add__</code> makes <code>+</code> work. That is the surface. The actual story is that Python's data model is a coherent, documented protocol through which user-defined objects can participate in the language itself: not just operators, but truthiness, hashing, iteration, context management, attribute access, memory layout, and more. Using it well means understanding what CPython actually calls, in what order, and why.</p> <h2> The Interpreter Calls These, Not You </h2> <p>The first thing to internalize: dunder methods are called by the interpreter, not by user code. When you write <code>len(obj)</code>, Python calls <code>type(obj).__len__(obj)</code>. Not <code>obj.__len__()</code>. The lookup goes through the type, not the instance.</p> <p>This matters for two reasons.</p> <p>First, defining a dunder on an instance rather than the class does not work:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight python"><code><span class="k">class</span> <span class="nc">MyClass</span><span class="p">:</span> <span class="k">pass</span> <span class="n">obj</span> <span class="o">=</span> <span class="nc">MyClass</span><span class="p">()</span> <span class="n">obj</span><span class="p">.</span><span class="n">__len__</span> <span class="o">=</span> <span class="k">lambda</span><span class="p">:</span> <span class="mi">42</span> <span class="nf">len</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span> <span class="c1"># TypeError: object of type 'MyClass' has no len() </span></code></pre> </div> <p>The interpreter checked <code>type(obj).__len__</code>, found nothing, and raised. The instance attribute was ignored entirely.</p> <p>Second, it means metaclasses can define dunders that apply to the class itself as an object. <code>__len__</code> on a metaclass makes <code>len(MyClass)</code> work. <code>__iter__</code> on a metaclass makes <code>for x in MyClass</code> work. The class is an instance of the metaclass, so the metaclass's dunders are the class's dunders.</p> <h2> Truthiness, Equality, and Hashing Are a Triangle </h2> <p>These three are deeply connected and breaking the contract between them is one of the more common ways to introduce subtle bugs.</p> <p><strong>Truthiness</strong> is determined by <code>__bool__</code>. If not defined, Python falls back to <code>__len__</code>: an object with length zero is falsy. If neither is defined, the object is always truthy.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight python"><code><span class="k">class</span> <span class="nc">Container</span><span class="p">:</span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">items</span><span class="p">):</span> <span class="n">self</span><span class="p">.</span><span class="n">items</span> <span class="o">=</span> <span class="n">items</span> <span class="k">def</span> <span class="nf">__len__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span> <span class="k">return</span> <span class="nf">len</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="n">c</span> <span class="o">=</span> <span class="nc">Container</span><span class="p">([])</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">c</span><span class="p">:</span> <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">empty</span><span class="sh">"</span><span class="p">)</span> <span class="c1"># prints, because __bool__ fell back to __len__ </span></code></pre> </div> <p><strong>Equality</strong> is <code>__eq__</code>. The default is identity comparison (same as <code>is</code>). Override it to define value equality.</p> <p><strong>Hashing</strong> is <code>__hash__</code>. Here is the contract: objects that compare equal must have the same hash. If you define <code>__eq__</code>, Python automatically sets <code>__hash__</code> to <code>None</code>, making your object unhashable. This is intentional. Mutable objects with value equality should not be hashable because their hash could change when they are mutated, silently breaking set and dict behavior.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight python"><code><span class="k">class</span> <span class="nc">Point</span><span class="p">:</span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">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="n">self</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span> <span class="n">self</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span> <span class="k">def</span> <span class="nf">__eq__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span> <span class="k">return</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">Point</span><span class="p">)</span> <span class="ow">and</span> <span class="n">self</span><span class="p">.</span><span class="n">x</span> <span class="o">==</span> <span class="n">other</span><span class="p">.</span><span class="n">x</span> <span class="ow">and</span> <span class="n">self</span><span class="p">.</span><span class="n">y</span> <span class="o">==</span> <span class="n">other</span><span class="p">.</span><span class="n">y</span> <span class="c1"># Python set __hash__ = None automatically </span> <span class="c1"># Point is now unhashable </span> <span class="n">p</span> <span class="o">=</span> <span class="nc">Point</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span><span class="n">p</span><span class="p">}</span> <span class="c1"># TypeError: unhashable type: 'Point' </span></code></pre> </div> <p>If you want a hashable value type, define both:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight python"><code> <span class="k">def</span> <span class="nf">__hash__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span> <span class="k">return</span> <span class="nf">hash</span><span class="p">((</span><span class="n">self</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">y</span><span class="p">))</span> </code></pre> </div> <p>The tuple hash is stable, consistent with equality, and composes well. Never define <code>__hash__</code> without thinking about what makes two instances equal, and never define <code>__eq__</code> without thinking about whether you also need <code>__hash__</code>.</p> <h2> Comparison Operators and the Reflected Protocol </h2> <p><code>__lt__</code>, <code>__le__</code>, <code>__gt__</code>, <code>__ge__</code>, <code>__eq__</code>, <code>__ne__</code> cover the six comparison operators. Python's mechanism for resolving them is more nuanced than most people realize.</p> <p>When you write <code>a < b</code>, Python first tries <code>type(a).__lt__(a, b)</code>. If that returns <code>NotImplemented</code>, Python tries the reflected operation: <code>type(b).__gt__(b, a)</code>. This gives the right operand a chance to handle the comparison when the left operand does not know how.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight python"><code><span class="k">class</span> <span class="nc">Celsius</span><span class="p">:</span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">temp</span><span class="p">):</span> <span class="n">self</span><span class="p">.</span><span class="n">temp</span> <span class="o">=</span> <span class="n">temp</span> <span class="k">def</span> <span class="nf">__lt__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span> <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">Fahrenheit</span><span class="p">):</span> <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">temp</span> <span class="o"><</span> <span class="p">(</span><span class="n">other</span><span class="p">.</span><span class="n">temp</span> <span class="o">-</span> <span class="mi">32</span><span class="p">)</span> <span class="o">*</span> <span class="mi">5</span> <span class="o">/</span> <span class="mi">9</span> <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">Celsius</span><span class="p">):</span> <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">temp</span> <span class="o"><</span> <span class="n">other</span><span class="p">.</span><span class="n">temp</span> <span class="k">return</span> <span class="nb">NotImplemented</span> <span class="c1"># not NotImplemented(), the singleton </span> <span class="k">class</span> <span class="nc">Fahrenheit</span><span class="p">:</span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">temp</span><span class="p">):</span> <span class="n">self</span><span class="p">.</span><span class="n">temp</span> <span class="o">=</span> <span class="n">temp</span> <span class="k">def</span> <span class="nf">__gt__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span> <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">Celsius</span><span class="p">):</span> <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">temp</span> <span class="o">></span> <span class="n">other</span><span class="p">.</span><span class="n">temp</span> <span class="o">*</span> <span class="mi">9</span> <span class="o">/</span> <span class="mi">5</span> <span class="o">+</span> <span class="mi">32</span> <span class="k">return</span> <span class="nb">NotImplemented</span> </code></pre> </div> <p><code>NotImplemented</code> is a singleton, not an exception. Returning it tells Python to try the reflected operation. Raising <code>TypeError</code> or returning <code>False</code> are both wrong here: they prevent the fallback.</p> <p><code>functools.total_ordering</code> is worth knowing. Define <code>__eq__</code> and one of the four ordering methods, and it derives the rest. It is slower than defining all four manually but saves considerable boilerplate for types where ordering is well-defined.</p> <h2> Arithmetic Operators and In-Place Variants </h2> <p>The arithmetic protocol has three layers: forward, reflected, and in-place.</p> <p><code>a + b</code> tries <code>type(a).__add__(a, b)</code> first. If that returns <code>NotImplemented</code>, it tries <code>type(b).__radd__(b, a)</code>. The reflected methods (<code>__radd__</code>, <code>__rsub__</code>, <code>__rmul__</code>, etc.) exist so that user-defined types can work with built-in types on both sides of an operator.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight python"><code><span class="k">class</span> <span class="nc">Vector</span><span class="p">:</span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">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="n">self</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span> <span class="n">self</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span> <span class="k">def</span> <span class="nf">__add__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span> <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">Vector</span><span class="p">):</span> <span class="k">return</span> <span class="nc">Vector</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">other</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">y</span> <span class="o">+</span> <span class="n">other</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="k">return</span> <span class="nb">NotImplemented</span> <span class="k">def</span> <span class="nf">__radd__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span> <span class="c1"># called when other + self and other.__add__ returned NotImplemented </span> <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="nf">__add__</span><span class="p">(</span><span class="n">other</span><span class="p">)</span> <span class="k">def</span> <span class="nf">__mul__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">scalar</span><span class="p">):</span> <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">scalar</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">return</span> <span class="nc">Vector</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">x</span> <span class="o">*</span> <span class="n">scalar</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">y</span> <span class="o">*</span> <span class="n">scalar</span><span class="p">)</span> <span class="k">return</span> <span class="nb">NotImplemented</span> <span class="k">def</span> <span class="nf">__rmul__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">scalar</span><span class="p">):</span> <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="nf">__mul__</span><span class="p">(</span><span class="n">scalar</span><span class="p">)</span> <span class="n">v</span> <span class="o">=</span> <span class="nc">Vector</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="nf">print</span><span class="p">(</span><span class="mi">3</span> <span class="o">*</span> <span class="n">v</span><span class="p">)</span> <span class="c1"># works because int.__mul__(3, v) returns NotImplemented, </span> <span class="c1"># then Vector.__rmul__(v, 3) is tried </span></code></pre> </div> <p>In-place operators (<code>+=</code>, <code>-=</code>, <code>*=</code>) call <code>__iadd__</code>, <code>__isub__</code>, <code>__imul__</code> and so on. If the in-place method is not defined, Python falls back to the regular method and rebinds the name. For mutable types, define the in-place variants to mutate in place and return <code>self</code>. For immutable types, do not define them and let Python handle the fallback.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight python"><code><span class="k">def</span> <span class="nf">__iadd__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span> <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">Vector</span><span class="p">):</span> <span class="n">self</span><span class="p">.</span><span class="n">x</span> <span class="o">+=</span> <span class="n">other</span><span class="p">.</span><span class="n">x</span> <span class="n">self</span><span class="p">.</span><span class="n">y</span> <span class="o">+=</span> <span class="n">other</span><span class="p">.</span><span class="n">y</span> <span class="k">return</span> <span class="n">self</span> <span class="c1"># must return self for in-place operations </span> <span class="k">return</span> <span class="nb">NotImplemented</span> </code></pre> </div> <h2> Attribute Access Is More Controllable Than You Think </h2> <p>The attribute access protocol has four hooks: <code>__getattribute__</code>, <code>__getattr__</code>, <code>__setattr__</code>, and <code>__delattr__</code>.</p> <p><code>__getattribute__</code> is called on every attribute access, including dunders. Overriding it without calling <code>super().__getattribute__</code> will break your class entirely. It is rarely the right hook.</p> <p><code>__getattr__</code> is called only when normal attribute lookup has failed. It is the right hook for lazy loading, proxying, and dynamic attributes:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight python"><code><span class="k">class</span> <span class="nc">LazyLoader</span><span class="p">:</span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">module_name</span><span class="p">):</span> <span class="n">self</span><span class="p">.</span><span class="n">_module_name</span> <span class="o">=</span> <span class="n">module_name</span> <span class="n">self</span><span class="p">.</span><span class="n">_module</span> <span class="o">=</span> <span class="bp">None</span> <span class="k">def</span> <span class="nf">__getattr__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span> <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">_module</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span> <span class="kn">import</span> <span class="n">importlib</span> <span class="n">self</span><span class="p">.</span><span class="n">_module</span> <span class="o">=</span> <span class="n">importlib</span><span class="p">.</span><span class="nf">import_module</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">_module_name</span><span class="p">)</span> <span class="k">return</span> <span class="nf">getattr</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">_module</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> </code></pre> </div> <p><code>__setattr__</code> is called on every attribute assignment, including in <code>__init__</code>. If you override it, you must use <code>object.__setattr__(self, name, value)</code> or <code>super().__setattr__(name, value)</code> to actually store the value, otherwise you get infinite recursion:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight python"><code><span class="k">class</span> <span class="nc">Validated</span><span class="p">:</span> <span class="k">def</span> <span class="nf">__setattr__</span><span class="p">(</span><span class="n">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="k">if</span> <span class="n">name</span> <span class="o">==</span> <span class="sh">"</span><span class="s">age</span><span class="sh">"</span> <span class="ow">and</span> <span class="ow">not</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">int</span><span class="p">):</span> <span class="k">raise</span> <span class="nc">TypeError</span><span class="p">(</span><span class="sh">"</span><span class="s">age must be int</span><span class="sh">"</span><span class="p">)</span> <span class="nf">super</span><span class="p">().</span><span class="nf">__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="c1"># not self.name = value </span></code></pre> </div> <h2> The Container Protocol in Full </h2> <p>To make a proper sequence, you need <code>__len__</code> and <code>__getitem__</code>. Python derives a surprising amount from these two:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight python"><code><span class="k">class</span> <span class="nc">Fibonacci</span><span class="p">:</span> <span class="k">def</span> <span class="nf">__len__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span> <span class="k">return</span> <span class="mi">100</span> <span class="k">def</span> <span class="nf">__getitem__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">index</span><span class="p">):</span> <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="nb">slice</span><span class="p">):</span> <span class="k">return</span> <span class="p">[</span><span class="n">self</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="o">*</span><span class="n">index</span><span class="p">.</span><span class="nf">indices</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">self</span><span class="p">)))]</span> <span class="k">if</span> <span class="n">index</span> <span class="o"><</span> <span class="mi">0</span><span class="p">:</span> <span class="n">index</span> <span class="o">+=</span> <span class="nf">len</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="mi">0</span> <span class="o"><=</span> <span class="n">index</span> <span class="o"><</span> <span class="nf">len</span><span class="p">(</span><span class="n">self</span><span class="p">):</span> <span class="k">raise</span> <span class="nc">IndexError</span><span class="p">(</span><span class="n">index</span><span class="p">)</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">index</span><span class="p">):</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">b</span><span class="p">,</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span> <span class="k">return</span> <span class="n">a</span> <span class="n">fib</span> <span class="o">=</span> <span class="nc">Fibonacci</span><span class="p">()</span> <span class="nf">print</span><span class="p">(</span><span class="n">fib</span><span class="p">[</span><span class="mi">10</span><span class="p">])</span> <span class="c1"># 55 </span><span class="nf">print</span><span class="p">(</span><span class="n">fib</span><span class="p">[</span><span class="mi">2</span><span class="p">:</span><span class="mi">5</span><span class="p">])</span> <span class="c1"># [1, 2, 3] </span><span class="nf">print</span><span class="p">(</span><span class="mi">10</span> <span class="ow">in</span> <span class="n">fib</span><span class="p">)</span> <span class="c1"># works: Python iterates using __getitem__ </span><span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">fib</span><span class="p">:</span> <span class="c1"># works: iteration falls back to __getitem__ </span> <span class="k">pass</span> </code></pre> </div> <p><code>in</code> and <code>for</code> both fall back to <code>__getitem__</code>-based iteration if <code>__contains__</code> and <code>__iter__</code> are not defined. Python calls <code>__getitem__</code> with increasing integer indices starting at 0 until it gets an <code>IndexError</code>. This fallback exists for backward compatibility but you should define <code>__iter__</code> explicitly for anything you intend to be iterable.</p> <p>For mappings, define <code>__getitem__</code>, <code>__setitem__</code>, <code>__delitem__</code>, <code>__len__</code>, and <code>__iter__</code>. Inheriting from <code>collections.abc.MutableMapping</code> and implementing those five methods gives you <code>keys()</code>, <code>values()</code>, <code>items()</code>, <code>get()</code>, <code>update()</code>, <code>pop()</code>, and the rest for free through mixin implementations.</p> <h2> <strong>slots</strong>: Memory Layout as a Data Model Feature </h2> <p><code>__slots__</code> is part of the data model. Defining it on a class prevents the creation of <code>__dict__</code> on instances and instead allocates a fixed set of descriptors for the named attributes.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight python"><code><span class="k">class</span> <span class="nc">Point</span><span class="p">:</span> <span class="n">__slots__</span> <span class="o">=</span> <span class="p">(</span><span class="sh">"</span><span class="s">x</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">y</span><span class="sh">"</span><span class="p">)</span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">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="n">self</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span> <span class="n">self</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span> <span class="n">p</span> <span class="o">=</span> <span class="nc">Point</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="n">p</span><span class="p">.</span><span class="n">z</span> <span class="o">=</span> <span class="mi">3</span> <span class="c1"># AttributeError: 'Point' object has no attribute 'z' </span></code></pre> </div> <p>The memory savings are significant for classes with many instances. A regular instance carries a <code>__dict__</code> (a hash table) and a <code>__weakref__</code> slot. A slotted instance carries only the declared attributes. For a class like <code>Point</code> that might be instantiated millions of times, this matters.</p> <p><code>__slots__</code> interacts with inheritance in ways that require care. If a subclass does not define <code>__slots__</code>, instances get a <code>__dict__</code> again, defeating the purpose. Every class in the hierarchy needs to define <code>__slots__</code> for the optimization to hold all the way down.</p> <h2> The One Rule That Ties Everything Together </h2> <p>Dunder methods are a protocol, not a feature list. Every method participates in a documented contract. <code>__eq__</code> implies <code>__hash__</code> constraints. <code>__iter__</code> implies <code>__next__</code>. <code>__enter__</code> implies <code>__exit__</code>. <code>__get__</code> participating in the descriptor protocol implies understanding when <code>obj</code> is <code>None</code>.</p> <p>The mistakes come from implementing part of a protocol and ignoring the rest. Define <code>__eq__</code> without thinking about <code>__hash__</code> and your objects silently break in sets. Define <code>__iter__</code> without <code>StopIteration</code> handling and your loops never end. Define <code>__enter__</code> without a matching <code>__exit__</code> and resources leak.</p> <p>Read the protocol documentation for any dunder you implement. The contracts are specified, they are not long, and violating them produces bugs that are difficult to reproduce and harder to trace. The data model is the most reliable part of Python. It rewards the developers who actually read it.</p> <h2> Further Reading </h2> <ul> <li> <a href="https://docs.python.org/3/reference/datamodel.html" rel="noopener noreferrer">Python Data Model (docs.python.org)</a> - read this end to end at least once, it is shorter than you expect</li> <li> <a href="https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/" rel="noopener noreferrer">Luciano Ramalho: Fluent Python, Part I</a> - the best book-length treatment of the data model with worked examples throughout</li> <li> <a href="https://docs.python.org/3/library/collections.abc.html" rel="noopener noreferrer">collections.abc documentation</a> - the abstract base classes that define the container protocols formally</li> <li> <a href="https://www.youtube.com/watch?v=HTLu2DFOdTg" rel="noopener noreferrer">Raymond Hettinger: Python's Class Development Toolkit (PyCon 2013)</a> - still the best talk on building well-behaved Python classes</li> </ul>
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。