5

próbuje modyfikować dekorator nie użyć weakref, natknąłem się na następujące zachowanie:wyciek pamięci podczas wywoływania __iadd__ poprzez __get__ bez stosowania tymczasowego

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!!! 

to wydaje bardzo dziwne dla mnie, jak ja oczekiwać, że oba przypadki zachowują się tak samo. Co więcej, z mojego rozumienia liczenia odnośników i czasu życia obiektów byłem przekonany, że w obu przypadkach obiekt powinien zostać uwolniony.

Przetestowałem to zarówno na python2.7, jak i python3.3.

Czy to błąd, czy zamierzone zachowanie? Czy istnieje sposób na uzyskanie obu połączeń, aby uzyskać oczekiwane wyniki (zwolnić dany obiekt)?

Nie chcę używać weakref w proxy bo to niszczy prawidłowe semantykę obiektu dożywotnie metod związanych:

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 

Odpowiedz

5

Dwa codepaths nie są równoważne.

W miejscu dodajemy działa na dwóch operatorów, cel przydziału i dodany element. W test1 czyli temp zmienna lokalna, a na miejscu dodatek jest tłumaczone na następujące:

temp = temp.__iadd__(object()) 

i od powrotu self i temp odnosi się do tego samego obiektu, staje temp = temp i że odniesienia oczyścić po wyjściu funkcji.

W test2, wy skomplikowane sprawy, ponieważ teraz deskryptory angażują ponownie:

a.descr += object() 

staje:

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

więc przypisać wynik A.__dict__['descr'].__get__(a, A) na przykład atrybut a.descr; deskryptor nie ma metody __set__() i nie jest konsultowany.

Ale, i tu jest haczyk, obiekt proxy posiada odniesienie do a sobie, a.descr.instance jest nawiązaniem do a! Utworzono okrągły odnośnik.

To odniesienie utrzymuje obiekt przy życiu wystarczająco długo, aby pokazać go przez słabe odniesienie, ale tak szybko, jak proces zbierania śmieci działa i zrywa ten cykl, tak czy inaczej, a zniknie.

Morał z tej historii? Nie używaj __iadd__ w połączeniu z deskryptorem danych innych niż dane; Obejmują zarówno __get____set__, ponieważ trzeba kontrolować, co dzieje się, gdy wynik jest przypisany z powrotem.

+0

Rzeczywiście, dodanie 'gc.collect()' do 'is_leaky' powoduje przerwanie odwołania cyklicznego i powoduje, że' is_leaky (test2) 'zwraca false. – unutbu

+0

Tak, zmiana test2 na a.descr .__ iadd __ (object()) skutkuje drugim fałszem. – BartoszKP

+1

WOW. To jest subtelne. Dodanie NOP '__set__' do discriptora rozwiązuje problem. Dziękuję Ci bardzo! – coldfix