2012-08-24 18 views
13

Hacia el final de un programa que estoy buscando para cargar una variable específica de todas las instancias de una clase en un diccionario.¿Cómo realizar un seguimiento de las instancias de clase?

Por ejemplo:

class Foo(): 
    __init__(self): 
    x = {} 

foo1 = Foo() 
foo2 = Foo() 
foo...etc. 

Digamos que el número de casos variará y quiero el dict x de cada instancia de Foo() cargado en un nuevo dict. ¿Como podría hacerlo?

Los ejemplos que he visto en mucho suponer uno ya tiene la lista de instancias.

+1

Sospecho que no es posible sin una profunda introspección (por ejemplo, de forma recursiva la expansión de todos los objetos en los 'locales 'y diccionarios 'globales' de todos sus marcos de pila). Es mucho más fácil hacer que el método '__init__' o' __new__' de su clase cree una referencia débil y la pegue en una lista en alguna parte. – Blckknght

+2

Esta pregunta lo explica: http://stackoverflow.com/questions/328851/python-printing-all-instances-of-a-class – TJD

+0

@Blckknght: involuntariamente robé su sugerencia por mi respuesta. –

Respuesta

27

Una manera de mantener un registro de los casos es una variable de clase:

class A(object): 
    instances = [] 

    def __init__(self, foo): 
     self.foo = foo 
     A.instances.append(self) 

Al final del programa, puede crear su dict la siguiente manera:

foo_vars = {id(instance): instance.foo for instance in A.instances} 

Sólo hay una lista:

>>> a = A(1) 
>>> b = A(2) 
>>> A.instances 
[<__main__.A object at 0x1004d44d0>, <__main__.A object at 0x1004d4510>] 
>>> id(A.instances) 
4299683456 
>>> id(a.instances) 
4299683456  
>>> id(b.instances) 
4299683456  
+0

gracias! Pero, ¿no creará esto una copia separada de 'instancias' en cada instancia de A? ¿A.instancias siempre tendría un elemento en la lista? – dwstein

+2

