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:
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.El conjunto de
instances
utiliza referencias débiles a las instancias de la clase, por lo que sidel
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 :
- Cuando se crea una clase (método
__init__
de metaclase), agregue un nuevo registro de instancia - 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