2014-10-02 8 views
8

Pracuję w Pythonie 2.7 i lubię ten problem, który mnie zastanawia.Dlaczego ustawić metodę ograniczoną do obiektu Pythona utworzyć odwołanie cykliczne?

To najprostszy przykład:

>>> class A(object): 
    def __del__(self): 
     print("DEL") 
    def a(self): 
     pass 

>>> a = A() 
>>> del a 
DEL 

To jest OK, jak oczekiwano ... teraz próbuję zmienić metodę obiektu aa() i co się stało jest to, że po zmianie to nie mogę usuwać a więcej:

>>> a = A() 
>>> a.a = a.a 
>>> del a 

Wystarczy zrobić kilka czeków mam przed i po cesji wydrukować odniesienie a.a

>>> a = A() 
>>> print a.a 
<bound method A.a of <__main__.A object at 0xe86110>> 
>>> a.a = a.a 
>>> print a.a 
<bound method A.a of <__main__.A object at 0xe86110>> 

końcu użyłem modułu objgraph aby spróbować zrozumieć, dlaczego obiekt nie zostanie wydany:

>>> b = A() 
>>> import objgraph 
>>> objgraph.show_backrefs([b], filename='pre-backref-graph.png') 

pre-backref-graph.png

>>> b.a = b.a 
>>> objgraph.show_backrefs([b], filename='post-backref-graph.png') 

post-backref-graph.png

Jak widać w post-backref-graph.png obrazie jest odniesienie __self__ w b, które nie mają dla mnie sensu, ponieważ self r Metodę metody instance należy zignorować (tak jak przed przypisaniem).

Ktoś może wyjaśnić, dlaczego to zachowanie i jak mogę obejść go?

Odpowiedz

3

Kiedy piszesz a.a, skutecznie działa:

A.a.__get__(a, A) 

ponieważ nie jesteś dostępu do pre-bound metodę ale klasy metodę który jest związany w czasie wykonywania.

Kiedy robisz

a.a = a.a 

skutecznie "cache" akt wiążący sposób. Ponieważ związana metoda ma odniesienie do obiektu (oczywiście, ponieważ musi przejść do funkcji self), tworzy to odwołanie cykliczne.


Więc jestem modelowania problemu jak:

class A(object): 
    def __del__(self): 
     print("DEL") 
    def a(self): 
     pass 

def log_all_calls(function): 
    def inner(*args, **kwargs): 
     print("Calling {}".format(function)) 

     try: 
      return function(*args, **kwargs) 
     finally: 
      print("Called {}".format(function)) 

    return inner 

a = A() 
a.a = log_all_calls(a.a) 

a.a() 

Można użyć słabe odwołania do związania na żądanie wewnątrz lof_all_calls jak:

import weakref 

class A(object): 
    def __del__(self): 
     print("DEL") 
    def a(self): 
     pass 

def log_all_calls_weakmethod(method): 
    cls = method.im_class 
    func = method.im_func 
    instance_ref = weakref.ref(method.im_self) 
    del method 

    def inner(*args, **kwargs): 
     instance = instance_ref() 

     if instance is None: 
      raise ValueError("Cannot call weak decorator with dead instance") 

     function = func.__get__(instance, cls) 

     print("Calling {}".format(function)) 

     try: 
      return function(*args, **kwargs) 
     finally: 
      print("Called {}".format(function)) 

    return inner 

a = A() 
a.a = log_all_calls_weakmethod(a.a) 

a.a() 

To jest naprawdę brzydka, więc wolałby go wydobyć, aby wykonać dekorator: weakmethod:

import weakref 

def weakmethod(method): 
    cls = method.im_class 
    func = method.im_func 
    instance_ref = weakref.ref(method.im_self) 
    del method 

    def inner(*args, **kwargs): 
     instance = instance_ref() 

     if instance is None: 
      raise ValueError("Cannot call weak method with dead instance") 

     return func.__get__(instance, cls)(*args, **kwargs) 

    return inner 

class A(object): 
    def __del__(self): 
     print("DEL") 
    def a(self): 
     pass 

def log_all_calls(function): 
    def inner(*args, **kwargs): 
     print("Calling {}".format(function)) 

     try: 
      return function(*args, **kwargs) 
     finally: 
      print("Called {}".format(function)) 

    return inner 

a = A() 
a.a = log_all_calls(weakmethod(a.a)) 

a.a() 

Gotowe!


FWIW to nie tylko Python 3.4 nie ma tych problemów, ma też wbudowaną wersję dla Ciebie.

+0

OK ... Czy można tego uniknąć? Powinienem buforować niektóre metody i odzyskać metodę później: czy to możliwe? –

+0

Zależy od tego, co próbujesz zrobić. – Veedrac

+0

OK Znalazłem rozwiązanie: aa = types.MethodType (Aa, a, A) –

4

Odpowiedź Veedrac na temat metody związanej z odniesieniem do instancji jest tylko częścią odpowiedzi. garbage collector CPython za umie wykryć i obsługiwać odwołań cyklicznych - z wyjątkiem, gdy jakiś obiekt, który jest częścią cyklu ma metodę __del__, jak wspomniano tutaj https://docs.python.org/2/library/gc.html#gc.garbage:

