Python sys.getsizeof()とは?オブジェクトのメモリサイズを調べる方法

Pythonのsys.getsizeof()は、オブジェクトがメモリ上で占めるバイト数を返す関数です。

ただし「中身まで含めた合計」ではないという重要な制約があります。ここを誤解すると、まったく違う数字を見ることになります。


sys.getsizeof()の基本的な使い方

import sys

for v in [0, 1, "", "a", [], [1, 2, 3], {}, None]:
    print(f"{repr(v):12} {sys.getsizeof(v):>4} バイト")

実行結果(64ビット環境の例):

0              24 バイト
1              28 バイト
''             49 バイト
'a'            50 バイト
[]             56 バイト
[1, 2, 3]     120 バイト
{}             64 バイト
None           16 バイト

数値がやや大きく感じられます。Pythonではすべてがオブジェクトで、型情報や参照カウントなどの管理データを持つためです。

整数の0でさえ24バイト——C言語のintが4バイトであることを考えると、その差が分かります。


最大の注意点|中身は数えられない

import sys

big = [list(range(1000))]

print("外側のリスト:", sys.getsizeof(big))

実行結果:

外側のリスト: 64

1000個の要素が入っているのにたった64バイトです。

リストが持っているのは中身への「参照」だけで、実体は別の場所にあります。getsizeof()は参照の分しか数えません。

import sys

inner = [1, 2, 3]
outer = [inner]

print("outer:", sys.getsizeof(outer))
print("inner:", sys.getsizeof(inner))

実行結果:

outer: 64
inner: 120

外側より中身のほうが大きい——「浅い」計測だと理解していないと必ず混乱します。


中身も含めて数えるには

import sys

big = [list(range(1000))]

total = sys.getsizeof(big) + sum(sys.getsizeof(x) for x in big)
print("中身を足すと:", total)

実行結果:

中身を足すと: 8120

64バイトから8120バイトになりました。127倍の違いです。

ただしこれも完全ではありません。さらに中の整数まで数えるなら再帰が必要で、同じオブジェクトを複数から参照している場合は二重に数えてしまいます。

正確なメモリ使用量を知りたいなら、tracemalloc(標準ライブラリ)やmemory_profilerを使ってください。getsizeof()は単体のオブジェクトを比較する道具と割り切るのが正解です。


リストは少しずつ余分に確保する

import sys

l = []
prev = sys.getsizeof(l)
print("0要素:", prev)

for i in range(1, 9):
    l.append(i)
    s = sys.getsizeof(l)
    if s != prev:
        print(f"{i}要素: {s}(増えた)")
    prev = s

実行結果:

0要素: 56
1要素: 88(増えた)
5要素: 120(増えた)

1要素ごとにサイズが増えるわけではありません。まとめて確保しておき、足りなくなったら広げるという動きです。

毎回確保し直すと遅くなるための工夫で、append()が高速な理由でもあります。


型による違いを比べる

import sys

data = list(range(1000))

print("list :", sys.getsizeof(data))
print("tuple:", sys.getsizeof(tuple(data)))
print("set  :", sys.getsizeof(set(data)))

実行結果:

list : 8056
tuple: 8040
set  : 32984

同じ1000件でも、型によって必要なメモリが変わります。setはlistの4倍以上です。

型 特徴
tuple 変更しない前提なので余分な確保がない
list 追加に備えて余分に確保する
set・dict 高速な検索のためすき間を持つ

「変更しないならタプル」と言われる理由の1つが、このメモリ効率です。


ジェネレータとの比較

import sys

print("リスト   :", sys.getsizeof([x * 2 for x in range(10000)]))
print("ジェネレータ:", sys.getsizeof((x * 2 for x in range(10000))))

実行結果:

リスト   : 85176
ジェネレータ: 112

ジェネレータは値を持たず作り方だけを覚えているため、件数が増えてもサイズが変わりません。

ここはgetsizeof()の「浅い」性質がむしろ実態を正しく表している珍しい例です。大量データを扱うときにジェネレータが推奨される理由が、数字で確認できます。


__sizeof__を持たない型もある

import sys

sys.getsizeof(obj, 0)   # 第2引数は既定値

第2引数を渡しておくと、サイズを取得できない型でもTypeErrorにならず既定値が返ります。

NumPyの配列など、独自の管理をしている型ではgetsizeof()が実態と合わないことがあります。NumPyならarr.nbytesを使ってください。


sys.getsizeof()のまとめ

  • getsizeof()はオブジェクト単体のバイト数を返す
  • 中身は数えない——リストの中の要素は含まれない
  • 合計が欲しいなら自分で足す(完全ではない)
  • リストは余分に確保するので、要素ごとに増えるわけではない
  • tuple は list より省メモリ、set・dict はすき間を持つ
  • 正確な計測はtracemalloc・memory_profilerへ

あわせて読みたい

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

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