5

を使用しないようにデコレータを変更しようとするとweakref、次の動作に遭遇しました。

import weakref

class descriptor(object):
    def __get__(self, instance, owner):
        return proxy(instance)

class proxy(object):
    def __init__(self, instance):
        self.instance = instance

    def __iadd__(self, other):
        return self

class A(object):
    descr = descriptor()

def is_leaky(test_fn):
    a = A()
    wr = weakref.ref(a)
    test_fn(a)
    del a
    return wr() is not None

def test1(a):
    tmp = a.descr
    tmp += object()

def test2(a):
    a.descr += object()

print(is_leaky(test1))  # gives False
print(is_leaky(test2))  # gives True!!!

どちらの場合も同じように動作すると予想されるため、これは私には非常に奇妙に思えます。さらに、参照カウントとオブジェクトの有効期間に関する私の理解から、どちらの場合もオブジェクトを解放する必要があると確信しました。

python2.7 と python3.3 の両方でテストしました。

これはバグですか、それとも意図的な動作ですか? 両方の呼び出しで期待どおりの結果が得られるようにする方法はありますか (問題のオブジェクトを解放します)。

weakrefinを使用したくないのはproxy、バインドされたメソッドの正しいオブジェクト ライフタイム セマンティクスが破壊されるためです。

a = A()
descr = a.descr
del a   # a is kept alive since descr is a bound method to a
descr() # should execute a.descr() as expected
4

1 に答える 1

5

2 つのコードパスは同等ではありません。

インプレース追加は、割り当てターゲットと追加されたアイテムの2 つの演算子に作用します。つまりtest1tempローカル変数であり、インプレース追加は次のように変換されます。

temp = temp.__iadd__(object())

そして、あなたが戻って同じオブジェクトを参照するので、これになりself、その参照は関数の終了後にクリーンアップされます。temptemp = temp

ではtest2、記述子が再び関与するようになったため、問題が複雑になりました。

a.descr += object() 

になります:

a.descr = A.__dict__['descr'].__get__(a, A).__iadd__(object())

A.__dict__['descr'].__get__(a, A)したがって、インスタンス属性に結果を割り当てますa.descr。記述子には__set__()メソッドがなく、参照されません。

しかし、ここにキャッチがあります。proxyオブジェクトはaそれ自体への参照を保持し、へa.descr.instanceの参照aです! 循環参照を作成しました

この参照は、オブジェクトを弱参照を介して表示するのに十分長く存続させますが、ガベージ コレクション プロセスが実行されてこのサイクルが中断されるとすぐに、aいずれにせよなくなります。

この話のモラル?__iadd__非データ記述子と組み合わせて使用​​しないでください。結果が代入されたときに何が起こるかを制御する必要があるため、 との両方__get__ を含めます。 __set__

于 2013-08-17T10:12:59.047 に答える