<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Dataclasses on KbWen Blog</title>
    <link>https://www.kbwen.com/tags/dataclasses/</link>
    <description>KbWen is a practical technology blog about AI systems, machine learning, Python, data engineering, and software development.</description>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <image>
      <url>https://www.kbwen.com/images/og-default.png</url>
      <title>KbWen Blog</title>
      <link>https://www.kbwen.com/</link>
    </image>
    
    <lastBuildDate>Mon, 12 Oct 2026 10:05:00 +0800</lastBuildDate><atom:link href="https://www.kbwen.com/tags/dataclasses/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Python __slots__ and class attribute defaults</title>
      <link>https://www.kbwen.com/python-slots-class-attribute-defaults/</link>
      <pubDate>Mon, 12 Oct 2026 10:05:00 +0800</pubDate><dc:creator>KbWen</dc:creator>
      <guid>https://www.kbwen.com/python-slots-class-attribute-defaults/</guid>
      <description>Let&amp;#39;s see what happens when an attribute listed in __slots__ is given a default value.</description>
      <content:encoded><![CDATA[<p><code>__slots__</code> is a class attribute that lists the names an instance is allowed to have. A class that declares it gets instances with no <code>__dict__</code> (as long as no base class supplies one), and assigning any name that isn&rsquo;t on the list raises <code>AttributeError</code>. On an ordinary class, a common way to give an attribute a default is to write it in the class body, and on a slotted class that looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Point</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">(</span><span class="s1">&#39;x&#39;</span><span class="p">,</span> <span class="s1">&#39;y&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ValueError: &#39;x&#39; in __slots__ conflicts with class variable
</span></span></code></pre></div><p>The class statement itself fails, before any instance exists.</p>
<p>Next, take the <code>x = 0</code> line out and print <code>Point.x</code>. It gives <code>&lt;member 'x' of 'Point' objects&gt;</code>, and <code>'x' in vars(Point)</code> is <code>True</code>. That object is in the class namespace under the name <code>x</code>, and Python put it there while building the class. The <a href="https://docs.python.org/3.14/reference/datamodel.html#slots">data model reference</a> says <code>__slots__</code> &ldquo;are implemented at the class level by creating descriptors for each variable name.&rdquo; When an instance reads or writes <code>p.x</code>, the lookup finds this member descriptor on the class first, and the descriptor goes to the instance for the value. The instance itself keeps &ldquo;a fixed-length array of slot values&rdquo; in place of a dictionary (<a href="https://docs.python.org/3.14/howto/descriptor.html#member-objects-and-slots">Descriptor Guide</a>), and the descriptor on the class is the only route from the name <code>x</code> to that value.</p>
<p>A default written as <code>x = 0</code> would need that same name in the same class namespace. CPython checks for this while it creates the class (in <a href="https://github.com/python/cpython/blob/3.14/Objects/typeobject.c">Objects/typeobject.c</a>), and if a slot name is already a class variable it stops with the <code>ValueError</code> above.</p>
<p>Once the class already exists, though, nothing stops you from assigning the class attribute yourself:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Later</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">(</span><span class="s1">&#39;x&#39;</span><span class="p">,)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">Later</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="n">o</span> <span class="o">=</span> <span class="n">Later</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">o</span><span class="o">.</span><span class="n">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">o</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">5</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0
</span></span><span class="line"><span class="cl">AttributeError: &#39;Later&#39; object attribute &#39;x&#39; is read-only
</span></span></code></pre></div><p>This time there&rsquo;s no error at the assignment. <code>Later.x = 0</code> simply replaces the descriptor, which is the case the data model reference has in mind when it says the class attribute &ldquo;would overwrite the descriptor assignment,&rdquo; and <code>vars(Later)['x']</code> is now the plain <code>0</code>. Reading <code>o.x</code> goes to the class and finds that <code>0</code> there, the same <code>0</code> for every instance, and it looks like a default that worked. Writing <code>o.x = 5</code> is where it breaks, because the plain <code>0</code> has no way to store a value into the instance, and the instance has no <code>__dict__</code> to fall back to. CPython reports that case as &ldquo;read-only,&rdquo; which can be confusing the first time. Nothing in the class says read-only anywhere.</p>
<p>For a default, the simplest place is the <code>__init__</code> signature, <code>def __init__(self, x=0, y=0)</code>, and then <code>self.x = x</code> in the body stores the value into the slot like any other assignment. If the class is a dataclass anyway, the other way is <code>@dataclass(slots=True)</code>. There you write <code>x: int = 0</code> as usual, and the decorator handles the conflict for you. In <a href="https://github.com/python/cpython/blob/3.14/Lib/dataclasses.py">Lib/dataclasses.py</a>, the code for <code>slots=True</code> takes the field names out of a copy of the class dictionary and builds a new class from it, since <code>__slots__</code> can&rsquo;t be added to a class after it exists. The names on the new class are member descriptors again, the same as in a slotted class you write by hand, and the defaults live in the generated <code>__init__</code>.</p>
<p>The examples here were run with Python 3.11.15, 3.12.13 and 3.14.3 on Windows through uv, and they behaved the same on all three. The explanation follows the 3.14 data model reference, the Descriptor Guide and the CPython 3.14 source, and it only looks at defaults, not at how much memory <code>__slots__</code> saves.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
