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を防げる - 子クラスにも書かないと効果が消える(不要なら
()) - 効果が出るのは数万個以上を扱うとき
- 動的な属性追加が必要な設計には向かない



