2016-05-14 19 views
16

Voglio tenere traccia degli oggetti di un certo tipo attualmente in uso. Ad esempio: Tieni traccia di tutte le istanze di una classe o di tutte le classi che sono state create da un metaclasse.In Python: come rimuovere un oggetto da un elenco se viene fatto riferimento solo a quell'elenco?

E 'facile tenere traccia di casi come questo:

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

Ma se un istanza non viene fatto riferimento da nessuna parte al di fuori di tale elenco non sarà più necessario e non voglio per elaborare tale istanza in un ciclo potenzialmente dispendioso in termini di tempo.

Ho provato a rimuovere gli oggetti a cui viene fatto riferimento nell'elenco utilizzando 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 

Il problema che ho è che il conteggio dei riferimenti è molto oscuro. apertura di una nuova shell e la creazione di una classe fittizia senza contenuti restituisce 5 per

sys.getrefcount(DummyClass) 

Un'altra idea è quella di copiare gli oggetti quindi l'eliminazione della lista e di controllo che gli oggetti sono stati programmati per la raccolta dei rifiuti e, in ultima fase di rimozione quegli oggetti. Qualcosa di simile:

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

Gli oggetti non devono essere rimosse immediatamente quando il conteggio dei riferimenti va a 0. Solo che non voglio sprecare troppo le risorse su oggetti che non vengono più utilizzati.

+1

Utilizzare un [riferimento debole] (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-a-list-of-all-instances-of-a-given-class.htm –

risposta

7

Questa risposta è la stessa di Kevin ma stavo elaborando un'implementazione di esempio con riferimenti deboli e la sto postando qui. L'uso di riferimenti deboli risolve il problema a cui un oggetto fa riferimento nell'elenco self.instance, quindi non verrà mai eliminato.

Una delle cose sulla creazione di un riferimento debole per un oggetto è che è possibile includere un callback quando l'oggetto viene cancellato. Ci sono problemi come la richiamata non accade quando il programma esce ... ma potrebbe essere quello che vuoi comunque.

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

Nella shell Python (3.5.1) l'asserzione nel ciclo di test fallisce perché il callback cleanup non è stato chiamato quando il loop raggiunge l'asserzione la seconda volta. Stampa qualsiasi cosa su stdout prima della dichiarazione di asserzione o dopo test.pop() lo corregge. Quindi, posizionare "False" prima di "asserire" lo corregge, mentre piazzando "Nessuno" non lo aggiusta. – uzumaki

+0

@uzumaki interessante. Ho provato con Python 3.4. Sono perplesso sul motivo per cui la richiamata non è avvenuta nel 3.5. – tdelaney

6

Il metodo standard per risolvere questo problema è attraverso weak references. L'idea di base è di tenere una lista di riferimenti deboli agli oggetti invece degli oggetti stessi e di potare periodicamente i riferimenti deboli morti dall'elenco.

Per dizionari e insiemi, ci sono alcuni tipi più astratti come weakref.WeakKeyDictionary() che possono essere usati quando si vogliono mettere riferimenti deboli in luoghi più complessi come le chiavi di un dizionario. Questi tipi non richiedono la potatura manuale.

1

Grazie a @Barmar per aver ricordato di usare weakref. Possiamo combinarlo con il metodo __del__ per implementare un elenco di istanze autogestione di una classe.Così, il class A nel post di PO può essere esteso come:

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 

Il distruttore __del__ può essere dichiarato come un metodo statico o un metodo oggetto legato come def __del__(self):, anche se non è documentata. Quest'ultimo può impedire che l'oggetto venga distrutto creando un altro riferimento ad esso. Qui io uso quello statico perché non c'è bisogno di un altro riferimento all'oggetto che muore. Il codice sopra è testato sia in Python 2.7 che in 3.3.

Il callback weakref.ref si comporta come __del__ eccetto per il fatto che è associato all'oggetto "weakref". Quindi, se crei più weakrefs per lo stesso oggetto con la stessa funzione di callback, sarà chiamato esattamente allo stesso tempo del numero di weakrefs.

+0

Nota che ['__del__'] (https://docs.python.org/3.5/reference/datamodel.html#object.__del__) non dovrebbe essere un metodo statico. Anche 'not i() è None' è scritto meglio come' i() non è None'. Inoltre, come tdelaney mostra 'weakref.ref' fornisce già i mezzi per aggiungere un callback chiamato quando l'oggetto viene distrutto. – Bakuriu

+0

@Bakuriu: grazie per il commento, vedere la risposta aggiornata per la discussione su '__del__' e' weakref.ref' – gdlmx

Problemi correlati