Singleton pattern

By Martin McBride, 2021-08-31
Tags: creational pattern singleton
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 type S.
  • 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 variable LOGGING, it would be possible for buggy code to reassign LOGGING to a brand new Logging object, which might cause log messages to be lost. With a singleton, it is impossible for there to ever be a second Logging 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

If you found this article useful, you might be interested in the book NumPy Recipes or other books by the same author.

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