Skip to content

Singleton Design Pattern

Video Lecture

Section Video Links
Singleton Overview Singleton Overview Singleton Overview Singleton Overview 
Singleton Use Case Singleton Use Case Singleton Use Case Singleton Use Case 
Python Dictionary Python Dictionary Python Dictionary Python Dictionary 

Overview

Sometimes you need an object in an application where there is only one instance.

You don't want there to be many versions, for example, you have a game with a score, and you want to adjust it. You may have accidentally created several instances of the class holding the score object. Or, you may be opening a database connection, there is no need to create many, when you can use the existing one that is already in memory. You may want a logging component, and you want to ensure all classes use the same instance. So, every class could declare their own logger component, but behind the scenes, they all point to the same memory address (ID).

By creating a class and following the Singleton pattern, you can enforce that even if any number of instances were created, they will still refer to the original class.

The Singleton can be accessible globally, but it is not a global variable. It is a class that can be instanced at any time, but after it is first instanced, any new instances will point to the same instance as the first.

For a class to behave as a Singleton, it should not contain any references to self but use static variables, static methods and/or class methods.

Singleton UML Diagram

Singleton UML Diagram

Source Code

In the source code, I override the classes __new__ method to return a reference to itself. This then makes the __init__ method irrelevant.

When running the example, experiment with commenting out the __new__ method, and you will see that the IDs of the instances no longer point to the same memory location of the class, but to new memory identifiers instead. The class is no longer a Singleton.

./singleton/singleton_concept.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# pylint: disable=too-few-public-methods
"Singleton Concept Sample Code"
import copy

class Singleton():
    "The Singleton Class"
    value = []

    def __new__(cls):
        return cls

    # def __init__(self):
    #     print("in init")

    @staticmethod
    def static_method():
        "Use @staticmethod if no inner variables required"

    @classmethod
    def class_method(cls):
        "Use @classmethod to access class level variables"
        print(cls.value)

# The Client
# All uses of singleton point to the same memory address (id)
print(f"id(Singleton)\t= {id(Singleton)}")

OBJECT1 = Singleton()
print(f"id(OBJECT1)\t= {id(OBJECT1)}")

OBJECT2 = copy.deepcopy(OBJECT1)
print(f"id(OBJECT2)\t= {id(OBJECT2)}")

OBJECT3 = Singleton()
print(f"id(OBJECT1)\t= {id(OBJECT3)}")

Output

1
2
3
4
5
python ./singleton/singleton_concept.py
id(Singleton)   = 2164775087968
id(OBJECT1)     = 2164775087968
id(OBJECT2)     = 2164775087968
id(OBJECT3)     = 2164775087968

Notes

Variables declared at class level are static variables that can be accessed directly using the class name without the class needing to be instantiated first.

cls is a reference to the class

self is a reference to the instance of the class

_new_ gets called before _init_,

_new_ has access to class level variables

_init_ references self that is created when the class is instantiated

By using _new_, and returning a reference to cls, we can force the class to act as a singleton. For a class to act as a singleton, it should not contain any references to self.

Singleton Use Case

In the example, there are three games created. They are all independent instances created from their own class, but they all share the same leaderboard. The leaderboard is a singleton.

It doesn't matter how the Games where created, or how they reference the leaderboard, it is always a singleton.

Each game independently adds a winner, and all games can read the altered leaderboard regardless of which game updated it.

Example UML Diagram

Singleton Use Case Diagram

Source Code

./singleton/client.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# pylint: disable=too-few-public-methods

"Singleton Use Case Example Code."

from game1 import Game1
from game2 import Game2
from game3 import Game3

# The Client
# All games share and manage the same leaderboard because it is a singleton.
GAME1 = Game1()
GAME1.add_winner(2, "Cosmo")

GAME2 = Game2()
GAME2.add_winner(3, "Sean")

GAME3 = Game3()
GAME3.add_winner(1, "Emmy")

GAME1.leaderboard.print()
GAME2.leaderboard.print()
GAME3.leaderboard.print()

./singleton/game1.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
"A Game Class that uses the Leaderboard Singleton"

from leaderboard import Leaderboard
from interface_game import IGame

