【Python】メモリ解放の基本と実践方法を初心者向けに解説

はじめに

Pythonでは、プログラムを実行中にメモリを自動的に管理してくれます。
しかし、データが増え続けるような処理や、大きなファイルを扱う場合には、メモリ使用量が気になることがあります。
いわゆるPython メモリ 解放を意識することで、必要以上にメモリを圧迫しない工夫ができるのがポイントです。

メモリの管理を怠ると、プログラムが遅くなったり、不安定になったりします。
そこで、この記事ではPythonのメモリ解放を理解するための基礎知識と実践的な方法を、なるべく専門用語を使わずに解説します。
初心者でもわかりやすいように、具体例やコード例を交えながら進めていきます。

この記事を読むとわかること

  • Pythonのメモリ管理がどのように行われているのか
  • 参照カウントとガーベジコレクションの基礎
  • delステートメントやgcモジュールを用いたメモリ解放方法
  • メモリ使用量を調べる基本的なやり方
  • 大規模データ処理でのメモリ管理の注意点

Pythonのメモリ管理とは

Pythonは基本的に、参照カウント方式と呼ばれる仕組みを使ってメモリを管理します。
さらに、それだけでは解放されないメモリ領域に対して ガーベジコレクション (gc)が働き、不要になったオブジェクトを掃除します。

参照カウントというのは、あるオブジェクトが「いくつの変数や他のオブジェクトから参照されているか」を数える仕組みです。
参照されている数(参照カウント)がゼロになったタイミングで、Pythonはそのオブジェクトを自動的に解放します。

一方で、循環参照が発生してしまうと、参照カウントがゼロにならなくても実質的に不要なオブジェクトが残ってしまうことがあります。
そこでガーベジコレクションが補助的に動き、不要になったオブジェクトをまとめて解放してくれます。

このように自動的に管理してくれるため、普段のちょっとしたスクリプト程度であれば、意識してメモリを解放する必要はあまりありません。
しかし、大きなデータ構造をたくさん使う場合や、サーバーで長時間動作させる場合には、Python メモリ 解放を意識して無駄を減らすことが大切です。

Pythonのメモリ解放を明示的に行う方法

Pythonの自動管理だけでなく、開発者が意図的にメモリを解放するための方法もいくつか存在します。
代表的なアプローチは、delステートメントgcモジュールを使う方法です。

delステートメントを使ったメモリ解放

オブジェクトを参照している変数を削除すると、そのオブジェクトへの参照カウントが減ります。
もし参照カウントがゼロになれば、Pythonは次のタイミングでそのオブジェクトを解放してくれます。
たとえば、大きなリストが必要なくなったときは以下のように書けます。

my_list = [i for i in range(1000000)]
# 大規模データを格納したと仮定

# my_listはここで用が済んだとする
del my_list

del my_listを実行することで、my_listが指していた大きなリストへの参照はゼロになり、以後Pythonがメモリ解放を自動で行います。
ただし、すぐに実行されるとは限らない点に注意してください。
Pythonは状況を見ながら最適なタイミングで解放処理を進めます。

gcモジュールを使った強制的なメモリ解放

Pythonにはgcモジュールが標準で含まれており、ガーベジコレクション(不要なオブジェクトの掃除)を明示的に呼び出すことができます。
通常はPythonが自動で行いますが、メモリを大量に使う処理の合間などで、強制的に解放したい場合に活用することもあります。

import gc

# 重たい処理を行ったあと、不要になったオブジェクトを解放したいときなど
gc.collect()

gc.collect()を呼び出すと、循環参照などにより残っている不要オブジェクトを検出して解放してくれます。
ただし、その分だけCPU時間を消費する可能性があるため、頻繁に呼び出しすぎるとパフォーマンスが落ちることがあります。
何でもかんでもgc.collect()を使えばいいわけではない点に注意が必要です。

