次のコード スニペットを検討してください。
import gc
from weakref import ref
def leak_class(create_ref):
class Foo(object):
# make cycle non-garbage collectable
def __del__(self):
pass
if create_ref:
# create a strong reference cycle
Foo.bar = Foo()
return ref(Foo)
# without reference cycle
r = leak_class(False)
gc.collect()
print r() # prints None
# with reference cycle
r = leak_class(True)
gc.collect()
print r() # prints <class '__main__.Foo'>
__del__
参照されるインスタンスにはメソッドがあるため、収集できない参照サイクルが作成されます。ここでサイクルが作成されます。
# create a strong reference cycle
Foo.bar = Foo()
これは単なる概念実証であり、外部コード、記述子などによって参照を追加できます。それが明確でない場合は、各オブジェクトがそのクラスへの参照を保持していることを思い出してください。
+-------------+ +--------------------+
| | Foo.bar | |
| Foo (class) +------------>| foo (Foo instance) |
| | | |
+-------------+ +----------+---------+
^ |
| foo.__class__ |
+--------------------------------+
Foo.bar
からのみアクセスされることを保証できればFoo
、理論的にはインスタンスはそのクラスへの弱い参照しか保持できないため、サイクルは必要ありません。
漏れなくこれを機能させる実用的な方法を考えられますか?
外部コードがクラスを変更するのに、そのライフサイクルを制御できない理由を尋ねる人もいるので、私が取り組んでいた実際の例と同様に、次の例を検討してください。
class Descriptor(object):
def __get__(self, obj, kls=None):
if obj is None:
try:
obj = kls._my_instance
except AttributeError:
obj = kls()
kls._my_instance = obj
return obj.something()
# usage example #
class Example(object):
foo = Descriptor()
def something(self):
return 100
print Example.foo
このコードのみDescriptor
(非データ記述子) は、私が実装している API の一部です。Example
クラスは、記述子の使用方法の例です。
記述子がクラス自体の内部にインスタンスへの参照を格納するのはなぜですか? 基本的にはキャッシング用です。Descriptor
実装者とのこのコントラクトが必要です。
- クラスには、「匿名インスタンス」を提供する引数のないコンストラクターがあります(私の定義)
- このクラスには、動作固有のメソッドがいくつかあります (
something
here )。 - クラスのインスタンスは、未定義の時間存続できます。
以下については何も想定していません。
- オブジェクトの構築にかかる時間
- クラスがdelまたはその他の魔法のメソッドを実装するかどうか
- クラスの存続期間
さらに、API は、クラスの実装者に余分な負荷がかからないように設計されています。オブジェクトをキャッシュする責任を実装者に移すこともできましたが、標準的な動作が必要でした。
実際には、この問題には簡単な解決策があります。インスタンスをキャッシュするデフォルトの動作を作成し (このコードのように)、実装者が実装する必要がある場合はそれをオーバーライドできるようにします__del__
。
もちろん、呼び出し間でクラスの状態を保持する必要があると仮定すると、これはそれほど単純ではありません。
出発点として、私は「弱いオブジェクト」をコーディングしていました。これobject
は、そのクラスへの弱い参照のみを保持する実装です。
from weakref import proxy
def make_proxy(strong_kls):
kls = proxy(strong_kls)
class WeakObject(object):
def __getattribute__(self, name):
try:
attr = kls.__dict__[name]
except KeyError:
raise AttributeError(name)
try:
return attr.__get__(self, kls)
except AttributeError:
return attr
def __setattr__(self, name, value):
# TODO: implement...
pass
return WeakObject
Foo.bar = make_proxy(Foo)()
限られた数のユースケースで機能するように見えますが、object
メソッドのセット全体を再実装する必要があり、オーバーライドするクラスを処理する方法がわかりません__new__
。