4

I was suggested to define a singleton in Python in the following way:

class Controller:
    pass
    # ...

controller = Controller()

However, this way, controller cannot be replaced with a derived class.

What are other sensible ways to define singletons in Python Django?

Maybe, when defining a reusable app, I should not define the singleton instance right in the app, but define it in the program which uses it? What are advantages and disadvantages of doing this?

5
  • Am I helping you with my answer? What's missing, if anything?
    – Aaron Hall
    Oct 15, 2016 at 17:42
  • @AaronHall You have a description of traditional singleton pattern in general and how it can be used in Python. I ask about the (possibly better) Python's way to do this, namely defining a singleton object as a module-scoped variable of our class. I ask whether my way is good and how to make it better (and I see not need to make traditional singleton class in the way you describe, because my way seems better). I ask how to make my way even better
    – porton
    Oct 15, 2016 at 17:53
  • 1
    By what criteria is your method better? If anything, I think it's worse - you can't import your module without creating your singleton. Is that necessary?
    – Aaron Hall
    Oct 15, 2016 at 18:35
  • My way is more concise. I suggest the modification of the way: The using project (not the reusable app itself) defines an instance. (It is equivalent to using your "factory".) Well, importing my real module without the class's singleton does not make sense
    – porton
    Oct 15, 2016 at 21:33
  • It usually doesn't make sense - until it suddenly does. You want your code to have minimal changes when you adapt it to other purposes. For example, are you running unit tests? Are you dynamically generating documentation? I would try to minimize what your code does on import.
    – Aaron Hall
    Oct 15, 2016 at 21:46

1 Answer 1

7

Using Singletons in Python

What is a singleton?

A singleton is an object of which there is only one, semantically, in memory.

Builtin singletons

None is a frequently used singleton. NotImplemented is another, much less used. We can test for singleton identity:

>>> maybe_None = None

And then

maybe_None is None

returns True.

Creating a semantic singleton

You can just use an object instance to create one that needs no functionality in a module's global namespace (all caps, because it's a global constant):

SINGLETON = object() 

Be careful not to overwrite the object on the global namespace. If you give it a good name, it should be obvious when someone is doing something wrong with it.

This is similar to the example you gave. However, I wouldn't do what you're doing if creating the object is expensive - it makes it so that it's expensive to import the module, even if you don't want to use the object.

Why you wouldn't want a global singleton on import

Where you would not want it to have a singleton created on import would be any time you want to inexpensively import the module.

You want your modules to be inexpensive to import when dynamically generating documentation, for example.

Another case where you should prefer inexpensive imports are for running unittests.

Also, the method you use does not support inheritance.

So I'm going to show you some ways to work around this.

A singleton instance

Where you only want to have one instance of a class, you can do this in various ways:

customize __new__ and use a class attribute, instance

class Controller(object):
    instance = None
    def __new__(cls):
        if cls.instance is not None:
            return cls.instance
        else:
            inst = cls.instance = super().__new__(cls)
            return inst

And note that this supports inheritance.

memoize a factory function

You can create a function attribute to memoize the function

def get_controller():
    if hasattr(get_controller, 'instance'):
        return get_controller.instance
    else:
        inst = get_controller.instance = Controller()
        return inst

You could also use the module or class namespace to keep the instance in - since the function and the module should both be singletons as well.

Or use lru_cache (since get_controller takes no arguments) to memoize it.

from functools import lru_cache

@lru_cache
def get_controller():
    return Controller()

Note that if you add arguments to get_controller, lru_cache will return a singleton for each unique combination of arguments, up to its cache size (theoretically), after which it forgets instances, so be careful with this if you add arguments - probably better to implement your own behavior, as I did with the function attribute example.

This can support inheritance, for example.

@lru_cache
def get_controller(controller_type): 
    if issubclass(controller_type, Controller):
        return controller_type()
    else:
        raise TypeError('must supply a Controller type')

Not the answer you're looking for? Browse other questions tagged or ask your own question.