4

Someone asked a similar one [question]:Printing all instances of a class. While I am less concerned about printing them, I'd rather to know how many instances are currently "live". The reason for this instance capture is more like a setting up a scheduled job, every hour check these "live" unprocessed instances and enrich the data. After that, either a flag in this instance is set or just delete this instance. Torsten Marek 's answer in [question]:Printing all instances of a class using weakrefs need a call to the base class constructor for every class of this type, is it possible to automate this? Or we can get all instances with some other methods?

Community
  • 1
  • 1
NathaneilCapital
  • 1,389
  • 5
  • 17
  • 23

4 Answers4

6

You can either track it on your own (see the other answers) or ask the garbage collector:

import gc

class Foo(object):
   pass

foo1, foo2 = Foo(), Foo()

foocount = sum(1 for o in gc.get_referrers(Foo) if o.__class__ is Foo)

This can be kinda slow if you have a lot of objects, but it's generally not too bad, and it has the advantage of being something you can easily use with someone else's code.

Note: Used o.__class__ rather than type(o) so it works with old-style classes.

kindall
  • 178,883
  • 35
  • 278
  • 309
3

If you only want this to work for CPython, and your definition of "live" can be a little lax, there's another way to do this that may be useful for debugging/introspection purposes:

>>> import gc
>>> class Foo(object): pass
>>> spam, eggs = Foo(), Foo()
>>> foos = [obj for obj in gc.get_objects() if isinstance(obj, Foo)]
>>> foos
[<__main__.Foo at 0x1153f0190>, <__main__.Foo at 0x1153f0210>]
>>> del spam
>>> foos = [obj for obj in gc.get_objects() if isinstance(obj, Foo)]
>>> foos
[<__main__.Foo at 0x1153f0190>, <__main__.Foo at 0x1153f0210>]
>>> del foos
>>> foos = [obj for obj in gc.get_objects() if isinstance(obj, Foo)]
>>> foos
[<__main__.Foo at 0x1153f0190>]

Note that deleting spam didn't actually make it non-live, because we've still got a reference to the same object in foos. And reassigning foos didn't not help, because apparently the call to get_objects happened before the old version is released. But eventually it went away once we stopped referring to it.

And the only way around this problem is to use weakrefs.

Of course this will be horribly slow in a large system, with or without weakrefs.

abarnert
  • 354,177
  • 51
  • 601
  • 671
1

Sure, store the count in a class attribute:

class CountedMixin(object):
    count = 0
    def __init__(self, *args, **kwargs):
        type(self).count += 1
        super().__init__(*args, **kwargs)
    def __del__(self):
        type(self).count -= 1
        try:
            super().__del__()
        except AttributeError:
            pass

You could make this slightly more magical with a decorator or a metaclass than with a base class, or simpler if it can be a bit less general (I've attempted to make this fit in anywhere in any reasonable multiple-inheritance hierarchy, which you usually don't need to worry about…), but basically, this is all there is to it.

If you want to have the instances themselves (or, better, weakrefs to them), rather than just a count of them, just replace count=0 with instances=set(), then do instances.add(self) instead of count += 1, etc. (Again, though, you probably want a weakref to self, rather than self.)

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • 2
    Probably worth mentioning: as with anything that relies on the `__del__` method, this can cause unintuitive behavior. For example: `CountedMixin()` causes `CountedMixin.count` to increment for a while, even though the new object is unreachable. – Henry Keiter Jan 15 '14 at 22:46
  • `__del__` also prevents the cyclic garbage collector from working on those objects – John La Rooy Jan 15 '14 at 22:55
  • @gnibbler: Yes, if you want to create cycles of objects and also hook their destruction, you need to add your own cycle-breaker to `gc.callbacks`. – abarnert Jan 15 '14 at 22:57
  • 2
    This is one of those places where `__count` is probably better than `count` – John La Rooy Jan 15 '14 at 23:01
0

I cannot comment to the answer of kindall, thus I write my comment as answer:

The solution with gc.get_referrers(<ClassName>) does not work with inherited classes in python 3. The method gc.get_referrers(<ClassName>) does not return any instances of a class that was inherited from <ClassName>.

Instead you need to use gc.get_objects() which is much slower, since it returns a full list of objects. But in case of unit-tests, where you simply want to ensure your objects get deleted after the test (no circular references) it should be sufficient and fast enough.

Also do not forget to call gc.collect() before checking the number of your instances, to ensure all unreferenced instances are really deleted.

I also saw an issue with weak references which are also counted in this way. The problem with weak references is, that the object which is referenced might not exist any more, thus isinstance(Instance, Class) might fail with an error about non existing weak references.

Here is a simple code example:

import gc

def getInstances(Class):
  gc.collect()
  Number = 0
  InstanceList = gc.get_objects()
  for Instance in InstanceList:
    if 'weakproxy' not in str(type(Instance)): # avoid weak references
      if isinstance(Instance, Class):
        Number += 1

  return Number
CountVonCount
  • 85
  • 1
  • 5