propiedades programacion poo otra objetos metodos metodo llamar importar herencia crear como clases clase atributo python class dictionary self instances

programacion - poo python 3



¿Cómo hacer un seguimiento de las instancias de clase? (6)

Hacia el final de un programa, estoy buscando cargar una variable específica de todas las instancias de una clase en un diccionario.

Por ejemplo:

class Foo(): __init__(self): x = {} foo1 = Foo() foo2 = Foo() foo...etc.

Digamos que la cantidad de instancias variará y quiero que el dictado x de cada instancia de Foo () se cargue en un dictado nuevo. ¿Como podría hacerlo?

Los ejemplos que he visto en SO asumen que uno ya tiene la lista de instancias.


La respuesta de @JoelCornett cubre perfectamente lo básico. 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 clase determinada, haga una subclase de lo siguiente (o incluya un 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 aborda dos posibles problemas con la implementación más sencilla que presentó @JoelCornett:

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

  2. El conjunto de instances utiliza referencias débiles a las instancias de la clase, por lo que si del o reasigna todas las demás referencias a una instancia en otro lugar de su código, el código de contabilidad no evitará que se recolecte la basura. De nuevo, esto podría no ser deseable para algunos casos de uso, pero es lo suficientemente fácil como para usar conjuntos regulares (o listas) en lugar de un conjunto de texto si realmente quiere que todas las instancias duren para siempre.

Algunos resultados de prueba prácticos (con los conjuntos de instances que se pasan siempre a la list solo porque no se imprimen 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) []


Otra opción para hacks y depuración rápidos de bajo nivel es 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 lo que el recolector de basura conoce, por lo que definitivamente contendrá todas las instancias de cualquier clase definida por el usuario en particular.

Tenga en cuenta que esto es indagar un poco en la parte interna del intérprete, por lo que puede o no funcionar (o funcionar bien) con personas como Jython, PyPy, IronPython, etc. No lo he comprobado. También es probable que sea muy lento independientemente. Utilizar con precaución / YMMV / etc.

Sin embargo, me imagino que algunas personas que se encuentran con esta pregunta podrían querer finalmente hacer este tipo de cosas como una sola vez para averiguar qué está pasando con el estado de ejecución de algún segmento de código que se está comportando de manera extraña. Este método tiene la ventaja de no afectar las instancias o su construcción en absoluto, lo que podría ser útil si el código en cuestión está saliendo de una biblioteca de terceros o algo así.


Probablemente querrá usar referencias débiles para sus instancias. De lo contrario, es probable que la clase termine haciendo un seguimiento de las instancias que debían haberse eliminado. Un valor débil de.weakSet eliminará automáticamente cualquier instancia muerta de su conjunto.

Una forma de realizar un seguimiento de las instancias es con 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, puedes crear tu dict de esta manera:

foo_vars = {id (instance): instance.foo por ejemplo en A.instances} Solo 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>]


También puedes resolver este problema usando una metaclass :

  1. Cuando se crea una clase (método __init__ de metaclase), agregue un nuevo registro de instancia
  2. Cuando se crea una nueva instancia de esta clase (método __call__ de metaclase), agréguela al registro de instancias.

La ventaja de este enfoque es que cada clase tiene un registro, incluso si no existe ninguna instancia. Por el contrario, al reemplazar __new__ (como en la respuesta de Blckknght ), 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: Crear un registro y subclasificarlo.

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 usa clases base abstractas desde el módulo abc , solo subclase abc.ABCMeta para evitar conflictos de metaclases:

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()


Una forma de realizar un seguimiento de las instancias es con una variable de clase:

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

Al final del programa, puedes crear tu dict de esta manera:

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

Solo 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


Usando la respuesta de @Joel Cornett, se me ocurrió lo siguiente, que parece funcionar. es decir, soy capaz de totalizar variables de 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

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