patternpythonMinor
Base class for subclasses that can track their own instances
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
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:
```
#! /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 (
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:
If you have a line length problem doing so, use parentheses:
Although it doesn't work with
This line:
Could be simplified to:
You don't need to build the list, you can pass the generator expression straight to
I would check whether you have too many instances as the first thing in
Note:
Making
It seems unnecessary to remove the empty list from
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_literalsIf 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.formatrather 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_literalsfrom __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.