いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Pythonメモ-40 (オブジェクトが削除されたことを検知する) (weakref.finalize, ファイナライザ, 弱参照)

概要

小ネタ。以下の書籍で知りました。

Fluent Python ―Pythonicな思考とコーディング手法

Fluent Python ―Pythonicな思考とコーディング手法

  • 作者: Luciano Ramalho,豊沢聡,桑井博之,梶原玲子
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2017/10/07
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

Pythonは、ガベージコレクタを持っているので、JavaやC#などと同様に

ユーザが明示的にオブジェクトを開放する必要はありません。使われなくなったオブジェクトは勝手に削除されていきます。

が、たまーにですが、オブジェクトが削除されるタイミングを検知したいときがあったりします。

大抵どの言語でもファイナライザって形で提供されていて

pythonの場合は、以下のものを利用します。

finalizer = weakref.finalize(obj, func, *args, **kwargs)

使い方は、第一引数にファイナライザを付与したいオブジェクト。

第二引数が、呼び出されるコールバック関数、第三引数以降が、コールバック呼び出し時に渡される値です。

finalizeの呼び出しで取得できるオブジェクト(ファイナライザオブジェクト)には、aliveという属性があり

この属性を確認することで、現在そのオブジェクトが「まだ生存しているかどうか」を確認できます。

弱参照

weakref は、弱参照という意味です。弱参照というのは、ざっくりというと「自分(weakref)以外に、そのオブジェクトが参照されていない場合、そのオブジェクトは削除対象となる」というイメージ。

Pythonの場合、ガベージコレクタのメインルーチンに「参照カウンタ方式」を採用しているので、カウンタが0になったら、そのオブジェクトは削除対象となります。

通常の参照は、「強参照」といって、参照を作った時点でカウンタが1上がります。弱参照というのは、このカウンタが増えないタイプです。

弱参照は、キャッシュ構造を作ったりする時によく使われますね。

weakrefモジュールは、単体で使うよりも、弱参照状態でオブジェクトを格納することができる WeakValueDictionary などを使うことの方が多いです。

サンプル

# coding: utf-8
"""
weakref モジュールについてのサンプルです。
weakref.finalize() についてのサンプルコードが記載されています。
"""
import time
import weakref

from trypython.common.commoncls import SampleBase


class Sample(SampleBase):
    def exec(self):
        print(f'処理開始')
        time.sleep(3)
        print(f'処理終了')


def finalize_sample(*args, **kwargs):
    print(f'object [sample] is dead. \n\targs   = {tuple(args)}\n\tkwargs = {kwargs}')


def go():
    obj = Sample()

    #################################################################
    # ファイナライザを設定して実行
    #
    # finalize() の書式は (obj, func, *args, **kwargs) となっており
    # 設定時に指定した *args, **kwargs が、ファイナライザに引き渡される
    #################################################################
    weakref.finalize(obj, finalize_sample, 999, keyword1='helloworld')
    obj.exec()


if __name__ == '__main__':
    go()

    ###################################################################################
    # go() が終了した段階で Sample オブジェクトの参照数が0となるので、オブジェクトが gc される
    # (python の gc は、メインルーチンに参照カウンタ方式なので、即削除される (*1))
    # ので、設定していたファイナライザが発動する
    #
    # (*1) 循環参照を検出するために 世代間GC もやっている
    ###################################################################################
    print(f'メインルーチン終了')

実行すると以下のように出力されます。

処理開始
処理終了
object [sample] is dead. 
    args   = (999,)
    kwargs = {'keyword1': 'helloworld'}
メインルーチン終了

補足

weakref周りの動作確認を コンソール上で試す際、pythonのコンソールでは

実行した式の結果がNone出ない場合、その値が 変数「_」に保存されます。なので、うまくやらないと

変数の参照は全部削除しているはずなのに、何故か リファレント が残るって状態になります。

特に ipython の場合、ノーマルのpythonコンソールを機能拡張しているので

いろんなところで参照が残ります。(上述の変数系とか履歴情報とかに)

弱参照の動作確認をする場合は、スクリプトファイルに書いて動作させた方がわかりやすいと思います。

以下、上記の書籍に載っていたやり方です。

> python
Python 3.6.3 |Anaconda, Inc.| (default, Oct 15 2017, 03:27:45) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import weakref
>>> a_set = {0, 1}
>>> wref = weakref.ref(a_set)
>>> wref
<weakref at 0x000000000383A188; to 'set' at 0x00000000037B8908>
>>> wref()
{0, 1}
>>> a_set = {6, 7, 8}
>>> wref()
{0, 1}
>>> wref() is None
False
>>> wref() is None
True
>>> wref()
>>>

参考情報

8.8. weakref — 弱参照 — Python 3.6.3 ドキュメント

stackoverflow.com


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。