2016-05-14 31 views
16

Chcę śledzić obiekty pewnego rodzaju, które są obecnie w użyciu. Na przykład: Śledź wszystkie wystąpienia klasy lub wszystkich klas, które zostały utworzone przez metaclass.W języku Python: Jak usunąć obiekt z listy, jeśli jest on wymieniony tylko na tej liście?

Łatwo jest śledzić przypadkach tak:

class A(): 
    instances = [] 
    def __init__(self): 
     self.instances.append(self) 

Ale jeśli instancja nie odwołuje się gdziekolwiek poza tej listy nie będą już potrzebne i nie chcę, aby przetworzyć tego wystąpienia w potencjalnie czasochłonna pętla.

Próbowałem usunąć obiekty, które są tylko przywoływane na liście przy użyciu sys.getrefcount.

for i in A.instances: 
    if sys.getrefcount(i) <=3: # in the list, in the loop and in getrefcount 
     # collect and remove after the loop 

Problem polega na tym, że liczba referencyjna jest bardzo mało znana. Otwarcie nowej powłoki i tworzenie obojętne klasę bez powrotów treści 5 na

sys.getrefcount(DummyClass) 

Innym pomysłem jest skopiowanie obiektów następnie usuwanie listy i sprawdziany, które obiekty zostały zaplanowane na śmieci zbieranie i w ostatnim etapie usuwania te obiekty. Coś jak:

Copy = copy(A.instances) 
del A.instances 
A.instances = [i for i in Copy if not copy_of_i_is_in_GC(i)] 

Przedmioty nie muszą być natychmiast usunięte, gdy liczba odniesienia przechodzi do 0. Po prostu nie chcesz tracić zbyt wiele ressources na obiekty, które nie są już używane.

+1

Użyj [słabego odnośnika] (https://docs.python.org/3.5/library/weakref.html) – Barmar

+0

http://stackoverflow.com/questions/12101958/keep-track-of-instances-in -python –

+0

http://effbot.org/pyfaq/how-do-i-get-i-list-all-instances-of-a-given-class.htm –

Odpowiedz

7

Ta odpowiedź jest taka sama jak Kevina, ale pracowałem nad przykładową implementacją ze słabymi referencjami i zamieszczam ją tutaj. Używanie słabych odniesień rozwiązuje problem, w którym obiekt jest przywoływany przez listę self.instance, więc nigdy nie zostanie usunięty.

Jedną z rzeczy dotyczących tworzenia słabego odniesienia dla obiektu jest to, że można dołączyć wywołanie zwrotne, gdy obiekt zostanie usunięty. Występują problemy, takie jak oddzwanianie nie dzieje się, gdy program wychodzi ... ale może być to, co chcesz.

import threading 
import weakref 

class A(object): 
    instances = [] 
    lock = threading.RLock() 

    @classmethod 
    def _cleanup_ref(cls, ref): 
     print('cleanup') # debug 
     with cls.lock: 
      try: 
       cls.instances.remove(ref) 
      except ValueError: 
       pass 

    def __init__(self): 
     with self.lock: 
      self.instances.append(weakref.ref(self, self._cleanup_ref)) 

# test 
test = [A() for _ in range(3)] 
for i in range(3,-1,-1): 
    assert len(A.instances) == i 
    if test: 
     test.pop() 

print("see if 3 are removed at exit") 
test = [A() for _ in range(3)] 
+0

W powłoce Pythona (3.5.1) asercja w pętli testowej nie działa, ponieważ wywołanie zwrotne czyszczenia nie zostało wywołane, gdy pętla osiągnie asercję po raz drugi. Drukowanie czegokolwiek na standardowe wyjście przed instrukcją assert lub po test.pop() naprawia ją. Zatem umieszczenie "False" przed "assert" naprawia go, natomiast umieszczenie "None" go nie naprawia. – uzumaki

+0

@uzumaki interesujące. Testowałem w Pythonie 3.4. Zastanawia mnie, dlaczego wywołanie zwrotne nie miało miejsca w wersji 3.5. – tdelaney

6

Standardowym sposobem rozwiązania tego problemu jest przez weak references. Podstawową ideą jest to, że zachowujesz listę słabych odniesień do obiektów zamiast samych obiektów i okresowo przycinasz martwe słabe referencje z listy.

W przypadku słowników i zestawów istnieje kilka bardziej abstrakcyjnych typów, takich jak weakref.WeakKeyDictionary(), które mogą być używane, gdy chcesz umieścić słabe referencje w bardziej złożonych miejscach, takich jak klucze słownika. Te typy nie wymagają ręcznego przycinania.

1

Dzięki @Barmar dla wskazując używać weakref. Możemy połączyć go z metodą __del__, aby zaimplementować samodzielnie zarządzającą listę instancji klasy.Zatem class A w poście OP może być przedłużony:

from weakref import ref 
class A(): 
    instances = [] 
    def __init__(self): 
     self.instances.append(ref(self)) 

    @staticmethod 
    def __del__(): 
     if A: 
     A.instances = [i for i in A.instances if not i() is None] 

Testing

#python2.7 
print dict((len(A.instances), A()) for i in range(5)).keys() # 0,1,2,3,4 
print len(A.instances) # 0 

Destruktor __del__ mogą być zadeklarowane jako statyczne metody lub metody obiektu związana jak def __del__(self):, choć nie jest to udokumentowane. Ten ostatni może zatrzymać obiekt przed zniszczeniem, tworząc kolejne odniesienie do niego. Tutaj używam statycznego, ponieważ nie ma potrzeby innego odniesienia do obiektu umierającego. Powyższy kod jest testowany zarówno w Pythonie 2.7, jak i 3.3.

Wywołanie zwrotne weakref.ref zachowuje się podobnie do __del__, z tym że jest powiązane z obiektem "weakref". Jeśli więc utworzysz wiele słabych punktów dla tego samego obiektu z tą samą funkcją wywołania zwrotnego, będzie ono nazywane dokładnym tym samym czasem co liczba słabych odwołań.

+0

Należy zauważyć, że ['__del__'] (https://docs.python.org/3.5/reference/datamodel.html#object.__del__) nie powinien być metodą statyczną. Również 'not i() jest None' jest lepiej napisane, ponieważ' i() nie jest None'. Ponadto, ponieważ tdelaney pokazuje 'weakref.ref' już zapewnia sposób dodania wywołania zwrotnego wywoływanego, gdy obiekt zostanie zniszczony. – Bakuriu

+0

@Bakuriu: dzięki za komentarz, zobacz zaktualizowaną odpowiedź do dyskusji na temat '__del__' i' weakref.ref' – gdlmx