HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Base class for subclasses that can track their own instances

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
cantrackinstancesownthatforsubclassesclasstheirbase

Problem

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 (

Solution

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

Code Snippets

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

if version_info.major < 3:
    ...
return tuple(
  [instance() for instance in cls._classnames[cls.__name__]])
return tuple(
  instance() for instance in cls._classnames[cls.__name__])

Context

StackExchange Code Review Q#54627, answer score: 2

Revisions (0)

No revisions yet.