31

Toward the end of a program I'm looking to load a specific variable from all the instances of a class into a dictionary.

For example:

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

foo1 = Foo()
foo2 = Foo()
...

Let's say the number of instances will vary and I want the x dict from each instance of Foo() loaded into a new dict. How would I do that?

The examples I've seen in SO assume one already has the list of instances.

John Vandenberg
  • 474
  • 6
  • 16
DBWeinstein
  • 8,605
  • 31
  • 73
  • 118
  • 1
    I suspect it's not possible without deep introspection (e.g. recursively expanding all the objects in the `locals` and `globals` dictionaries of all your stack frames). It's much easier to make your class's `__init__` or `__new__` method create a weak reference and stick that into a list somewhere. – Blckknght Aug 24 '12 at 01:03
  • 2
    This question explains it: http://stackoverflow.com/questions/328851/python-printing-all-instances-of-a-class – TJD Aug 24 '12 at 01:03
  • @Blckknght: I unintentionally stole your suggestion for my answer. – Joel Cornett Aug 24 '12 at 01:40

8 Answers8

49

One way to keep track of instances is with a class variable:

class A(object):
    instances = []

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

At the end of the program, you can create your dict like this:

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

There is only one list:

>>> 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    
Joel Cornett
  • 24,192
  • 9
  • 66
  • 88
  • thanks! But, won't this create a separate copy of 'instances' in each instance of A? Wouldn't A.instances always have one item in the list? – DBWeinstein Aug 24 '12 at 01:45
  • 2
    @dwstein: No, see edit. `instances` is a class variable. Here is a related concept: [“Least Astonishment” in Python: The Mutable Default Argument](http://stackoverflow.com/q/1132941/1142167) – Joel Cornett Aug 24 '12 at 01:50
  • Thanks for the detailed answer and the info. It's gonna take me a while to absorb. Also, will 'instances' include instances of derived classes? Or, will that be a separate list? – DBWeinstein Aug 24 '12 at 01:59
  • They will be the same list. This behavior can be confusing, but it shouldn't be (if you look at it correctly). In python, all variables are references to objects. Only assignment operations change what a variable is pointing to (thus `instances = []` will cause this variable to point to a new list object). There is only one assignment operation defined here. All the rest of the operations (`A.instances.append()` for example) operate on the actual object -- they don't reassign the variable name. Classes don't operate any differently. – Joel Cornett Aug 24 '12 at 02:08
  • 1
    You could make it create separate instance lists for subclasses if you overrode the `__new__` method instead of `__init__`. One of its arguments is the class of the object being created, so you can assign to it in the right place (though you'll need to go explictly via `cls.__dict__[instances]` to avoid the instances dict being inherited). Hmm, perhaps I should write that up as my own answer.... – Blckknght Aug 24 '12 at 02:30
  • @JoelCornett I've been playing with this for a couple hours and it's exactly what I'm looking for. Wherever I am in the code I have access to a list of instances and the information in them. Thanks again. – DBWeinstein Aug 24 '12 at 03:44
  • @dwstein: Thanks for that link. This *is* a great post. – Joel Cornett Aug 24 '12 at 18:02
  • This answer seems to offer a different perspective. Can anyone confirm? http://stackoverflow.com/questions/4831307/is-it-bad-to-store-all-instances-of-a-class-in-a-class-field – atm Mar 22 '17 at 15:47
36

@JoelCornett's answer covers the basics perfectly. This is a slightly more complicated version, which might help with a few subtle issues.

If you want to be able to access all the "live" instances of a given class, subclass the following (or include equivalent code in your own base class):

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

This addresses two possible issues with the simpler implementation that @JoelCornett presented:

  1. Each subclass of base will keep track of its own instances separately. You won't get subclass instances in a parent class's instance list, and one subclass will never stumble over instances of a sibling subclass. This might be undesirable, depending on your use case, but it's probably easier to merge the sets back together than it is to split them apart.

  2. The instances set uses weak references to the class's instances, so if you del or reassign all the other references to an instance elsewhere in your code, the bookkeeping code will not prevent it from being garbage collected. Again, this might not be desirable for some use cases, but it is easy enough to use regular sets (or lists) instead of a weakset if you really want every instance to last forever.

Some handy-dandy test output (with the instances sets always being passed to list only because they don't print out nicely):

>>> 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)
[]
Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • 2
    WeakSet, unfortunately, will use standard hashing semantics instead of identity semantics, which means if the OP's base class wants to override `__eq__`, it will error out without a corresponding `__hash__` override, and even with the override, it will still misbehave since it will coalesce objects that are equal. – Kevin May 15 '16 at 00:06
  • 1
    Hmm, that's a good point, we don't really need or want the `set` semantics that come with `WeakSet`. I just picked it because it's the only non-mapping "weak" container defined in the `weakref` module. I guess a `list` of `weakref.ref` objects would be better, but it's a bit less convenient to work with. – Blckknght May 15 '16 at 08:44
12

You would probably want to use weak references to your instances. Otherwise the class could likely end up keeping track of instances that were meant to have been deleted. A weakref.WeakSet will automatically remove any dead instances from its set.

One way to keep track of instances is with a class variable:

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

At the end of the program, you can create your dict like this:

foo_vars = {id(instance): instance.foo for instance in A.instances} There is only one list:

>>> 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>]   
OrenR61
  • 121
  • 1
  • 5
  • Would it be possible to use a dictionary of sorts instead of the WeakSet to allow looking up an instance by key? – omegacore Jan 06 '16 at 19:46
  • 1
    Answering my own question here, yes it is possible. I used the weakvaluedictionary. Seems to work perfectly. – omegacore Jan 07 '16 at 21:53
  • 2
    This is interesting but not completely reliable: when a reference is deleted (`del a`), it may not be out of the instances' set at next line, especially if an exception has been handled in the mean time. See the question I asked [here](https://stackoverflow.com/questions/48528694/how-to-keep-track-of-instances-of-python-objects-in-a-reliable-way) for more details. – zezollo Feb 04 '18 at 08:01
3

You can also solve this problem using a metaclass:

  1. When a class is created (__init__ method of metaclass), add a new instance registry
  2. When a new instance of this class is created (__call__ method of metaclass), add it to the instance registry.

The advantage of this approach is that each class has a registry - even if no instance exists. In contrast, when overriding __new__ (as in Blckknght's answer), the registry is added when the first instance is created.

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

Usage: Create a registry and subclass it.

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

If using abstract base classes from the abc module, just subclass abc.ABCMeta to avoid metaclass conflicts:

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()
sfinkens
  • 1,210
  • 12
  • 15
1

Another option for quick low-level hacks and debugging is to filter the list of objects returned by gc.get_objects() and generate the dictionary on the fly that way. In CPython that function will return you a (generally huge) list of everything the garbage collector knows about, so it will definitely contain all of the instances of any particular user-defined class.

Note that this is digging a bit into the internals of the interpreter, so it may or may not work (or work well) with the likes of Jython, PyPy, IronPython, etc. I haven't checked. It's also likely to be really slow regardless. Use with caution/YMMV/etc.

However, I imagine that some people running into this question might eventually want to do this sort of thing as a one-off to figure out what's going on with the runtime state of some slice of code that's behaving strangely. This method has the benefit of not affecting the instances or their construction at all, which might be useful if the code in question is coming out of a third-party library or something.

Walter Mundt
  • 24,753
  • 5
  • 53
  • 61
1

Here's a similar approach to Blckknght's, which works with subclasses as well. Thought this might be of interest, if someone ends up here. One difference, if B is a subclass of A, and b is an instance of B, b will appear in both A.instances and B.instances. As stated by Blckknght, this depends on the use case.

from weakref import WeakSet


class RegisterInstancesMixin:
    instances = WeakSet()

    def __new__(cls, *args, **kargs):
        o = object.__new__(cls, *args, **kargs)
        cls._register_instance(o)
        return o

    @classmethod
    def print_instances(cls):
        for instance in cls.instances:
            print(instance)

    @classmethod
    def _register_instance(cls, instance):
        cls.instances.add(instance)
        for b in cls.__bases__:
            if issubclass(b, RegisterInstancesMixin):
                b._register_instance(instance)

    def __init_subclass__(cls):
        cls.instances = WeakSet()


class Animal(RegisterInstancesMixin):
    pass


class Mammal(Animal):
    pass


class Human(Mammal):
    pass


class Dog(Mammal):
    pass


alice = Human()
bob = Human()
cannelle = Dog()
Animal.print_instances()
Mammal.print_instances()
Human.print_instances()

Animal.print_instances() will print three objects, whereas Human.print_instances() will print two.

ArthurPGB
  • 43
  • 4
0

Using the answer from @Joel Cornett I've come up with the following, which seems to work. i.e. i'm able to total up object variables.

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

output:

[<__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
DBWeinstein
  • 8,605
  • 31
  • 73
  • 118
0

(For Python) I have found a way to record the class instances via the "dataclass" decorator while defining a class. Define a class attribute 'instances' (or any other name) as a list of the instances you want to record. Append that list with the 'dict' form of created objects via the dunder method __dict__. Thus, the class attribute 'instances' will record instances in the dict form, which you want.

For example,

from dataclasses import dataclass

@dataclass
class player:
    instances=[]
    def __init__(self,name,rank):
        self.name=name
        self.rank=rank
        self.instances.append(self.__dict__)