メモリ解放のためにgc.collect()を多用しすぎると、逆に処理時間の増加を招く恐れがあります。 必要な場面だけで使うようにしましょう。

メモリ使用量を調べるテクニック

メモリの解放を考える前に、そもそもどのぐらいメモリを使っているのかを把握しておくことが大切です。
以下の方法を使うと、Pythonプログラム内でメモリ使用量をある程度確認できます。

sys.getsizeof()を使う

組み込みの**sys.getsizeof()**関数を使うと、指定したオブジェクトがおおよそどれぐらいメモリを占有しているかを調べることができます。

import sys

my_list = [i for i in range(100000)]
print(sys.getsizeof(my_list), "bytes")

ただし、この関数が返すのはオブジェクト本体のメモリ使用量のみです。
リスト内の要素1つ1つが別オブジェクトである場合、合計のメモリを厳密に把握するのは難しい点に注意しましょう。

外部ライブラリを使う方法

プログラム全体のメモリ使用量を細かくモニタリングしたい場合は、専用のライブラリを使う方法も検討できます。
たとえば、プロセスのメモリ使用量を確認するにはpsutilなどがありますが、本記事では基本的な仕組みに焦点を当てるため詳細は割愛します。

大規模データ処理での注意点

大量のデータを扱うときには、不要になった変数をこまめにdelしていくことや、一時ファイルを使ってメモリ上にデータをためすぎないことが大切です。
また、リストや辞書を扱っている場合は、使わなくなった段階で明確に参照を外す工夫が必要になります。

大きなリストや辞書のクリーンアップ

大量の要素を持つリストや辞書を長時間保持し続けると、メモリが圧迫されやすくなります。
以下は大きな辞書を生成し、あとでクリーンアップする例です。

data_dict = {i: str(i) for i in range(1000000)}

# ここでデータをいろいろと処理したと仮定

del data_dict  # もう必要ないのでクリーンアップ

一度大きなリストや辞書を生成すると、それをずっと保持したままだとメモリを使い続けることになります。
不要になったらdelを使って参照を切り、必要に応じてgc.collect()を呼ぶと、メモリが解放されるきっかけになります。

with文によるリソースの自動解放

ファイル操作やネットワーク接続を行う場合には、with文を使うと処理終了時に自動的にリソースを閉じてくれます。
これは厳密にはメモリを「解放」するというより、ファイルやソケットといった外部リソースを「終了」してリークを防ぐための工夫です。

with open("data.txt", "r") as f:
    lines = f.readlines()

# with文のブロックを抜けたタイミングでファイルが閉じられる

メモリ管理はオブジェクトだけではなく、外部リソースの管理にも関わってきます。
長期間動かすプログラムでは、開いたままのファイルハンドルがどんどん溜まるといった問題も起こりえますので、with文で確実に解放する習慣が大切です。

大規模データを頻繁に読み込む際は、読み込み単位を小さくしてこまめに処理する方法も検討できます。

まとめ

ここまで、Python メモリ 解放に関する基礎知識と具体的なアプローチを紹介しました。
Pythonのメモリ管理は参照カウントとガーベジコレクションによって自動化されていますが、大規模データを扱う場合などには、delステートメントやgcモジュールを使ってメモリを意識的に開放するのが効果的です。

また、メモリ使用量を把握するための手段としてsys.getsizeof()などがあることもポイントです。
不要になったオブジェクトを早めにクリーンアップする習慣をつけるだけで、プログラムが安定して動作しやすくなります。

自動でやってくれる仕組みに頼りきりにせず、必要に応じてしっかりとメモリ管理を意識できるようになると、安定感のあるアプリケーションを作りやすくなるでしょう。
皆さんも、ぜひ自分の開発環境で試してみてください。

Pythonをマスターしよう

この記事で学んだPythonの知識をさらに伸ばしませんか?
Udemyには、現場ですぐ使えるスキルを身につけられる実践的な講座が揃っています。