1

次のコード スニペットを検討してください。

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実装者とのこのコントラクトが必要です。

  1. クラスには、「匿名インスタンス」を提供する引数のないコンストラクターがあります(私の定義)
  2. このクラスには、動作固有のメソッドがいくつかあります ( somethinghere )。
  3. クラスのインスタンスは、未定義の時間存続できます。

以下については何も想定していません。

  1. オブジェクトの構築にかかる時間
  2. クラスがdelまたはその他の魔法のメソッドを実装するかどうか
  3. クラスの存続期間

さらに、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__

4

2 に答える 2

2

_my_instanceあなたの例では、記述子を保持するクラスではなく、記述子クラスの dict に格納しないのはなぜですか? その辞書でweakrefまたはWeakValueDictionaryを使用できます。これにより、オブジェクトが消えると、辞書はその参照を失い、記述子は次のアクセスで新しいものを作成します。

編集:インスタンスが存続している間にクラスを収集する可能性について誤解していると思います。Python のメソッドは、インスタンスではなくクラスに格納されます (特殊なトリックを除けば)。objclassのオブジェクトがあり、まだ存在している間にガベージ コレクションを行うことClassを許可した場合、オブジェクトのメソッドの呼び出しは失敗します。これは、メソッドがクラスと共に消えてしまうためです。そのため、唯一のオプションは、class->obj 参照を弱体化することです。オブジェクトにそのクラスを弱く参照させることができたとしても、その弱点が「有効になった」場合 (つまり、インスタンスがまだ存在している間にクラスが収集された場合) は、クラスを壊すだけです。Classobjobj.meth()

于 2013-03-20T20:58:02.573 に答える
1

あなたが直面している問題は、一般的なref-cycle-with-__del__問題の特殊なケースです。

あなたのケースでサイクルが作成される方法に異常は見られません。つまり、一般的な問題を回避する標準的な方法に頼る必要があります。

a を実装して使用するのweak objectは難しいと思いますが、 を定義するすべての場所で使用することを覚えておく必要があります__del__。それは最善のアプローチのようには聞こえません。

代わりに、次のことを試してください。

  1. __del__クラスで定義しないことを検討してください (推奨)
  2. を定義するクラスでは__del__、参照サイクルを避けます(一般に、コードのどこにもサイクルが作成されないようにすることは難しい/不可能かもしれません。あなたの場合、サイクルが存在することを望んでいるようです)
  3. を使用して、サイクルを明示的に中断delします (コード内にそれを行う適切なポイントがある場合)。
  4. gc.garbageリストをスキャンし、参照サイクルを明示的に破る ( を使用del)
于 2013-03-20T20:41:29.650 に答える