Как отслеживать экземпляры классов?
В конце программы я хочу загрузить определенную переменную из всех экземпляров класса в словарь.
Например:
class Foo():
__init__(self):
x = {}
foo1 = Foo()
foo2 = Foo()
foo...etc.
Скажем, количество экземпляров будет меняться, и я хочу, чтобы x dict из каждого экземпляра Foo() загружался в новый dict. Как мне это сделать?
Примеры, которые я видел в SO, предполагают, что у одного уже есть список экземпляров.
Ответы
Ответ 1
Один из способов отслеживания экземпляров - это переменная класса:
class A(object):
instances = []
def __init__(self, foo):
self.foo = foo
A.instances.append(self)
В конце программы вы можете создать свой dict следующим образом:
foo_vars = {id(instance): instance.foo for instance in A.instances}
Существует только один список:
>>> 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
Ответ 2
@JoelCornett отвечает полностью на основы. Это немного более сложная версия, которая может помочь в нескольких тонких проблемах.
Если вы хотите иметь доступ ко всем "живым" экземплярам данного класса, подкласс следующий (или включить эквивалентный код в свой собственный базовый класс):
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
Это касается двух возможных проблем с более простой реализацией, представленной @JoelCornett:
-
Каждый подкласс base
будет отслеживать отдельные экземпляры отдельно. Вы не получите экземпляры подкласса в списке экземпляров родительского класса, и один подкласс никогда не будет спотыкаться о экземплярах подкласса sibling. Это может быть нежелательным, в зависимости от вашего варианта использования, но, вероятно, проще объединить множества обратно, а не разделять их.
-
В наборе instances
используются слабые ссылки на экземпляры класса, поэтому, если вы del
или переназначаете все другие ссылки на экземпляр в другом месте вашего кода, код бухгалтерского учета не будет препятствовать сбору мусора, Опять же, это может быть нежелательно для некоторых случаев использования, но достаточно легко использовать обычные наборы (или списки) вместо слабого, если вы действительно хотите, чтобы каждый экземпляр продолжался вечно.
Какой-то удобный тестовый вывод (с наборами instances
всегда передается на list
только потому, что они не распечатываются красиво):
>>> 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)
[]
Ответ 3
Вероятно, вы захотите использовать слабые ссылки на свои экземпляры. В противном случае класс, вероятно, мог бы отслеживать экземпляры, которые должны были быть удалены. Слабыйref.WeakSet автоматически удалит все мертвые экземпляры из своего набора.
Один из способов отслеживания экземпляров - это переменная класса:
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
В конце программы вы можете создать свой dict следующим образом:
foo_vars = {id (instance): instance.foo, например, в A.instances}
Существует только один список:
>>> 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>]
Ответ 4
Вы также можете решить эту проблему, используя metaclass:
- Когда создается класс (
__init__
метод метакласса), добавьте новый реестр экземпляров
- Когда создается новый экземпляр этого класса (
__call__
метод метакласса), добавьте его в реестр экземпляров.
Преимущество этого подхода в том, что каждый класс имеет реестр - даже если ни один экземпляр не существует. Напротив, при переопределении __new__
(как в Blckknght answer) реестр добавляется при создании первого экземпляра.
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))
Использование: создайте реестр и подклассы его.
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()
Если использовать абстрактные базовые классы из модуля abc
, просто подкласс abc.ABCMeta
, чтобы избежать конфликтов метакласса:
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()
Ответ 5
Используя ответ от @Joel Cornett, я придумал следующее, которое, похоже, работает. то есть я могу суммировать объектные переменные.
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
выход:
[<__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
Ответ 6
Еще одна опция для быстрых хакеров и отладки низкого уровня - это фильтрация списка объектов, возвращаемых gc.get_objects()
, и генерация словаря на лету таким образом. В CPython эта функция вернет вам (как правило, огромный) список всего, о чем знает сборщик мусора, поэтому он обязательно будет содержать все экземпляры любого определенного пользовательского класса.
Обратите внимание, что это немного копается во внутренностях интерпретатора, поэтому он может работать или не работать (или работать хорошо) с подобными Jython, PyPy, IronPython и т.д. Я не проверял. Это также, вероятно, будет действительно медленным, несмотря на это. Следует использовать с осторожностью/YMMV/и т.д.
Однако я предполагаю, что некоторые люди, столкнувшиеся с этим вопросом, могут в конечном итоге захотят сделать такие вещи как разовые, чтобы выяснить, что происходит с состоянием выполнения некоторого фрагмента кода, который ведет себя странно. Этот метод имеет преимущество не затрагивать экземпляры или их конструкцию вообще, что может быть полезно, если этот код выходит из сторонней библиотеки или что-то в этом роде.