Using Singletons in Python
What is a singleton?
A singleton is an object of which there is only one, semantically, in memory.
None is a frequently used singleton.
NotImplemented is another, much less used. We can test for singleton identity:
>>> maybe_None = None
maybe_None is None
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:
__new__ and use a class attribute,
instance = None
if cls.instance is not None:
inst = cls.instance = super().__new__(cls)
And note that this supports inheritance.
memoize a factory function
You can create a function attribute to memoize the function
if hasattr(get_controller, 'instance'):
inst = get_controller.instance = Controller()
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
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.
if issubclass(controller_type, Controller):
raise TypeError('must supply a Controller type')