2010年11月22日公開
Python の __del__ メソッド、ガベージコレクタ、循環参照、そして弱参照についての解説と考察。
C/C++ では、malloc
を使って確保したメモリや new
で作成したオブジェクトについては、必ず対応する free
/ delete
を呼ばなければならない。これを忘れるといつまでも解放されないメモリがプログラムの生存期間中居残ることになる。これをメモリリークと呼ぶ。メモリリークがあると、確保されたままになったメモリが OS や他のプロセスを圧迫して、システム全体のパフォーマンスに影響を与えることがある。
Python (およびその他多くの軽量言語)では、基本的に作成したオブジェクトの解放について考えなくてもよい。使われなくなったオブジェクトに割り当てられていたメモリは、インタプリタによって自動的に解放される。
では、あるオブジェクトが「使われなくなった」ことを、インタプリタはどうやって判定しているのか。Python では、それは参照カウンタというもので実現されている。これは簡単に言うと次のようなものである:
del
されたりすると、変数にもともと割り当てられていたオブジェクトの参照カウントは 1 減らされる。オブジェクトの参照カウントは、sys
モジュールの getrefcount
関数で取得できる。
C:\Users\mshibata>python Python 2.7 (r27:82525, Jul 4 2010, 09:01:59) [MSC v.1500 32 bit (Intel)] on win 32 Type "help", "copyright", "credits" or "license" for more information. >>> a = object() >>> import sys >>> sys.getrefcount(a) 2 >>> b = a >>> sys.getrefcount(a) 3 >>> a = None >>> sys.getrefcount(b) 2
最初の sys.getrefcount
で帰ってくるのは 1 ではないのか、と思うかもしれない。しかしこれでいいのだ。なぜならオブジェクトが getrefcount
の引数として渡されるときにその参照カウントがひとつ増えるからである。
オブジェクトの参照カウントが 0 になったとき、そのオブジェクトの __del__
メソッドが(もし定義されていれば)呼ばれる。その意味で、__del__
メソッドはデストラクタとも呼ばれる。このメソッドで、ファイルハンドルをクローズするとか、メンバ変数として保持していたリソースを解放するといった処理を行うことが想定されている。
とはいえ当然のことながら、メンバ変数として保持していたオブジェクトが Python のクラスに由来するインスタンスであるのなら(つまりたいていの場合は)、わざわざ __del__
メソッドを付けてそれを解放するような処理は必要ない(そんなことはやろうとしたこともないはず)。親にあたるオブジェクトへの参照が 0 になった時点で、そのメンバ変数のオブジェクトの参照カウントも適切に 1 ずつ減らされるからである。その結果が 0 になれば、メンバ変数として割り当てられていたオブジェクトも解放されることになる。
ドキュメントにも書かれていることだが、del x
を実行したからといって、そのタイミングで x.__del__()
が実行されるわけではないことに注意。del
文は、オブジェクトを指している変数名を名前空間から抹消して、そのオブジェクトへの参照カウントを減らすだけである。その結果参照カウントが 0 になれば、そのときはじめて __del__
が呼ばれる。
すでに書いたとおり、インスタンスの後処理として __del__
が必要になることはあまりない。あるとすれば、次のようなケースが考えられる。
__init__
で確保したオブジェクトを明示的に解放する必要がある場合。__del__
はその性質上、__init__
に対応するものと思えるかもしれないが、厳密にはそうではない。対応するものを挙げるとすれば、__init__
ではなく __new__
である(“Python Gotchas 1: __del__ is not the opposite of __init__”)。__init__
は、それが呼ばれた時点で、オブジェクトそのものが作成されたことは確定している。__init__
ではその初期化処理をするだけである。だから、__init__
が失敗したときでも、そのオブジェクトが解放されるタイミングで __del__
が呼ばれる。「__init__
が失敗したとき」というのは、つまりは __init__
内で例外が発生したときである。したがって、注意深くなるのであれば、__del__
内では __init__
の処理が行われていること(あるメンバ変数が存在することなど)を前提としてはいけないことになる。
例外といえば、また、__del__
で発生した例外は外部からは捕らえられないことにも注意。これは、そもそも __del__
が実行されるタイミングが決められないからである。__del__
内で発生した例外が捕捉されなかったばあい、それはそのまま無視されてプログラムの実行が続く(例外が起こったこと自体は標準エラー出力で報告される)。
class C(object):
def __del__(self):
raise Exception('exception in __del__.')
print 'can you see me?' # 例外が発生するので、この文は実行されない。
def main():
a = C()
del a
print 'after "del a"' # この文は何事もなかったかのように実行され、プログラムはつつがなく終了する。
if __name__ == '__main__':
main()
これにより、__del__
内で解放したつもりのリソースがじつは解放されていなかった、ということが起こりうる。
だんだん __del__
を使いたくなくなってくる。
さらに、__del__
はインタプリタが終了する時点で残っているオブジェクトに対して呼ばれる保証がない。これはドキュメントにそう書かれている。そうなると、「絶対にぜったいにやらなきゃいけない終了処理」は、__del__
に書くことができないことになる。
循環参照とは、オブジェクトが別のオブジェクトへの参照を保持しているという参照の連鎖が、どこかで一巡して元のオブジェクトに到達している状態のことである。いちばん単純な例は、ふたつのオブジェクトがお互いを参照しあっている、次のようなもの。
class C(object):
pass
a = C()
b = C()
a.x = b
b.x = a # 循環参照
a
は b
への参照を持ち、b
は a
への参照を持っている。
先に、オブジェクトの参照カウントが 0 になったときにオブジェクトは解放されると書いた。ところが循環参照があると、参照カウントの管理はとたんに難しい問題を抱える。なぜなら、上の例でいえば、b
の参照カウントは変数 a.x
がなくならない限り(つまり a
が解放されない限り) 0 にならないが、その a
といえば b.x
の参照カウントが 0 にならない限り解放されないからである。
さて、実際には、Python は頭のいい工夫によって、参照カウントを減らすことはできないものの「どこからも参照されなくなった」オブジェクトを検出して、それを解放している(“Garbage Collection for Python” / 日本語訳「PythonのGCについて」)。このため、循環参照によるデッドロックはふつうは発生しない。オブジェクトがいくつも組み合わさった複雑なクラスを使ったり、メソッドを変数として渡す処理などをしていると(メソッドは元のインスタンスへの参照を保持している)、プログラマが気づいてないうちに循環参照が発生していることもあるが、やはりまた気づかない間にそれらは問題なく解放されており、メモリリークは発生しない。
次のような(あまり意味のない)コードを実行してみると、プログラムは無限に走り続けるが、タスクマネージャで確認してみればわかるとおり、使用メモリ量はある値以上にはならない。ガベージコレクタが、使われなくなった循環参照しているオブジェクトをちゃんと解放しているからである。
class C(object):
pass
def main():
while 1:
a = C()
b = C()
a.x = b
b.x = a
if __name__ == '__main__':
main()
しかし、この循環参照を解決するガベージコレクションの仕組みを適用できない場合が存在する。それは、オブジェクトに __del__
メソッドが定義されている場合である。__del__
メソッドを持つふたつのオブジェクト a
, b
が互いへの参照を保持しあっている(循環参照している)場合を考えてみよう。ガベージコレクタは、どちらのオブジェクトの __del__
を最初に呼ぶべきだろうか。a.__del__()
の中で、b の値を利用しているかもしれない。すると、b
を最初に解放するべきか。しかし同様に b.__del__()
の中で、a の値を利用しているかもしれない。各オブジェクトの __del__
メソッドを呼ぶ順番は、機械的には決められないのだ。このため、Python のガベージコレクタは、__del__
メソッドを持つオブジェクトが循環参照している場合には、そのオブジェクトを自動では解放しない。これらのオブジェクトは、プログラム上で循環参照関係を明示的に解決しない限り、(上の例でいえば、a.x = None; b.x = None
などとしない限り、)メモリ上に存在し続ける。
先ほど自動的に解放される循環参照を確認する例として挙げたコードを少しだけ書き換えてみる。
class C(object):
def __del__(self):
pass
def main():
while 1:
a = C()
b = C()
a.x = b
b.x = a
if __name__ == '__main__':
main()
クラス C
に __del__
メソッドを付けただけのものだが、これを実行すると、恐ろしいことが起こる。
みるみるうちにメモリを食い尽くし、そのうち MemoryError
例外が発生して、プログラムは異常終了する。__del__
メソッドを定義しているオブジェクトでは、循環参照を残して放置してはいけないのである。
さて、(これは筆者が考えたわけではなく Imri Goldberg 氏のブログから拝借してきたものだが、)これまでのことから、「__del__
+ 循環参照 = リーク」という公式が導かれる。問題となる事態を避けるために、どのような対策が考えられるだろうか。
クラスで __del__
を定義しない。基本的にはこれがいちばん安全である。__del__
がないのであれば、プログラマは基本的にメモリリークの心配をする必要はない。ドキュメントに「後処理は自動で行われる」と書いてあるオブジェクト(たとえば file
オブジェクト)に対して、なんとなく気持ち悪いからという理由だけで安易に __del__
メソッドを付けて明示的な後処理を書いたりするのはかえって危険な可能性がある。また、解放が必要なリソースをメンバ変数として確保する場合は、そのクラスに open
/ close
などのメソッドを定義して、クラスを利用する側で明示的にそのメソッドを呼ばせるようにすることを考える。
__del__
を定義していなければ、循環参照があってもオブジェクトは「いずれ」解放される。しかし、その「いずれ」がいつかは、はっきりと知るすべはない。ブログ “Geek at Play” によれば、サーバ用のコードなどで負荷の高い処理が続くような場合、解放のタイミングがなかなか訪れずリークしたのと同様のメモリの逼迫した状況が発生することもあるという。(“Finding my first Python reference cycle”)
意識せずに循環参照を作ってしまう例として多い(と思われる)のが、“Geek at Play” でも紹介されていた、コールバック用のハンドラ関数としてメソッドを他の変数に代入するというケースだ。メソッドは関数のように扱えるが、考えてみれば当然のことながら、それ自身にオブジェクトのインスタンスへの参照を保持している。このため、ハンドラを登録したオブジェクトが自身のメンバ変数だったりすると、循環参照ができてしまう。
urllib2
にメモリリークがあるという話があるそうだが、それもメソッドの代入による循環参照が原因のようだ。(注意:ここでいう「リーク」は厳密な意味ではリークではない、循環参照のオブジェクトはいずれ解放されるので。しかし、先に述べたようにガベージコレクタが働くタイミングは決定できない。このため、ループなどで循環参照しているオブジェクトを大量に作ってしまうとガベージコレクタが動く前にメモリを使いつぶしてしまうのである。)
もちろん循環参照を避けることができるのであれば、それは望ましい。しかし、循環参照を確実に回避するこれといった方法はあるのだろうか。簡単なチェック機構を書けば、相互参照レベルの循環参照は見つけられるかもしれない。しかし、実際のコードでは非常に長いステップの循環参照が発生しうるし、それに気づくのは場合によってはとても難しい。ソフトウェア会社 LShift のブログによると、 かれらがツールを使ったデバッグの末にようやく見つけた循環参照は 9 ステップあったという(記事の図を参照のこと)。こういうものを見ると、循環参照の発生を予測するのは不可能ではないかという気さえしてくる。
個人的には、起こりうる循環参照を避けることを意識しながらコードを書く、あるいはそれを検出することを試みるのは、無理のある話のような気がするのだ。
Python には、オブジェクトへの弱参照を作る、weakref
モジュールが用意されている。弱参照というのは、オブジェクトそのものを保持するのではなく、後からそのオブジェクトへの参照を取り出せるような一時的なチケットを持つような仕組みだ。これにより、オブジェクトの参照カウントを増やすことなく、必要に応じてそのオブジェクトへアクセスする手段を持つことができる(ただし、参照カウントを増やさないということは、必要なときにそのオブジェクトがすでになくなっている場合もあるということで、プログラムではそのことも考慮しなければならない)。
相互参照をしている場面でも、片方が相手への弱参照を持つようにすれば循環参照にはならない。ツリー構造で親子が相互に相手の参照を保持しあっているようなケースなど、場合によってはこのアプローチが有効な場面もあるかもしれない。
とはいえ、C ライブラリのラッパなどを作成していると、__del__
メソッドを使いたい場面は多い。そんなことを考えていたら、最近になって weakref
モジュールを利用したハックで、__del__
を定義することなしにオブジェクトの解放時に任意の関数を実行させるレシピが公開されているのを見つけた(“Calling (C-level) finalizers without __del__”)。
これは、weakref.ref
の(あまり使われない)第 2 引数のコールバックを利用したものだ。詳しくはリンク先のページを見ていただくとして、メンバ変数として確保したリソースを解放するためのコードを、親オブジェクトが解放されるタイミングで実行するというものである。
ただしこの処理も万能ではない。たとえば、この方法で解放処理を予約されたメンバ変数が、x = a.to_be_finalized
のようにそれだけで取り出されて代入されたらどうなるだろう。予約された a.to_be_finalized
の解放処理は a
が解放されるタイミングで走るので、代入してとっておいた x
を利用するときには、x
はすでに無効なオブジェクトとなっている可能性がある。それでも、こうした代入を避けるなどの工夫をして気を付けていれば、有効に思う。(疑問:弱参照の callback はインタプリタ終了までに呼び出されることは保証されてるのかしら?)
考えれば考えるほど、__del__
の使いどころは難しい。呼ばれる保証がないところから、絶対にやらなければいけない終了処理を記述することはできない。すると、「どうせインタプリタが終了してしまえば同じことだが、いまやっておくとまあちょっといい処理」を書けばいいのかということになる。たとえばファイルをクローズするなどの処理(ファイルをオープンしたハンドルは、プログラムが終了するとすべて自動的にクローズされる)。
ところが、そういう「やっておくといいかもしれない」程度の処理を書くには、__del__
は代償(循環参照によるメモリリーク)が大きすぎるように思う。しかし、かといってファイルを閉じる程度の終了処理に weakref
ファイナライゼーションのハックを導入するのも、なんだか仰々しい気がする。クラス設計が十分に自己完結していて、利用形態も単純なものに限られる(すなわち循環参照の発生が想定しにくい)のであれば、__del__
は容認できるかもしれない。ここで想定しているのは、C ライブラリや API のラッパ・モジュールなどである。対して、メンバ変数にたくさんのオブジェクトを保持し、複雑な参照関係を持つクラスを設計しているときに、__del__
を定義しようとするのはものすごく危険な兆候ということになる。
結局、「いつでも常にこれをやればオーケー」というやり方は存在しない、というどうしようもない結論になるのかもしれない。いずれにせよ必要なのは __del__
とガベージコレクタの仕様と挙動を理解しておくことで、そうなっておけば、将来予期しないハングアップやメモリリークを解決することができる(かもしれない)。
Python 開発コミュニティと、さまざまな解説記事やサンプルコードを公開されている方々に感謝します。
内容については正確さを心がけていますが、知識不足により間違ったことを書いてしまっているかもしれません。記事の内容によって生じた損害等について作者はその責任を負いません。ご了承ください。記事に関するご指摘やご意見ご感想はいつでも歓迎です。ゲストブックやメールなどでご連絡ください。
この文書は、クリエイティブ・コモンズ・ライセンスの下でライセンスされています。また記事に掲載しているサンプルコードはすべてパブリックドメインに置きます。
Copyright 2010 Masaaki Shibata <mshibata at emptypage.jp>