<?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>Memory on KbWen Blog</title>
    <link>https://www.kbwen.com/tags/memory/</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:00:00 +0800</lastBuildDate><atom:link href="https://www.kbwen.com/tags/memory/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Python 的 __slots__ 能省下多少記憶體</title>
      <link>https://www.kbwen.com/python-slots-instance-memory/</link>
      <pubDate>Mon, 12 Oct 2026 10:00:00 +0800</pubDate><dc:creator>KbWen</dc:creator>
      <guid>https://www.kbwen.com/python-slots-instance-memory/</guid>
      <description>兩個只差在 __slots__ 這個區塊的 class，量量看最後的差別是什麼。</description>
      <content:encoded><![CDATA[<p>Python 的物件屬性中，一般是在每個實例的字典 <code>__dict__</code>，寫 <code>self.x = 1</code> 等同於字典加入 <code>'x': 1</code>。如果在 class 裡宣告 <code>__slots__</code>，實例就不會有 <code>__dict__</code>。</p>
<h2 id="兩個-class">兩個 class</h2>
<p>先寫兩個 class，其中的差別在有沒有 <code>__slots__</code>：</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">Plain</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">y</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">z</span> <span class="o">=</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Slotted</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="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;y&#34;</span><span class="p">,</span> <span class="s2">&#34;z&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">y</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">z</span> <span class="o">=</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span>
</span></span></code></pre></div><p>接下來，各建立一個實例來看看：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">p</span> <span class="o">=</span> <span class="n">Plain</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="mi">3</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">s</span> <span class="o">=</span> <span class="n">Slotted</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="mi">3</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">hasattr</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="s2">&#34;__dict__&#34;</span><span class="p">)</span>   <span class="c1"># True</span>
</span></span><span class="line"><span class="cl"><span class="n">p</span><span class="o">.</span><span class="vm">__dict__</span>               <span class="c1"># {&#39;x&#39;: 1, &#39;y&#39;: 2, &#39;z&#39;: 3}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">hasattr</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="s2">&#34;__dict__&#34;</span><span class="p">)</span>   <span class="c1"># False</span>
</span></span></code></pre></div><p><a href="https://docs.python.org/3/reference/datamodel.html#slots">Python 語言參考</a>說 <code>__slots__</code> 會替宣告的變數保留空間，並且不會替每個實例建立 <code>__dict__</code> 和 <code>__weakref__</code>。</p>
<h2 id="接下來我們測測看">接下來我們測測看</h2>
<p>光看有沒有字典，還看不出有甚麼差別，所以利用 <code>tracemalloc</code> 測量每個實例平均會佔多少記憶體：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">tracemalloc</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">per_instance</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">n</span><span class="o">=</span><span class="mi">500_000</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">objs</span> <span class="o">=</span> <span class="p">[</span><span class="kc">None</span><span class="p">]</span> <span class="o">*</span> <span class="n">n</span>          <span class="c1"># 清單先開好，不計入</span>
</span></span><span class="line"><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">objs</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="bp">cls</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="mi">3</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">current</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">get_traced_memory</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">current</span> <span class="o">/</span> <span class="n">n</span>
</span></span></code></pre></div><p>在 Windows 64 位元的 CPython 3.11.15 上跑，<code>Plain</code> 每個實例大約 96 bytes，<code>Slotted</code> 大約 56 bytes，差了 40 bytes；換到 3.14.3，也是一樣。因為屬性值都是 1、2、3 這種小整數，CPython 本來就會共用同一個整數物件（參考之前寫的<a href="/python-is-vs-equals/">Python 的 is 和 == 差在哪</a>），所以這裡量測到的只有實例本身。</p>
<p>不過從 3.11 開始，一般物件的屬性值會先放在一個專門的陣列裡，要等到有程式真的去讀 <code>__dict__</code>，字典才會建立，CPython 的<a href="https://github.com/python/cpython/blob/3.14/Objects/object_layout.md">內部文件</a>有說明這個設計（lazily created when the <code>__dict__</code> attribute is specifically asked for）。也就是說，開始量測時，<code>Plain</code> 的實例內其實還沒有字典。</p>
<p>那如果真的有程式讀了呢？在迴圈裡加入一行，等待物件建好之後讀取 <code>objs[i].__dict__</code>，<code>Plain</code> 就從 96 變成大約 160 bytes，其中多出來的這些部分來自此時才建立的字典；若 <code>Slotted</code> 加上同一行則會直接拋出 <code>AttributeError</code>，因為它沒有 <code>__dict__</code> 可以讀。</p>
<p>同段程式如果是在 3.10.20 上面跑，<code>Plain</code> 不管有沒有讀取 <code>__dict__</code> 都是 152，<code>Slotted</code> 一樣是 56，因為當時只要設定第一個屬性（這裡是在 <code>__init__</code> 裡），字典就會跟著建立。所以 <code>__slots__</code> 到底能夠節省多少，也跟 Python 版本有關。</p>
<h2 id="換來的限制">換來的限制</h2>
<p>一開始字典本來是可以隨時加新屬性的地方，拿掉之後，等於這個能力也跟著沒了。對 <code>Slotted</code> 設定沒宣告過的屬性，會拋出錯誤：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">s</span><span class="o">.</span><span class="n">w</span> <span class="o">=</span> <span class="mi">4</span>
</span></span><span class="line"><span class="cl"><span class="c1"># AttributeError: &#39;Slotted&#39; object has no attribute &#39;w&#39;</span>
</span></span></code></pre></div><p>在 3.13.12 和 3.14.3 上，錯誤訊息還會多 <code>and no __dict__ for setting new attributes</code>。另外，繼承 <code>Slotted</code> 的子類別如果自己沒有宣告 <code>__slots__</code>，實例又會有 <code>__dict__</code>，也就可以再加新屬性。</p>
<h2 id="那什麼時候值得用">那什麼時候值得用</h2>
<p>40 bytes 對幾個、幾十個物件來說沒什麼感覺，要一次建出幾十萬個同型的小物件，每個實例省下的部分總和就會很有感覺。所以要不要加 <code>__slots__</code>，主要也要看這個 class 會被建立多少次，使用情境和範圍，以及之後需不需要替實例補上新的屬性。而屬性數量、平台或版本不同，也會有點不一樣，所以如果想知道在自己的環境下差多少，要在什麼情況下使用，可以把自己的 class 交給上面的 <code>per_instance</code> 測量，也能順便看看程式裡有沒有地方會去讀 <code>__dict__</code>。</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
