Base class for subclasses that can track their own instances I'm new to Python, with some background in Java and C# from a while ago, and more recently in scripting worlds like Bash and AppleScript.

I wanted to be able to create classes that could


report their instances
limit their number of instances.


Synthesizing various answers from Stack Overflow and general trial and error, I developed the following base class that can be inherited from when creating new classes. I'd like feedback on:


Could I be doing it better/more elegantly/more efficiently?
Does the code style follow PEP 8?
Anything else you think a Python noob should be considering?




#! /usr/bin/env python

# for Python 2.6/2.7; omit for Python 3.x
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import
from __future__ import division

# technique for instance-tracking class is adapted from:
#   http://stackoverflow.com/a/23844406/3531387
class SelfTrackingClass(object):
    """Use as base class for classes that can report their instances.

    Subclass.instances: get list of object instances.
    Subclass.max: get/set max number of instances; default is unlimited.
    """

    # set in Subclass to limit number of instances of Subclass
    max = None

    # key: class name as string, value: list of weakrefs to instances
    _classnames = {}

    # technique for "class properties" is adapted from:
    #   http://stackoverflow.com/a/7864317/3531387
    class ClassProperty(property):
        def __get__(self, cls, owner):
            return self.fget.__get__(None, owner)()
    @ClassProperty
    @classmethod
    def instances(cls):
        """Return tuple of instances for this class, or None if none."""
        try:
            return tuple(
              [instance() for instance in cls._classnames[cls.__name__]])
        except KeyError:
            return None

    def __new__(cls):
        """Create instance, if not over limit, and store ref to it."""
        import weakref
        if (cls.__name__ not in cls._classnames):
            cls._classnames[cls.__name__] = []
        # enforce maximum number of instances
        if ((cls.max is not None) and (len(cls.instances) &gt;= cls.max)):
            raise Exception("Too many instances of class: " + cls.__name__)
        instance = object.__new__(cls)
        cls._classnames[cls.__name__].append(weakref.ref(instance))
        return instance

    def __del__(self):
        """Remove ref to dead instance from list of instances."""
        for instance in self.__class__._classnames[self.__class__.__name__]:
            if (instance() is None):
                self.__class__._classnames[
                  self.__class__.__name__].remove(instance)    
        if (len(self.__class__._classnames[self.__class__.__name__]) == 0):
            del self.__class__._classnames[self.__class__.__name__]


Example:

class MyClass1(SelfTrackingClass):
    pass

class MyClass2(SelfTrackingClass):
    pass

a = MyClass1()
b = MyClass1()
print(MyClass1.instances)
c = MyClass2()
print(MyClass2.instances)
del a
print(MyClass1.instances)
del b
print(MyClass1.instances)
MyClass1.max = 2
d = MyClass1()
e = MyClass1()
f = MyClass1()


Output:

(&lt;__main__.MyClass1 object at 0x10e61c4d0&gt;, &lt;__main__.MyClass1 object at 0x10e61c7d0&gt;)
(&lt;__main__.MyClass2 object at 0x10e61c810&gt;,)
(&lt;__main__.MyClass1 object at 0x10e61c7d0&gt;,)
None
Traceback (most recent call last):
  File "./tst2.py", line 78, in &lt;module&gt;
    f = MyClass1()
  File "./tst2.py", line 46, in __new__
    raise Exception("Too many instances of class: " + cls.__name__)
Exception: Too many instances of class: MyClass1
Generally, that all looks pretty good - Pythonic and well laid out. A few possible improvements below.



Per PEP-0008, multiple imports from the same module can be done on one line, and I would recommend alphabetical order:

from __future__ import absolute_import, division, print_function, unicode_literals


If you have a line length problem doing so, use parentheses:

from __future__ import (absolute_import, division, 
                        print_function, unicode_literals)


Although it doesn't work with from __future__ ..., note that if you only want something imported in a particular version, you can do something like:

from sys import version_info

if version_info.major &lt; 3:
    ...




This line:

return tuple(
  [instance() for instance in cls._classnames[cls.__name__]])


Could be simplified to:

return tuple(
  instance() for instance in cls._classnames[cls.__name__])


You don't need to build the list, you can pass the generator expression straight to tuple().



I would check whether you have too many instances as the first thing in __new__; why do work you don't need to?

def __new__(cls):
    """Create instance, if not over limit, and store ref to it."""
    if cls.max is not None and len(cls.instances) &gt;= cls.max:
        raise Exception("Too many instances of class: {0}".format(cls.__name__))
    ...


Note:


Removal of the import - that should be at the top of the file, with the others; 
Use of str.format rather than + string concatenation; and 
Removal of redundant parentheses (just my personal taste) and comment.




Making SelfTrackingClass._classnames a defaultdict(list) from collections would save you from needing to manually check and add a new list when a new class name is used. Alternatively, you can use dict.setdefault:

cls._classnames.setdefault(cls.__name__, []).append(weakref.ref(instance))


It seems unnecessary to remove the empty list from _classnames when you delete an instance.