weakref --- 弱参照¶
ソースコード: Lib/weakref.py
weakref モジュールを使うと、Python プログラムからオブジェクトへの 弱参照 を作成できます。
以下では、用語リファレント(referent) は弱参照が参照するオブジェクトを意味します。
オブジェクトへの弱参照があることは、そのオブジェクトを生かしておくのには不十分です。リファレントへの参照が弱参照しか残っていない場合、 garbage collection はリファレントを自由に破棄し、メモリを別のものに再利用することができます。しかし、オブジェクトへの強参照がなくても、オブジェクトが実際に破棄されるまでは、弱参照はオブジェクトを返す場合があります。
弱参照の主な用途は、巨大なオブジェクトを保持するキャッシュやマッピングを実装することです。ここで、キャッシュやマッピングに保持されているからという理由だけで、巨大なオブジェクトが生き続けることは望ましくありません。
例えば、大きなバイナリ画像オブジェクトがいくつもあり、それぞれに名前を関連付けたい場合を考えます。
Python の辞書で名前を画像に、または画像を名前に対応付けると、辞書の値やキーに含まれるという理由だけで画像オブジェクトが生き続けます。
代わりに weakref モジュールの WeakKeyDictionary と WeakValueDictionary を使うと、
弱参照によって、マッピングに含まれるという理由だけではオブジェクトを生存させないマッピングを構築できます。
例えば画像オブジェクトが WeakValueDictionary の値である場合、
その画像への参照が弱参照マッピングの持つ弱参照だけになれば、ガベージコレクションはそのオブジェクトを回収でき、弱参照マッピング内の対応するエントリーも削除されます。
WeakKeyDictionary と WeakValueDictionary はその実装に弱参照を使用しており、キーや値がガーベジコレクションによって回収されたことを弱参照辞書に通知するコールバック関数を設定しています。 WeakSet は set インターフェースを実装していますが、 WeakKeyDictionary のように要素への弱参照を持ちます。
finalize は、オブジェクトのガベージコレクションの実行時にクリーンアップ関数が呼び出されるように登録する、単純な方法を提供します。これは、未加工の弱参照上にコールバック関数を設定するよりも簡単です。なぜなら、オブジェクトのコレクションが完了するまでファイナライザが生き続けることを、モジュールが自動的に保証するからです。
ほとんどのプログラムでは、これらの弱参照コンテナ型か finalize のいずれかを使うだけで十分でしょう。
通常は、弱参照を直接作成する必要はありません。
weakref モジュールは、高度な用途のために低水準の仕組みも公開しています。
すべてのオブジェクトを弱参照できるわけではありません。 弱参照に対応するオブジェクトには、クラスインスタンス、(C ではなく) Python で書かれた関数、インスタンスメソッド、 set、frozenset、一部の ファイルオブジェクト、ジェネレータ、型オブジェクト、 ソケット、配列、deque、正規表現パターンオブジェクト、コードオブジェクトがあります。
バージョン 3.2 で変更: thread.lock, threading.Lock, code オブジェクトのサポートが追加されました。
list や dict など、いくつかの組み込み型は弱参照を直接サポートしませんが、以下のようにサブクラス化を行えばサポートを追加できます:
class Dict(dict):
pass
obj = Dict(red=1, green=2, blue=3) # このオブジェクトは弱参照できる
拡張型は、簡単に弱参照をサポートできます。詳細については、 弱参照(Weak Reference)のサポート を参照してください。
When __slots__ are defined for a given type, weak reference support is
disabled unless a '__weakref__' string is also present in the sequence of
strings in the __slots__ declaration.
See __slots__ documentation for details.
- class weakref.ref(object[, callback])¶
object への弱参照を返します。 リファレントがまだ生きていれば、参照オブジェクトを呼び出すことで元のオブジェクトを取り出せます。 リファレントが生きていなければ、参照オブジェクトを呼び出すと
Noneが返ります。 callback にNone以外の値が指定され、返された弱参照オブジェクトがまだ生きている場合、リファレントの後始末処理の直前にコールバックが呼び出されます。 コールバックには弱参照オブジェクトが唯一の引数として渡されますが、その時点でリファレントは利用できません。同じオブジェクトに対してたくさんの弱参照を作れます。それぞれの弱参照に対して登録されたコールバックは、もっとも新しく登録されたコールバックからもっとも古いものへと呼び出されます。
コールバックで発生した例外は標準エラー出力に表示されますが、呼び出し元には伝播しません。 オブジェクトの
__del__()メソッドで発生した例外とまったく同じように扱われます。object が ハッシュ可能 ならば、弱参照はハッシュ可能です。それらは object が削除された後でもそれらのハッシュ値を保持します。 object が削除されてから初めて
hash()が呼び出された場合に、その呼び出しはTypeErrorを発生させます。弱参照は等価性のテストをサポートしていますが、順序をサポートしていません。 リファレントがまだ生きているならば、 callback に関係なく二つの参照はそれらのリファレントと同じ等価関係を持ちます。 リファレントのどちらか一方が削除された場合、参照オブジェクトが同一である場合に限り、その参照は等価です。
これはファクトリ関数ではなく、サブクラス化可能な型です。
弱参照は、参照するオブジェクトの型を引数とする ジェネリック です。
- __callback__¶
この読み出し専用の属性は、現在弱参照に関連付けられているコールバックを返します。コールバックが存在しないか、弱参照のリファレントが生きていない場合、この属性の値は
Noneになります。
バージョン 3.4 で変更:
__callback__属性が追加されました。
- weakref.proxy(object[, callback])¶
弱参照を使う object へのプロキシを返します。弱参照オブジェクトを明示的な参照外しをしながら利用する代わりに、多くのケースでプロキシを利用することができます。返されるオブジェクトは、 object が呼び出し可能かどうかによって、
ProxyTypeまたはCallableProxyTypeのどちらかの型を持ちます。プロキシオブジェクトはリファレントに関係なく ハッシュ可能 ではありません。これによって、それらの基本的な変更可能という性質による多くの問題を避けています。そして、辞書のキーとしての利用を妨げます。 callback はref()関数の同じ名前のパラメータと同じものです。(--- 訳注: リファレントが変更不能型であっても、プロキシはリファレントが消えるという状態の変更があるために、変更可能型です。---)リファレントがガベージコレクションで回収された後にプロキシオブジェクトの属性にアクセスすると、
ReferenceErrorが発生します。バージョン 3.8 で変更: プロキシオブジェクトで使える演算子に、行列乗算演算子
@と@=を追加しました。
- weakref.getweakrefcount(object)¶
object を参照する弱参照とプロキシの数を返します。
- weakref.getweakrefs(object)¶
object を参照するすべての弱参照とプロキシオブジェクトのリストを返します。
- class weakref.WeakKeyDictionary([dict])¶
キーを弱参照するマッピングクラス。キーへの強参照がなくなったときに、辞書のエントリは捨てられます。アプリケーションの他の部分が所有するオブジェクトへ属性を追加することもなく、それらのオブジェクトに追加データを関連づけるために使うことができます。これは属性へのアクセスをオーバーライドするオブジェクトに特に便利です。
既存のキーと等しい値を持つ別のオブジェクトをキーとして辞書に挿入すると、値は置き換わりますが、既存のキーは置き換わりません。 そのため、元のキーへの参照を削除すると、辞書のエントリーも削除されます。
>>> class T(str): pass ... >>> k1, k2 = T(), T() >>> d = weakref.WeakKeyDictionary() >>> d[k1] = 1 # d = {k1: 1} >>> d[k2] = 2 # d = {k1: 2} >>> del k1 # d = {}
再代入する前にキーを削除すれば回避できます。
>>> class T(str): pass ... >>> k1, k2 = T(), T() >>> d = weakref.WeakKeyDictionary() >>> d[k1] = 1 # d = {k1: 1} >>> del d[k1] >>> d[k2] = 2 # d = {k2: 2} >>> del k1 # d = {k2: 2}
バージョン 3.9 で変更: PEP 584 で規定されている
|演算子と|=演算子のサポートを追加しました。
WeakKeyDictionary オブジェクトには、内部の弱参照を直接公開する追加のメソッドがあります。
その参照先が利用時にも生きている保証はないため、弱参照を呼び出した結果を確認してから使用する必要があります。
このメソッドを使うと、キーを必要以上に長く生存させる参照を作らずに済みます。
- WeakKeyDictionary.keyrefs()¶
キーへの弱参照を持つ iterable オブジェクトを返します。
- class weakref.WeakValueDictionary([dict])¶
値を弱参照するマッピングクラス。値への強参照が存在しなくなったときに、辞書のエントリは捨てられます。
バージョン 3.9 で変更: PEP 584 で規定されている
|演算子と|=演算子のサポートを追加しました。
WeakValueDictionary オブジェクトには、WeakKeyDictionary.keyrefs() メソッドと同じ注意点を持つ追加のメソッドがあります。
- WeakValueDictionary.valuerefs()¶
値への弱参照を持つ iterable オブジェクトを返します。
- class weakref.WeakSet([elements])¶
要素への弱参照を持つ集合型。要素への強参照が無くなったときに、その要素は削除されます。
- class weakref.WeakMethod(method[, callback])¶
束縛されたメソッド (クラスで定義され、インスタンスから取得されるメソッド) への弱参照をシミュレートする、
refのカスタムサブクラスです。 束縛されたメソッドは短命なので、通常の弱参照では保持し続けられません。WeakMethodには、オブジェクトまたは元の関数のどちらか一方が破棄されるまで、束縛されたメソッドを再作成するための特別なコードがあります。>>> class C: ... def method(self): ... print("method called!") ... >>> c = C() >>> r = weakref.ref(c.method) >>> r() >>> r = weakref.WeakMethod(c.method) >>> r() <bound method C.method of <__main__.C object at 0x7fc859830220>> >>> r()() method called! >>> del c >>> gc.collect() 0 >>> r() >>>
callback は
ref()関数の同名のパラメータと同じです。Added in version 3.4.
- class weakref.finalize(obj, func, /, *args, **kwargs)¶
obj がガベージコレクションで回収されるときに呼び出される、呼び出し可能なファイナライザオブジェクトを返します。 通常の弱参照とは異なり、ファイナライザは参照しているオブジェクトが回収されるまで必ず生き残り、そのおかげでライフサイクル管理が大いに簡単になります。
ファイナライザは (直接もしくはガベージコレクションのときに) 呼び出されるまで 生きている と見なされ、呼び出された後には 死んでいます 。 生きているファイナライザを呼び出すと、
func(*arg, **kwargs)を評価した結果を返します。一方、死んでいるファイナライザを呼び出すとNoneを返します。ガベージコレクション中にファイナライザのコールバックで発生した例外は標準エラー出力に表示されますが、呼び出し元には伝播しません。 オブジェクトの
__del__()メソッドや弱参照のコールバックで発生した例外と同じように扱われます。プログラム終了時 (より一般には インタープリタのシャットダウン 時) に、 残っている生存中のファイナライザは、
atexit属性が偽に設定されていない限り、それぞれ呼び出されます。 呼び出し順は作成順の逆です。インタープリタのシャットダウン の後半では、モジュールのグローバル変数が
Noneに置き換えられている可能性があります。 ファイナライザは、この段階では決してコールバックを呼び出しません。- alive¶
ファイナライザが生きている場合には真、そうでない場合には偽のプロパティです。
- atexit¶
書き込み可能なブール値のプロパティで、デフォルトは真です。 インタープリタのシャットダウン 時には、 生存中で
atexitが真のファイナライザがすべて、作成順の逆に呼び出されます。
注釈
func、args、kwargs が、直接にも間接にも obj への参照を持たないことが重要です。 参照を持っていると、obj はガベージコレクションで回収されなくなります。 特に、func を obj の束縛されたメソッドにしてはいけません。
Added in version 3.4.
- class weakref.ReferenceType¶
弱参照オブジェクトのための型オブジェクト。
- class weakref.ProxyType¶
呼び出し可能でないオブジェクトのプロキシのための型オブジェクト。
- class weakref.CallableProxyType¶
呼び出し可能なオブジェクトのプロキシのための型オブジェクト。
- weakref.ProxyTypes¶
プロキシのためのすべての型オブジェクトを含むシーケンス。これは両方のプロキシ型の名前付けに依存しないで、オブジェクトがプロキシかどうかのテストをより簡単にできます。
参考
- PEP 205 - 弱参照
この機能の提案と理論的根拠。初期の実装と他の言語における類似の機能についての情報へのリンクを含んでいます。
弱参照オブジェクト¶
弱参照オブジェクトにはメソッドはなく、ref.__callback__ 以外の属性もありません。
弱参照オブジェクトを呼び出すと、リファレントがまだ存在していれば取得できます:
>>> import weakref
>>> class Object:
... pass
...
>>> o = Object()
>>> r = weakref.ref(o)
>>> o2 = r()
>>> o is o2
True
リファレントがもはや存在しないならば、参照オブジェクトの呼び出しは None を返します:
>>> del o, o2
>>> print(r())
None
弱参照オブジェクトがまだ生きているかどうかのテストは、式 ref() is not None を用いて行われます。通常、参照オブジェクトを使う必要があるアプリケーションコードはこのパターンに従います:
# r は弱参照オブジェクト
o = r()
if o is None:
# リファレントはガベージコレクションで回収された
print("Object has been deallocated; can't frobnicate.")
else:
print("Object is still live!")
o.do_something_useful()
"生存性(liveness)"のテストを分割すると、スレッド化されたアプリケーションにおいて競合状態を作り出します。 (訳注:if r() is not None: r().do_something() では、2度目のr()がNoneを返す可能性があります) 弱参照が呼び出される前に、他のスレッドは弱参照が無効になる原因となり得ます。上で示したイディオムは、シングルスレッドのアプリケーションと同じくマルチスレッド化されたアプリケーションにおいても安全です。
サブクラス化を行えば、 ref オブジェクトの特殊なバージョンを作成できます。これは WeakValueDictionary の実装で使われており、マップ内の各エントリによるメモリのオーバヘッドを減らしています。こうした実装は、ある参照に追加情報を関連付けたい場合に便利ですし、リファレントを取り出すための呼び出し時に何らかの追加処理を行いたい場合にも使えます。
以下の例は、ref のサブクラスを使ってオブジェクトに関する追加情報を保存し、
リファレントへのアクセス時に返す値を変更する方法を示しています。
import weakref
class ExtendedRef(weakref.ref):
def __init__(self, ob, callback=None, /, **annotations):
super().__init__(ob, callback)
self.__counter = 0
for k, v in annotations.items():
setattr(self, k, v)
def __call__(self):
"""リファレントと、この参照が呼び出された回数のペアを返す。
"""
ob = super().__call__()
if ob is not None:
self.__counter += 1
ob = (ob, self.__counter)
return ob
使用例¶
この簡単な例では、アプリケーションが以前に参照したオブジェクトを、オブジェクト ID を使って取り出す方法を示します。 オブジェクトを生存させ続けることなく、その ID を他のデータ構造で使用できます。 オブジェクトがまだ生きていれば、ID から取り出すこともできます。
import weakref
_id2obj_dict = weakref.WeakValueDictionary()
def remember(obj):
oid = id(obj)
_id2obj_dict[oid] = obj
return oid
def id2obj(oid):
return _id2obj_dict[oid]
ファイナライザオブジェクト¶
finalize を使う主な利点は、返されたファイナライザオブジェクトを保持せずに、簡単にコールバックを登録できることです。
例えば次のようになります。
>>> import weakref
>>> class Object:
... pass
...
>>> kenny = Object()
>>> weakref.finalize(kenny, print, "You killed Kenny!")
<finalize object at ...; for 'Object' at ...>
>>> del kenny
You killed Kenny!
ファイナライザは直接呼び出すこともできます。ただし、ファイナライザはコールバックを最大でも一度しか呼び出しません。
>>> def callback(x, y, z):
... print("CALLBACK")
... return x + y + z
...
>>> obj = Object()
>>> f = weakref.finalize(obj, callback, 1, 2, z=3)
>>> assert f.alive
>>> assert f() == 6
CALLBACK
>>> assert not f.alive
>>> f() # callback not called because finalizer dead
>>> del obj # callback not called because finalizer dead
detach() メソッドでファイナライザの登録を解除できます。
ファイナライザは死んだ状態になり、作成時にコンストラクタへ渡した引数が返されます。
>>> obj = Object()
>>> f = weakref.finalize(obj, callback, 1, 2, z=3)
>>> f.detach()
(<...Object object ...>, <function callback ...>, (1, 2), {'z': 3})
>>> newobj, func, args, kwargs = _
>>> assert not f.alive
>>> assert newobj is obj
>>> assert func(*args, **kwargs) == 6
CALLBACK
atexit 属性を False に設定しない限り、ファイナライザがまだ生きていればプログラム終了時に呼び出されます。
例えば次のようになります。
>>> obj = Object()
>>> weakref.finalize(obj, print, "obj dead or exiting")
<finalize object at ...; for 'Object' at ...>
>>> exit()
obj dead or exiting
ファイナライザと __del__() メソッドの比較¶
インスタンスが一時ディレクトリを表す、クラスを作成するとします。そのディレクトリは、次のイベントのいずれかが起きた時に、そのディレクトリの内容とともに削除されるべきです。
オブジェクトのガベージコレクションが行われた場合
オブジェクトの
remove()メソッドが呼び出される、またはプログラムが終了した場合
__del__() メソッドを使って、以下のようにクラスを実装しようとするかもしれません。
class TempDir:
def __init__(self):
self.name = tempfile.mkdtemp()
def remove(self):
if self.name is not None:
shutil.rmtree(self.name)
self.name = None
@property
def removed(self):
return self.name is None
def __del__(self):
self.remove()
Starting with Python 3.4, __del__() methods no longer prevent
reference cycles from being garbage collected, and module globals are
no longer forced to None during interpreter shutdown.
So this code should work without any issues on CPython.
ただし、__del__() メソッドの扱いは、インタープリタのガベージコレクタの内部実装に依存するため、実装ごとの差が大きいことが知られています。
より堅牢な方法として、オブジェクトの状態全体にアクセスする代わりに、 必要な特定の関数やオブジェクトだけを参照するファイナライザを定義できます。
class TempDir:
def __init__(self):
self.name = tempfile.mkdtemp()
self._finalizer = weakref.finalize(self, shutil.rmtree, self.name)
def remove(self):
self._finalizer()
@property
def removed(self):
return not self._finalizer.alive
このように定義すると、ファイナライザが受け取る参照は、ディレクトリを適切に削除するために必要なものだけになります。 オブジェクトがガベージコレクションで回収されない場合でも、終了時にファイナライザが呼び出されます。
弱参照を使ったファイナライザのもう 1 つの利点は、第三者が定義を管理するクラスにもファイナライザを登録できることです。 例えば、モジュールがアンロードされたときにコードを実行できます。
import weakref, sys
def unloading_module():
# implicit reference to the module globals from the function body
weakref.finalize(sys.modules[__name__], unloading_module)
注釈
プログラムが終了する直前にデーモンスレッド内でファイナライザオブジェクトを作成すると、終了時に呼び出されない可能性があります。
ただし、デーモンスレッド内では atexit.register()、try: ... finally: ...、with: ... もクリーンアップの実行を保証しません。