Quiero hacer un seguimiento de los objetos de cierto tipo que están actualmente en uso. Por ejemplo: realice un seguimiento de todas las instancias de una clase o de todas las clases creadas por una metaclase.

Es fácil hacer un seguimiento de instancias como esta:

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

Pero si no se hace referencia a una instancia en ningún lugar fuera de esa lista, ya no será necesaria y no quiero procesar esa instancia en un bucle que puede llevar mucho tiempo.

Intenté eliminar objetos a los que solo se hace referencia en la lista usando 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

El problema que tengo es que el recuento de referencias es muy oscuro. Abrir un nuevo shell y crear una clase ficticia sin contenido devuelve 5 para

sys.getrefcount(DummyClass)

Otra idea es copiar los objetos, luego eliminar la lista y verificar qué objetos se han programado para la recolección de basura y, en el último paso, eliminar esos objetos. Algo como:

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

Los objetos no tienen que eliminarse inmediatamente cuando el recuento de referencias va a 0. Simplemente no quiero desperdiciar demasiados recursos en objetos que ya no se usan.

17
uzumaki 15 may. 2016 a las 02:21

4 respuestas

La mejor respuesta

Esta respuesta es la misma que la de Kevin, pero estaba trabajando en una implementación de ejemplo con referencias débiles y la estoy publicando aquí. El uso de referencias débiles resuelve el problema en el que la lista self.instance hace referencia a un objeto, por lo que nunca se eliminará.

Una de las cosas sobre la creación de una referencia débil para un objeto es que puede incluir una devolución de llamada cuando se elimina el objeto. Hay problemas como que la devolución de llamada no ocurre cuando el programa sale ... pero eso puede ser lo que quieras de todos modos.

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)]
7
tdelaney 15 may. 2016 a las 00:38

Gracias a @Barmar por señalarnos que usamos weakref . Podemos combinarlo con el método __del__ para implementar una lista de instancias autogestionadas de una clase. Por lo tanto, el class A en la publicación de OP se puede extender como:

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]

Pruebas

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

El destructor __del__ puede declararse como un método estático o un método vinculado a objetos como def __del__(self):, aunque no está documentado. Este último puede evitar que el objeto sea destruido creando otra referencia a él. Aquí uso el estático porque no hay necesidad de otra referencia al objeto de morir. El código anterior se prueba en Python 2.7 y 3.3.

La devolución de llamada weakref.ref se comporta de manera similar a __del__, excepto que está vinculada al objeto "weakref". Por lo tanto, si crea múltiples puntos débiles para el mismo objeto con la misma función de devolución de llamada, se llamará exactamente al mismo tiempo que el número de puntos débiles.

1
gdlmx 15 may. 2016 a las 09:47

La forma estándar de resolver este problema es a través de referencias débiles. La idea básica es mantener una lista de referencias débiles a los objetos en lugar de los objetos mismos, y podar periódicamente las referencias débiles muertas de la lista.

Para los diccionarios y conjuntos, hay algunos tipos más abstractos, como weakref.WeakKeyDictionary(), que se pueden usar cuando se desean poner referencias débiles en lugares más complejos como las teclas de un diccionario. Estos tipos no requieren poda manual.

7
Kevin 14 may. 2016 a las 23:59

Prueba el gc.get_referrers(obj). La documentación del módulo gc

len(gc.get_referrers(my_obj))
1
WreckeR 14 may. 2016 a las 23:42