きくらげ観察日記

好きなことを、適当に。

Pythonにおける弱参照

Pythonにも弱参照は存在するらしく、weakrefというモジュールで提供されています。

>>> import weakref
>>> class A(object): pass
... 
>>> a = A()   # ... (*)
>>> ptr = weakref.ref(a)
>>> ptr()
<__main__.A object at 0x7fd47bb929b0>
>>> del a

... (しばらく色々な操作をして、GCが呼ばれるのを待つ) ...

>>> ptr() is None
True

このように、(*)で生成されたAのインスタンスに対する(weakでない)参照の数が0となった時点で、このインスタンスGCにより削除され、ptrも何の値も指さなくなります。

そう言われてみるとPythonGCは参照カウント方式なので、次のように循環参照を持つオブジェクトを生成した場合、弱参照にしないとメモリリークが発生するのでしょうか?

class Loop(object):
    def __init__(self, ptr=None):
        self.ptr = ptr

    def __del__(self):
        print('%s: deleted.' % self)

class Leak(object):
    def __init__(self):
        a = Loop()
        b = Loop()
        a.ptr = b
        b.ptr = a
        self.a = a
        self.b = b

while True:
    hoge = Leak()
    print('yeah')

しかし、これを実行してみてもメモリリークは起こりません。実際に出力を確認してみると、(環境にもよるのかもしれませんが)ループ数百回ごとにGCが呼ばれ、ループ内で生成されたLeakのインスタンスが全消去されているのがわかります。

実はPythonGCアルゴリズムは少し賢いらしく、循環参照を検出することによって、循環の外からの参照のないオブジェクトを削除できるようになっているらしいです。

となると、よけいにweakptrの使い所がわかりません。何か他にメモリリークを起こしうるパターンがあるのでしょうか……。