Python 物件屬性預設儲存在字典中,帶來彈性的同時也消耗了大量記憶體,尤其在物件數量龐大的情況下。slots 機制允許預先定義物件屬性,Python 會使用更緊湊的資料結構儲存,避免建立字典,進而節省記憶體並提升效能。這對於需要大量物件的應用程式,例如機器學習模型訓練、資料處理管道或高併發伺服器,尤其有效。然而,slots 也存在一些限制,例如無法動態新增屬性、繼承時需要特別注意 __slots__ 的定義等。

Python 的 slots 機制:節省記憶體與效能最佳化的利器

slots 的核心概念與應用場景

在 Python 中,每個物件的屬性都儲存在一個字典 (dict) 中。這提供了高度的彈性,允許動態新增或修改屬性。然而,當物件數量龐大時,這也會造成記憶體浪費和效能下降。slots 機制則提供了一種解決方案,它允許我們預先定義物件的屬性集合,避免建立額外的字典,從而節省記憶體並提升效能。slots 特別適用於需要建立大量物件的應用程式,例如機器學習、資料處理管道或高併發伺服器。

slots 的內部機制:靜態記憶體組態

當我們在類別中定義 __slots__ 時,Python 會為該類別的每個例項分配一個緊湊的結構,而不是建立一個 dict。這個結構包含預先定義的屬性,其記憶體空間是靜態分配的。讓我們來看一個例子:

class Normal:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Slotted:
    __slots__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

Normal 類別中,每個物件都會擁有一個 dict 來儲存 xy 的值。而在 Slotted 類別中,xy 的記憶體位置是預先分配好的,不再需要額外的 dict

內容解密:

Normal 類別的例項會在建立時動態組態一個字典,用於儲存屬性,這會造成記憶體額外開銷。而 Slotted 類別則透過 __slots__ 預先定義了屬性,Python 會直接組態固定大小的記憶體空間給這些屬性,避免了字典的建立,因此節省了記憶體。

slots 的限制與注意事項

使用 slots 會帶來一些限制:

  1. 無法動態新增屬性: 一旦定義了 __slots__,就無法再為物件新增不在 __slots__ 中定義的屬性。嘗試新增未定義的屬性會引發 AttributeError

  2. 繼承的影響: 如果父類別使用了 __slots__,子類別必須也定義 __slots__ 才能新增自己的屬性。否則,子類別將無法新增任何屬性。如果父類別沒有定義 __slots__,則子類別可以正常新增屬性。多重繼承的情況下,所有父類別都必須使用 slots,否則會造成錯誤。

  3. 弱參照 (weakref): 預設情況下,使用 slots 的類別不支援弱參照。如果需要使用弱參照,必須將 '__weakref__' 加入 __slots__ 列表中。

  4. 序列化: 使用 slots 的類別可能會影響序列化 (例如使用 pickle 模組)。因為沒有 dict,標準的序列化機制可能無法正常運作。這時需要手動實作 __getstate____setstate__ 方法來控制序列化過程。

slots 與 dataclass 的整合

從 Python 3.10 開始,@dataclass 裝飾器支援 slots=True 引數,方便我們建立使用 slots 的資料類別,同時保留 dataclass 的簡潔語法。

from dataclasses import dataclass

@dataclass(slots=True)
class Point:
    x: int
    y: int

這結合了 dataclass 的自動產生 __init__, __repr__ 等方法的優點,以及 slots 節省記憶體的優點。

內容解密:

@dataclass(slots=True)slots 的功能與 dataclass 的便利性結合,簡化了程式碼撰寫,並同時獲得記憶體最佳化的效益。

實務應用案例:

以下是一些 slots 在實際應用中的例子:

1. 機器學習/資料處理管道: 在處理大量資料時,每個資料點都可以表示為一個物件。使用 slots 可以顯著減少記憶體消耗。

class DataPoint:
    __slots__ = ('id', 'timestamp', 'value')
    def __init__(self, id, timestamp, value):
        self.id = id
        self.timestamp = timestamp
        self.value = value

# 生成大量資料點
data_points = [DataPoint(i, 1627845123 + i, i * 0.5) for i in range(1_000_000)]

2. 伺服器應用程式: 在高併發的伺服器應用程式中,每個客戶端連線都可以表示為一個物件。使用 slots 可以減少每個連線物件的記憶體佔用,從而提高伺服器處理能力。

3. 快取和物件池: 在快取或物件池系統中,物件會被頻繁地建立和銷毀。使用 slots 可以減少記憶體碎片並加快垃圾回收。

內容解密:

以上三個例子都說明瞭在需要大量物件的情況下,使用 slots 可以有效減少記憶體佔用,提升效能。 資料點、客戶端連線和快取專案這些物件通常具有固定數量的屬性,因此非常適合使用 slots 機制。