@dwstein: No, mira editar. 'instances' es una variable de clase. Aquí hay un concepto relacionado: ["Menos asombro" en Python: el argumento predeterminado mutable] (http://stackoverflow.com/q/1132941/1142167) –

+0

Gracias por la respuesta detallada y la información. Me tomará un tiempo absorber. Además, ¿'instancias' incluirán instancias de clases derivadas? O, ¿será eso una lista separada? – dwstein

0

Usando la respuesta de @Joel Cornett He encontrado lo siguiente, que parece funcionar. es decir, puedo sumar todas las variables del objeto.

import os 

os.system("clear") 

class Foo(): 
    instances = [] 
    def __init__(self): 
     Foo.instances.append(self) 
     self.x = 5 

class Bar(): 
    def __init__(self): 
     pass 

    def testy(self): 
     self.foo1 = Foo() 
     self.foo2 = Foo() 
     self.foo3 = Foo() 

foo = Foo() 
print Foo.instances 
bar = Bar() 
bar.testy() 
print Foo.instances 

x_tot = 0 
for inst in Foo.instances: 
    x_tot += inst.x 
    print x_tot 

de salida:

[<__main__.Foo instance at 0x108e334d0>] 
[<__main__.Foo instance at 0x108e334d0>, <__main__.Foo instance at 0x108e33560>, <__main__.Foo instance at 0x108e335a8>, <__main__.Foo instance at 0x108e335f0>] 
5 
10 
15 
20 
21

@ respuesta de JoelCornett cubre los conceptos básicos a la perfección. Esta es una versión un poco más complicada, que podría ayudar con algunos problemas sutiles.

Si desea poder acceder a todas las instancias "en vivo" de una determinada clase, subclase el siguiente (o incluir código equivalente en su propia clase base):

from weakref import WeakSet 

class base(object): 
    def __new__(cls, *args, **kwargs): 
     instance = object.__new__(cls, *args, **kwargs) 
     if "instances" not in cls.__dict__: 
      cls.instances = WeakSet() 
     cls.instances.add(instance) 
     return instance 

Esto responde a dos puntos posibles con la implementación más simple que presenta @JoelCornett:

  1. Cada subclase de base hará un seguimiento de sus propios casos por separado. No obtendrá instancias de subclase en la lista de instancias de una clase principal, y una subclase nunca tropezará con las instancias de una subclase de hermanos. Esto puede ser indeseable, dependiendo de su caso de uso, pero probablemente sea más fácil fusionar los conjuntos nuevamente que dividirlos.

  2. El conjunto instances utiliza referencias débiles a instancias de la clase, por lo que si del o reasignar todas las demás referencias a una instancia en el código otra parte, el código de contabilidad no va a evitar que se recoge la basura. De nuevo, esto puede no ser deseable para algunos casos de uso, pero es bastante fácil usar conjuntos regulares (o listas) en lugar de un conjunto debilitado si realmente desea que cada instancia dure para siempre.

Algunos salida de prueba práctico-excelente (con los conjuntos instances siempre que se pasa al list sólo porque no se imprimen muy bien):

>>> b = base() 
>>> list(base.instances) 
[<__main__.base object at 0x00000000026067F0>] 
>>> class foo(base): 
...  pass 
... 
>>> f = foo() 
>>> list(foo.instances) 
[<__main__.foo object at 0x0000000002606898>] 
>>> list(base.instances) 
[<__main__.base object at 0x00000000026067F0>] 
>>> del f 
>>> list(foo.instances) 
[] 
+0

WeakSet, desafortunadamente, usará semántica de hash estándar en su lugar de la semántica de identidad, lo que significa que si la clase base del OP quiere anular '__eq__', se producirá un error sin la correspondiente anulación' __hash__', e incluso con la anulación, se comportará mal ya que unirá objetos que son iguales. – Kevin

+0

Hmm, ese es un buen punto, realmente no necesitamos ni queremos la semántica 'set' que viene con' WeakSet'. Lo elegí porque es el único contenedor "débil" que no corresponde al mapa definido en el módulo 'weakref'. Supongo que una 'lista' de objetos' weakref.ref' sería mejor, pero es un poco menos conveniente para trabajar. – Blckknght

0

Otra opción para los cortes de bajo nivel y rápidas la depuración consiste en filtrar la lista de objetos devueltos por gc.get_objects() y generar el diccionario sobre la marcha de esa manera. En CPython esa función le devolverá una lista (generalmente enorme) de todo que el recolector de basura conoce, por lo que definitivamente contendrá todas las instancias de cualquier clase particular definida por el usuario.

Tenga en cuenta que esto está cavando un poco en las partes internas del intérprete, por lo que puede o no funcionar (o funcionar bien) con los gustos de Jython, PyPy, IronPython, etc. No he comprobado. También es probable que sea realmente lento independientemente. Usar con precaución/YMMV/etc.

Sin embargo, imagino que algunas personas que se topen con esta pregunta podrían eventualmente querer hacer este tipo de cosas como algo único para descubrir qué está sucediendo con el estado de ejecución de una porción de código que se comporta de manera extraña. Este método tiene el beneficio de no afectar en absoluto las instancias o su construcción, lo que podría ser útil si el código en cuestión proviene de una biblioteca de terceros o algo así.

6

Es probable que desee utilizar referencias débiles a sus instancias. De lo contrario, la clase podría terminar haciendo un seguimiento de las instancias que se suponía que debían haber sido eliminadas. Un weakref.WeakSet eliminará automáticamente cualquier instancia muerta de su conjunto.

Una manera de mantener un registro de los casos es una variable de clase:

import weakref 
class A(object): 
    instances = weakref.WeakSet() 

    def __init__(self, foo): 
     self.foo = foo 
     A.instances.add(self) 

    @classmethod 
    def get_instances(cls): 
     return list(A.instances) #Returns list of all current instances 

Al final del programa, puede crear su dict la siguiente manera:

foo_vars = {id (ejemplo) : instance.foo por ejemplo en A.instances} Sólo hay una lista:

>>> a = A(1) 
>>> b = A(2) 
>>> A.get_instances() 
[<inst.A object at 0x100587290>, <inst.A object at 0x100587250>] 
>>> id(A.instances) 
4299861712 
>>> id(a.instances) 
4299861712 
>>> id(b.instances) 
4299861712 
>>> a = A(3) #original a will be dereferenced and replaced with new instance 
>>> A.get_instances() 
[<inst.A object at 0x100587290>, <inst.A object at 0x1005872d0>] 
+0

¿Sería posible usar un diccionario de géneros en lugar del WeakSet para permitir buscar una instancia por clave? – omegacore

+1

Respondiendo a mi propia pregunta aquí, sí, es posible. Usé el weakvaluedictionary. Parece que funciona a la perfección. – omegacore

+0

Esto es interesante pero no completamente confiable: cuando se elimina una referencia ('del a'), puede que no esté fuera de las instancias establecidas en la línea siguiente, especialmente si se ha manejado una excepción mientras tanto. Ver la pregunta que hice [aquí] (https://stackoverflow.com/questions/48528694/how-to-keep-track-of-instances-of-python-objects-in-a-reliable-way) para más detalles . – zezollo

0

también puede resolver este problema utilizando una metaclass:

  1. Cuando una clase se crea (__init__ método de metaclase), añadir un nuevo registro de la instancia
  2. Cuando se crea una nueva instancia de esta clase (__call__ método de metaclase), añadirlo al registro de la instancia.

La ventaja de este enfoque es que cada clase tiene un registro, incluso si no existe una instancia. Por el contrario, al anular __new__ (como en Blckknght's answer), el registro se agrega cuando se crea la primera instancia.

class MetaInstanceRegistry(type): 
    """Metaclass providing an instance registry""" 

    def __init__(cls, name, bases, attrs): 
     # Create class 
     super(MetaInstanceRegistry, cls).__init__(name, bases, attrs) 

     # Initialize fresh instance storage 
     cls._instances = weakref.WeakSet() 

    def __call__(cls, *args, **kwargs): 
     # Create instance (calls __init__ and __new__ methods) 
     inst = super(MetaInstanceRegistry, cls).__call__(*args, **kwargs) 

     # Store weak reference to instance. WeakSet will automatically remove 
     # references to objects that have been garbage collected 
     cls._instances.add(inst) 

     return inst 

    def _get_instances(cls, recursive=False): 
     """Get all instances of this class in the registry. If recursive=True 
     search subclasses recursively""" 
     instances = list(cls._instances) 
     if recursive: 
      for Child in cls.__subclasses__(): 
       instances += Child._get_instances(recursive=recursive) 

     # Remove duplicates from multiple inheritance. 
     return list(set(instances)) 

Uso: Cree un registro y la subclase.

class Registry(object): 
    __metaclass__ = MetaInstanceRegistry 


class Base(Registry): 
    def __init__(self, x): 
     self.x = x 


class A(Base): 
    pass 


class B(Base): 
    pass 


class C(B): 
    pass 


a = A(x=1) 
a2 = A(2) 
b = B(x=3) 
c = C(4) 

for cls in [Base, A, B, C]: 
    print cls.__name__ 
    print cls._get_instances() 
    print cls._get_instances(recursive=True) 
    print 

del c 
print C._get_instances() 

Si el uso de clases base abstractas del módulo abc, simplemente subclase abc.ABCMeta para evitar MetaClass conflictos:

from abc import ABCMeta, abstractmethod 


class ABCMetaInstanceRegistry(MetaInstanceRegistry, ABCMeta): 
    pass 


class ABCRegistry(object): 
    __metaclass__ = ABCMetaInstanceRegistry 


class ABCBase(ABCRegistry): 
    __metaclass__ = ABCMeta 

    @abstractmethod 
    def f(self): 
     pass 


class E(ABCBase): 
    def __init__(self, x): 
     self.x = x 

    def f(self): 
     return self.x 

e = E(x=5) 
print E._get_instances() 
Cuestiones relacionadas