obiektów, które __del__() metod i są częścią cyklu odniesienia powoduje, że cały cykl odniesienia jest nieodgrywalny, w tym obiekty niekoniecznie w cyklu, ale dostępne tylko z niego. Python nie zbiera takich cykli automatycznie, ponieważ, ogólnie rzecz biorąc, , nie jest możliwe, aby Python odgadł bezpieczną kolejność wykonywania metod. (...) Zasadniczo lepiej jest uniknąć problemu, nie tworząc cykli zawierających obiekty metodami __del__(), a śmieci można w tym przypadku sprawdzić, aby sprawdzić, czy nie zostały utworzone takie cykle.

IOW: usuń swoją metodę __del__, a wszystko powinno być w porządku.

EDIT: wrt/komentarz:

Używam go na obiekt jako funkcja a.a = functor(a.a). Po wykonaniu testu chciałbym zastąpić funktor oryginalną metodą.

Wtedy rozwiązaniem jest jasne i proste:

a = A() 
a.a = functor(a.a) 
test(a) 
del a.a 

Dopóki jawnie wiążą go, a ma żadnego „a” instancja atribute, więc spojrzał na klasy i nowa method instancja jest zwracana (Aby uzyskać więcej informacji na ten temat, patrz: https://wiki.python.org/moin/FromFunctionToMethod). Ta instancja method jest następnie wywoływana i (zwykle) odrzucana.

+0

To nie jest rozwiązanie .... Potrzebuję metody __del__ (powody tego są poza zakresem) i ten problem jest jedynym, którego nie wiem, jak rozwiązać dla innych słabych odniesień działa dobrze –

+1

Dobrze , faktycznie odpowiada na twoje pytanie "Ktoś może wyjaśnić, dlaczego to zachowanie i jak mogę go obejść?" - nie wspomniałeś, że potrzebujesz '__del__', a twój fragment nie sugeruje, że jest to prawdziwy użytek;) Teraz jeśli spojrzysz na link, który wskazałam ciebie, to jest trochę więcej na ten temat ... –

+0

Jak piszesz w linku, który zamieściłeś w najlepszy sposób, Nie twórz cyklu i zadałem pytanie: "Nie rozumiem, dlaczego ten cykl się zrodził i czy istnieje sposób, aby nie tworzyć tego rodzaju cykli" ... ale usuń __del__ nie usuwaj cyklu, ale tylko efekty uboczne cyklu :) –

1

Co do tego, dlaczego Python to robi. Technicznie wszystkie obiekty zawierają odwołania kołowe, jeśli mają metody. Jednak odśmiecanie może potrwać znacznie dłużej, jeśli odśmiecacz musiałby przeprowadzić jawne kontrole metod obiektów, aby upewnić się, że zwolnienie obiektu nie spowoduje problemu. W związku z tym Python przechowuje metody oddzielnie od obiektu __dict__. Tak więc, pisząc a.a = a.a, śledzisz metodę samą w sobie w polu a obiektu. W związku z tym istnieje wyraźne odniesienie do metody, która zapobiega właściwemu uwolnieniu obiektu.

Rozwiązaniem twojego problemu nie jest zachowywanie "pamięci podręcznej" oryginalnej metody i po prostu usunięcie zmiennej shadowed, gdy skończysz. Spowoduje to wyłączenie metody i udostępnienie jej ponownie.

>>> class A(object): 
...  def __del__(self): 
...   print("del") 
...  def method(self): 
...   print("method") 
>>> a = A() 
>>> vars(a) 
{} 
>>> "method" in dir(a) 
True 
>>> a.method = a.method 
>>> vars(a) 
{'method': <bound method A.method of <__main__.A object at 0x0000000001F07940>>} 
>>> "method" in dir(a) 
True 
>>> a.method() 
method 
>>> del a.method 
>>> vars(a) 
{} 
>>> "method" in dir(a) 
True 
>>> a.method() 
method 
>>> del a 
del 

Tutaj vars pokazuje, co jest w atrybucie obiektu __dict__. Zwróć uwagę, że __dict__ nie zawiera odniesienia do samej siebie, nawet jeśli a.__dict__ jest prawidłowe. dir tworzy listę wszystkich atrybutów osiągalnych z danego obiektu. Tutaj widzimy wszystkie atrybuty i metody obiektu oraz wszystkie metody i atrybuty jego klas i ich podstawy.To pokazuje, że związana metoda a jest przechowywana w miejscu oddzielnym, gdzie przechowywane są atrybuty a.

+0

Dzięki za odpowiedź, ale prawdziwe pytanie nie brzmi:" dlaczego obiekt nie został zniszczony? " ale dlaczego 'a.a = a.a' tworzy odwołanie cykliczne. Jeśli usuniesz przesłonięcie "__del__" i spróbujesz wykreślić wykresy przed i po przypisaniu, zobaczysz, że wykresy są różne, a drugi ma odniesienie kołowe. –

+0

Źle zrozumiałem pytanie. Całkowicie zmieniłem swoją odpowiedź, z nową rekomendacją i nauczyłem się czegoś w tym procesie. – Dunes

+0

THX: Dokładnie to właśnie miałem na myśli: "Po co ustawić powiązaną metodę na obiekt Pythona, aby utworzyć odwołanie cykliczne?". Veedrac dał mi sposób na pracę z jakimś problemem związanym z tym, ale z twoją odpowiedzią, którą naprawdę rozumiem. –

Powiązane problemy