class Game1(IGame):  # pylint: disable=too-few-public-methods
    "Game1 implements IGame"

    def __init__(self):
        self.leaderboard = Leaderboard()

    def add_winner(self, position, name):
        self.leaderboard.add_winner(position, name)

./singleton/game2.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
"A Game Class that uses the Leaderboard Singleton"

from leaderboard import Leaderboard
from interface_game import IGame

class Game2(IGame):  # pylint: disable=too-few-public-methods
    "Game2 implements IGame"

    def __init__(self):
        self.leaderboard = Leaderboard()

    def add_winner(self, position, name):
        self.leaderboard.add_winner(position, name)

./singleton/game3.py

1
2
3
4
5
6
"A Game Class that uses the Leaderboard Singleton"

from game2 import Game2

class Game3(Game2):  # pylint: disable=too-few-public-methods
    """Game 3 Inherits from Game 2 instead of implementing IGame"""

./singleton/leaderboard.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
"A Leaderboard Singleton Class"

class Leaderboard():
    "The Leaderboard as a Singleton"
    _table = {}

    def __new__(cls):
        return cls

    @classmethod
    def print(cls):
        "A class level method"
        print("-----------Leaderboard-----------")
        for key, value in sorted(cls._table.items()):
            print(f"|\t{key}\t|\t{value}\t|")
        print()

    @classmethod
    def add_winner(cls, position, name):
        "A class level method"
        cls._table[position] = name

./singleton/interface_game.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# pylint: disable=too-few-public-methods
"A Game Interface"

from abc import ABCMeta, abstractmethod

class IGame(metaclass=ABCMeta):
    "A Game Interface"
    @staticmethod
    @abstractmethod
    def add_winner(position, name):
        "Must implement add_winner"

Output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
python ./singleton/client.py
-----------Leaderboard-----------
|       1       |       Emmy    |
|       2       |       Cosmo   |
|       3       |       Sean    |

-----------Leaderboard-----------
|       1       |       Emmy    |
|       2       |       Cosmo   |
|       3       |       Sean    |

-----------Leaderboard-----------
|       1       |       Emmy    |
|       2       |       Cosmo   |
|       3       |       Sean    |

New Coding Concepts

Python Dictionary

In the file ./singleton/leaderboard.py,

4
5
    "The Leaderboard as a Singleton"
    _table = {}

The {} is indicating a Python Dictionary.

A Dictionary can be instantiated using the curly braces {} or dict()

The Dictionary is similar to a List, except that the items are key:value pairs.

The Dictionary can store multiple key:value pairs, they can be changed, can be added and removed, can be re-ordered, can be pre-filled with key:value pairs when instantiated and is very flexible.

Since Python 3.7, dictionaries are ordered in the same way that they are created.

The keys of the dictionary are unique.

You can refer to the dictionary items by key, which will return the value.

1
2
3
4
PS> python
>>> items = {"abc": 123, "def": 456, "ghi": 789}
>>> items["abc"]
123

You can change the value at a key,

1
2
3
4
5
PS> python
>>> items = {"abc": 123, "def": 456, "ghi": 789}
>>> items["def"] = 101112
>>> items["def"]
101112

You can add new key:value pairs, and remove them by using the key.

1
2
3
4
5
6
7
8
9
PS> python
>>> items = {"abc": 123, "def": 456, "ghi": 789}
>>> items["jkl"] = 101112
>>> items["jkl"]
101112
>>> items.pop('def')
456
>>> items
{'abc': 123, 'ghi': 789, 'jkl': 101112}

Furthermore, you can order a dictionary alphabetically by key

1
2
3
4
5
6
PS> python
>>> items = {"abc": 123, "ghi": 789, "def": 456}
>>> items
{'abc': 123, 'ghi': 789, 'def': 456}
>>> dict(sorted(items.items()))
{'abc': 123, 'def': 456, 'ghi': 789}

Summary

  • To be a Singleton, there must only be one copy of the Singleton, no matter how many times, or in which class it was instantiated.
  • You want the attributes or methods to be globally accessible across your application, so that other classes may be able to use the Singleton.
  • You can use Singletons in other classes, as I did with the leaderboard, and they will all use the same Singleton regardless.
  • You want controlled access to a sole instance.
  • For a class to act as a singleton, it should not contain any references to self.