Mike McCoy
Mike McCoy

Reputation: 279

Using base class member to keep track of derived class instances

I want to keep track of all of the class instances that derive from a base class, and I'm curious if the following way is common, or if it presents serious pitfalls. That is, pattern or antipattern?

class Animal(object):
    all_animals = []
    def __init__(self):
        self.all_animals.append(self)

class Dog(Animal):
    def __init__(self):
        # Do stuff here...
        super(Dog, self).__init__()

class Person(Animal):
    def __init__(self):
        # Do other stuff here...
        super(Person, self).__init__()

Is this a recommended way to do things? Does this method have any trouble when the inheritance happens over a large project or a number of files? For example, can I rely on a consistent Animal.all_animals cache?

Upvotes: 1

Views: 241

Answers (1)

Ashwini Chaudhary
Ashwini Chaudhary

Reputation: 250931

One issue in your approach is that the list will prevent the instances from being garbage collected once there are no more references left to them. So, if the instances are hashable and order of instances doesn't matter then we can use weakref.WeakSet to store the instances and instead of storing the instances in __init__ method we can use a Metaclass to do this whenever an instance is created.(You can use the weakreflist package from PyPI if you want a list)

When we instantiate a class its Metaclass's __call__ method is called first, there we can store the instance in the WeakSet. Apart from this it would make more sense if we allow all_animals to be accessed only from Animal class, so I've used a descriptor whose __get__ method will only allow us to access it from Animal class.

from weakref import WeakSet


class Meta(type):
    def __call__(self):
        instance = super(Meta, self).__call__()
        Animal.all_animals.add(instance)
        return instance


class Animals_prop(object):

    def __init__(self, cls=WeakSet):
        self.all_animals = cls()

    def __get__(self, ins, cls):
        if ins is None and cls is Animal:
            return self.all_animals
        else:
            raise AttributeError


class Animal(object):
    __metaclass__ = Meta
    all_animals = Animals_prop()

    def __del__(self):
        print "{}'s instance is dead.".format(type(self).__name__)


class Dog(Animal):
    pass


class Person(Animal):
    pass


if __name__ == '__main__':
    a = Animal()
    b = Dog()
    c = Person()
    print set(Animal.all_animals)
    del c
    print set(Animal.all_animals)
    print '-'*10

Output:

set([<__main__.Animal object at 0x1012f5590>, <__main__.Dog object at 0x1012f55d0>, <__main__.Person object at 0x1012f5610>])
Person's instance is dead.
set([<__main__.Animal object at 0x1012f5590>, <__main__.Dog object at 0x1012f55d0>])
----------
Animal's instance is dead.
Dog's instance is dead.

Upvotes: 1

Related Questions