Object-oriented – Is it good practice to store instances within a class variable in Python

design-patternsobject-orientedpython

I've come across two Stack Overflow posts that seem to offer conflicting answers:

Can anyone confirm whether or not storing instances of a class within a class variable of the same class is an acceptable Python design pattern?

Best Answer

Well, maybe that it's common to do in Python. However it's definitely not common in other languages and not a proper OOP way. (I don't want to start a language war - au contraire, Python is a very good language/environment for some use cases.)

There are times where you absolutely want to have track of instances of particular class. (DI containers, resources which are not managed by a garbage collector (if you have any) ...)

Usually it's solved by implementing a factory pattern. Here is quite a good article about it in Python:

http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Factory.html

When a class instance is created, a reference to it can be put into a collection. There is a glitch, however (when using languages which have garbage collector or smart pointers). When somebody stops using the instance, your register still have a reference to the instance and therefore it's not garbage collected. You have to use either a weak reference (or it's smart-pointer equivalent).

Here is a Python doc for weak reference:

https://docs.python.org/3/library/weakref.html

So the code could look like this:

import weakref

shape_register = weakref.WeakValueDictionary()

class Shape(object):
    # Create based on class name:
    def factory(type):
        #return eval(type + "()")
        if type == "Circle":
            instance = Circle()
            shape_register[id(instance)] = instance
            return instance
        if type == "Square":
            instance = Square()
            shape_register[id(instance)] = instance
            return isinstance

        assert 0, "Bad shape creation: " + type
    factory = staticmethod(factory)

class Circle(Shape):
    def draw(self): print("Circle.draw")
    def erase(self): print("Circle.erase")

class Square(Shape):
    def draw(self): print("Square.draw")
    def erase(self): print("Square.erase")

And the use:

>>> s = Shape.factory("Circle")
>>> shape_register.valuerefs()
[<weakref at 0x00000000037830F8; to 'Circle' at 0x0000000003773C18>]

^ You can see that the Circle instance in in the register.

>>> s = Shape.factory("Circle")
>>> shape_register.valuerefs()
[<weakref at 0x0000000003759F10; to 'Circle' at 0x00000000037737F0>]

^ A new instance was created, you can no longer access the original one. It was garbage collected and doesn't show in the register anymore.

>>> c = Shape.factory("Circle")
>>> shape_register.valuerefs()
[<weakref at 0x0000000003759F10; to 'Circle' at 0x00000000037737F0>, <weakref at 0x00000000037830F8; to 'Circle' at 0x0000000003773C18>]
>>> 

^ However now another circle was created and stored in different variable. Both s and c are now accessible and the register shows two values.