Singleton pattern
Categories: design pattern
A singleton is an object for which there is only one instance in the entire system. Singleton is a creational design pattern.
Examples of potential singletons include:
- A logging system. Various parts of the system might need to write log messages as the system executes. These will normally need to be written out to the same file, so you will normally only want a single logger object that the whole system uses.
- A database connection. The system might use a single database for storing all of its data, and you might need to access that database connection from many places in your code.
- A device driver. A manufacturing system, for example, might be controlling a robotic arm. Any parts of the software that generate control instructions for the device will need to send those instructions to the device driver.
Generally, a singleton can be useful wherever you have a single object that needs to be accessed from many different places in the software.
Singleton pattern
The singleton pattern affects the way the object is constructed.
Normally, if you have a class A
, then each time you call the constructor, it will return a new object of type A
.
With a singleton class S
:
- The first time you call the constructor, it will return a new object
x
of typeS
. - If you call the constructor again, one or more times, it will always return the same object
x
.
This means that there will only ever be one S
object created, and it will be created the first time your code calls the constructor.
Taking the example of the logger, then each time your code needs a logger, it will simply create one:
logger = Logger()
logger.error(...)
Even if this code is deep down in some low-level class, you can just create a Logger
whenever you need one. Due to the singleton pattern, a new Logger
will only be created the very first time you call the constructor, wherever in your code that might be. Every time after that, the constructor will simply return the original Logger
object. There will only ever be one Logger
.
Alternatives
There are a couple of alternatives, that don't involve using the singleton pattern.
The first is just to create a global Logger
object in a top-level module of your code (let's call it the globals
module).
LOGGER = Logger()
You can then import this object and use it anywhere in our code:
from globals import LOGGER
LOGGER.error(...)
This isn't completely unreasonable in the context of a "special case" like logging. The main arguments against this are:
- The logging object is baked into all your functions. If you want to unit test a class, you can't isolate it from the logging module.
- Global variables are generally a bad thing. If you do it for logging because it is a special case, the bad practice might proliferate into other areas.
The other alternative is to implement Logger
as a normal class. In your code, you would then create a single Logger
object that you could pass into every other object that needs it.
This has the advantage that your code is not using global variables. It also means that you can apply unit tests to your classes in isolation (you can pass a dummy Logger
into a class when it is being tested, which simply discards messages).
The disadvantage is that almost every class might use logging, so you will need to pass a Logger
object into almost every object you ever create.
Criticism
The singleton pattern is often criticised as being a global variable in disguise. It indeed shares some of the disadvantages of global variables:
- The state of the singleton is shared globally. Any part of the code can alter the state of the object at any time, which can cause bugs that only happen in very specific circumstances. These types of bugs can be difficult to detect and often difficult to recreate when trying to solve them.
- It can make unit testing difficult.
Care is also required if the system uses concurrent processing (see later).
Still, using a singleton has several advantages compared to a global variable:
- If we had a global
Logging
object, stored in the variableLOGGING
, it would be possible for buggy code to reassignLOGGING
to a brand newLogging
object, which might cause log messages to be lost. With a singleton, it is impossible for there to ever be a secondLogging
object. - The singleton doesn't directly encourage the use of other globals in the code.
- Since the codebase always creates a
Logging
object when it needs one, it leaves open the possibility open of switching to an alternative implementation later.
Even so, the singleton pattern is regarded as an anti-pattern by some, and should probably not be used where there are viable alternatives.
Implementations
We will look at a couple of implementations:
- Making a simple class act as a singleton.
- A singleton base class implementation.
- An alternative using a factory method.
Simple singleton class
Let's start with a simple logger class:
class Logger():
def message(self, str):
# Log the message
print(str)
a = Logger()
b = Logger()
print(a is b) # False
This simple logger class only has one method, message
, that we can call to write a message to the log. In this implementation, it just prints the message, but in a real implementation, we might write the message to a file.
This class isn't a singleton yet, it is just an ordinary class. Each time we call Logger()
we get a brand new logger object. So in the example, a
and b
are different objects.
That isn't what we want, of course. We would like the first call to Logger()
to create a new Logger
object, but all subsequent calls to return the same object. How do we do that?
We normally use the __init__
function to initialise an object when it is created. However, there is a different function that is more useful in this case, the __new__
function. This is the function that actually creates the object, and Python calls it immediately before calling __init__
whenever it creates a new object. Here is the default implementation of __new__
:
def __new__(cls, *args, **kwargs):
return super(Logger, cls).__new__(cls, *args, **kwargs)
The __new__
function doesn't have a self
parameter, because it is called before the new object has even been created. It has a cls
parameter that holds an instance of the class object. It also has *args
and **kwargs
parameters that contain any arguments passed into the constructor.
In the default implementation, we simply call the __new__
method of the superclass object
.
All classes inherit object
. If a class does not specify a base class (which Logger
does not), that means it is directly derived from object
.
If we want to force Logger
to only ever create one unique instance, we need to modify the __nerw__
method like this:
- The first time
__new__
is called, it creates a new object and stores it. - If
__new__
is called again, it just returns the stored object.
Here is the code for the complete singleton:
class Logger():
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Logger, cls).__new__(cls, *args, **kwargs)
return cls._instance
def message(self, str):
# Log the message
print(str)
a = Logger()
b = Logger()
print(a is b) # True
Here we have a class variable _instance
, that is initially None
.
In the __new__
method, when the first Logger
is created, a new object is created and stored in _instance
. When Logger
is called again, the method just returns the previously created object. This means that the constructor will always return the same object. When we call Logger()
twice and save the result in a
and b
, they will both be the same object.
This is a reasonable implementation of a simple singleton.
Initialising the logger
If we were creating a real logger, we would probably need to initialise it with some parameters. For example, if we were logging to file, we might want to pass in a filename. We might normally do this by adding a parameter to the __init__
method, like this:
def initialize(self, filename):
self.filename = filename
The only problem with this is that you need to supply a filename every time you use the constructor.
a = Logger('test.log')
b = Logger('test.log')
Since the whole point of the logging singleton is that we should be able to call it easily from anywhere in our code, having to supply a filename each time is a bit of a pain. And there is also the question of what happens (or indeed what should happen) if we accidentally provide a different parameter somewhere in the depths of our code.
It is far easier to use a separate initialize
method to set the parameters. By convention this method is only called once, right at the start of our code:
class Logger():
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Logger, cls).__new__(cls, *args, **kwargs)
return cls._instance
def initialize(self, filename):
self.filename = filename
def message(self, str):
print(str)
Logger().initialize('test.log')
a = Logger()
b = Logger()
print(a is b)
print(a.filename)
Here, after initialising the Logger
, we can just call it in the usual way.
Using a singleton base class
A slight problem with the previous class is that the singleton functionality and the logger functionality are both implemented in the same class. It would be nice to separate the singleton implementation into a separate class. This simplifies the logger class, and also makes it easier to re-use the singleton implementation if we ever needed a different singleton.
It is quite easy to separate the functionality:
class Singleton():
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
# Inherits Singleton
class Logger(Singleton):
def message(self, str):
print(str)
The Singleton
class ensures that only one instance is created. The Logger
class performs the logging and inherits the singleton behaviour from the Singleton
class.
We could also create another singleton class, say a database access class, like this:
class Database(Singleton):
def query(self, str):
# Perform the query
print(str)
This class inherits the same singleton behaviour as Logger
because it also derives from the Singleton
class.
The variable _instance
is a class variable, meaning that there is one variable that is shared between every object in that class. However, even though the class variable is declared in the Singleton
class, that doesn't mean that Logger
and Database
share the same variable. Every class derived from Singleton
has its own private version of _instance
. For example:
a = Logger()
b = Logger()
c = Database()
d = Database()
print(a is b) # True
print(c is d) # True
print(a is c) # False
This code creates a single Logger
object and a single Database
object.
Using a factory method
An alternative approach is to use a factory method rather than a singleton. Here is how it works:
class Logger():
def message(self, str):
# Log the message
print(str)
def get_logger(logger=Logger()):
return logger
a = get_logger()
b = get_logger()
print(a is b) # True
Here the Logger
class is just a simple class that makes no attempt to do anything clever.
However, the Logger
class should never be used directly to create a logger object. Instead, the function get_logger
should be used. This function has a logger
parameter with a default value Logger()
.
This works because in Python the default argument is only evaluated once when the function is defined. So each time you call get_logger
you will always receive the same logger object.
This system relies on a convention: you must always use get_logger
, and never call Logger()
directly. If you use this technique, you might consider giving the Logger
class a different name, that discourages direct use, such as _Logger
.
Handling multi-threading
All the techniques above assume a single-threaded environment. If you are using multiple threads in your code, there is a potential problem. It occurs in the __new__
method:
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Logger, cls).__new__(cls, *args, **kwargs)
return cls._instance
Consider the situation where no instance has been created, so _instance
is still set to None
. Now imagine that thread A and thread B both attempt to create an object at the same time.
Let's assume thread A executes __new__
first. It checks _instance
, finds it is None
, and proceeds to create a new instance.
Now if thread B also executes __new__
, before thread A has finished. Thread B will see that _instance
is still None
and will also decide to create a new instance.
Thread A finishes creating its object and returns it. Soon afterwards, thread B finishes creating its object and returns it. Now the system has two different objects, but all the code has been written assuming there is only one object.
This is potentially a very nasty bug. It might not happen every time, in fact it might even be quite rare. But it could have a devastating effect when it does occur.
There are two ways to avoid this. The first is to make absolutely certain that only one thread is active when the instance is created. This is quite a good solution if the software is initially single-threaded and adds new threads after startup.
The other solution is to place a lock on __new__
so that only one thread at a time can run it.
We won't look at those solutions in detail here, but it is something to be aware of it using singletons in a multi-threaded environment.
See also
Join the PythonInformer Newsletter
Sign up using this form to receive an email when new content is added:
Popular tags
2d arrays abstract data type alignment and angle animation arc array arrays bar chart bar style behavioural pattern bezier curve built-in function callable object chain circle classes clipping close closure cmyk colour combinations comparison operator comprehension context context manager conversion count creational pattern data science data types decorator design pattern device space dictionary drawing duck typing efficiency ellipse else encryption enumerate fill filter font font style for loop formula function function composition function plot functools game development generativepy tutorial generator geometry gif global variable gradient greyscale higher order function hsl html image image processing imagesurface immutable object in operator index inner function input installing iter iterable iterator itertools join l system lambda function latex len lerp line line plot line style linear gradient linspace list list comprehension logical operator lru_cache magic method mandelbrot mandelbrot set map marker style matplotlib monad mutability named parameter numeric python numpy object open operator optimisation optional parameter or pandas partial application path pattern permutations pie chart pil pillow polygon pong positional parameter print product programming paradigms programming techniques pure function python standard library radial gradient range recipes rectangle recursion reduce regular polygon repeat rgb rotation roundrect scaling scatter plot scipy sector segment sequence setup shape singleton slice slicing sound spirograph sprite square str stream string stroke structural pattern subpath symmetric encryption template tex text text metrics tinkerbell fractal transform translation transparency triangle truthy value tuple turtle unpacking user space vectorisation webserver website while loop zip zip_longest