Python __slots__とは?メモリを節約するクラスの書き方

__slots__は、クラスが持てる属性名をあらかじめ固定する仕組みです。

メモリ使用量が大きく下がり、typoによる属性の作り間違いも防げます。


__slots__の基本

class Point:
    __slots__ = ("x", "y")

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

p = Point(1, 2)
print(p.x, p.y)

p.z = 3

実行結果:

1 2
AttributeError: 'Point' object has no attribute 'z'

クラスの先頭に属性名のタプルを書くだけです。定義していない名前への代入は拒否されます。


なぜメモリが減るのか

通常のPythonオブジェクトは、属性を__dict__という辞書に入れています。

import sys

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

class WithSlots:
    __slots__ = ("x", "y")

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

a = NoSlots(1, 2)
b = WithSlots(1, 2)

print("通常 :", sys.getsizeof(a), "+ dict", sys.getsizeof(a.__dict__))
print("slots:", sys.getsizeof(b))

実行結果:

通常 : 48 + dict 104
slots: 48

__slots__を使うと__dict__そのものが作られません。

b.__dict__

実行結果:

AttributeError: 'WithSlots' object has no attribute '__dict__'

辞書の代わりに、固定長の配列のような領域に値が置かれます。


実測してみる

import tracemalloc

def measure(cls, n=100000):
    tracemalloc.start()
    objs = [cls(1, 2) for _ in range(n)]
    cur, peak = tracemalloc.get_traced_memory()
    tracemalloc.stop()
    return cur / 1024 / 1024

print(f"通常 : {measure(NoSlots):.1f} MB")
print(f"slots: {measure(WithSlots):.1f} MB")

実行結果:

通常 : 15.3 MB
slots: 5.3 MB

10万個で約3分の1になりました。

逆に言えば、オブジェクトを数個しか作らないなら効果はほぼありません。数万個以上を扱うときに検討してください。


typoを防げる

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

a = NoSlots(1, 2)
a.z = 3
print(a.z)

実行結果:

3

通常のクラスはどんな属性でも後から足せます。柔軟ですが、self.namaeのような書き間違いも黙って通ります。

__slots__があれば、その場でAttributeErrorになります。メモリ削減より、こちらを目的に使う人も多い機能です。


継承すると効果が消える

最大の落とし穴です。

class Base:
    __slots__ = ("x", "y")

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

class Child(Base):
    pass

c = Child(1, 2)
c.z = 9
print(c.z, hasattr(c, "__dict__"))

実行結果:

9 True

子クラスに__slots__を書かないと、__dict__が復活します。親で節約した意味がなくなります。

追加する属性がないなら、空のタプルを書きます。

class Child(Base):
    __slots__ = ()

c = Child(1, 2)
c.z = 9

実行結果:

AttributeError: 'Child' object has no attribute 'z'

継承階層のすべてに__slots__が必要と覚えてください。


使うときの注意点

  • 属性を動的に追加できない——プラグイン的な設計とは相性が悪い
  • __dict__を前提にしたライブラリで動かないことがある
  • クラス変数と同じ名前を__slots__に書けない
  • 多重継承では、複数の親が__slots__を持つと衝突する場合がある

@dataclassと併用する場合、Python 3.10以降なら@dataclass(slots=True)が使えます。3.9以前では__slots__を手で書く必要があります。

「Python @dataclassとは?クラスを短く書く方法を解説」も参考にしてください。


__slots__のまとめ

  • 持てる属性名をタプルで固定する
  • __dict__が作られないのでメモリが減る(実測で約3分の1)
  • 定義外の属性はAttributeError——typoを防げる
  • 子クラスにも書かないと効果が消える(不要なら())
  • 効果が出るのは数万個以上を扱うとき
  • 動的な属性追加が必要な設計には向かない

あわせて読みたい

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら