7.1. Singleton

  • EN: Singleton

  • PL: Singleton

  • Type: object

The Singleton design pattern is a creational design pattern that ensures a class has only one instance, and provides a global point of access to it.

Here's a simple example of the Singleton pattern in Python:

>>> class Singleton:
...     _instance = None
...
...     def __new__(cls, *args, **kwargs):
...         if not cls._instance:
...             cls._instance = super().__new__(cls, *args, **kwargs)
...         return cls._instance
...
>>> a = Singleton()
>>> b = Singleton()
>>>
>>> print(a is b)
True

In this example, Singleton is a class that overrides the __new__ method to control the creation of instances. The __new__ method checks if an instance already exists. If not, it creates a new instance and assigns it to the _instance class attribute. If an instance already exists, it returns the existing instance. This ensures that there is only one instance of the Singleton class.

7.1.1. Why?

  • To ensure a class has a single instance

  • Database connection pool

  • HTTP Gateway

  • Settings

  • Main game/program window

7.1.2. Pattern

../../../_images/pattern.png

7.1.3. Problem

  • I want to have only one place to store configuration

  • So that if I change something, this will be visible everywhere

  • If there are multiple instances, each instance has it's own state

>>> class Config:
...     host = '127.0.0.1'
...     port = 5432
...     username = 'mwatney'
...     password = 'Ares3'
...     schema = 'nasa'
>>>
>>>
>>> conf1 = Config()
>>> conf2 = Config()
>>>
>>> conf1 is conf2
False
>>> conf1  
<__main__.Config object at 0x1076d57f0>
>>>
>>> conf2  
<__main__.Config object at 0x1076f2710>

7.1.4. Solution

>>> class Singleton:
...     _instance = None
...
...     def __new__(cls, *args, **kwargs):
...         if cls._instance is None:
...             cls._instance = super().__new__(cls)
...             cls._instance.__init__(*args, **kwargs)
...         return cls._instance
>>>
>>>
>>> class Config(Singleton):
...     host = '127.0.0.1'
...     port = 5432
...     username = 'mwatney'
...     password = 'Ares3'
...     schema = 'nasa'
>>>
>>> conf1 = Config()
>>> conf2 = Config()
>>>
>>> conf1 is conf2
True
>>> conf1  
<__main__.Config object at 0x1076d56a0>
>>>
>>> conf2  
<__main__.Config object at 0x1076d56a0>

7.1.5. Solution: __new__

>>> class Singleton:
...     _instance = None
...
...     def __new__(cls, *args, **kwargs):
...         if cls._instance is None:
...             cls._instance = super().__new__(cls)
...             cls._instance.__init__(*args, **kwargs)
...         return cls._instance

7.1.6. Solution: @classmethod

>>> class Singleton:
...     _instance = None
...
...     @classmethod
...     def get_instance(cls):
...         if cls._instance is None:
...             cls._instance = super().__new__(cls)
...         return cls._instance

7.1.7. Solution: Metaclass

>>> class Singleton(type):
...     _instance = None
...
...     def __call__(cls, *args, **kwargs):
...         if cls._instance is None:
...             cls._instance = object.__new__(cls)
...             cls._instance.__init__(*args, **kwargs)
...         return cls._instance

7.1.8. Case Study

>>> class DatabaseConnectionPool(Singleton):
...     pass

7.1.9. Use Case - 1

from typing import Self


class Database:
    connection: Self | None = None

    @classmethod
    def connect(cls):
        if not cls.connection:
            cls.connection = ...  # connect to database
        return cls.connection


db1 = Database.connect()
db2 = Database.connect()

db1 is db2
# True

7.1.10. Use Case - 2

from typing import Self


class Singleton:
    instance: Self | None = None

    def __new__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = object.__new__(cls)
            cls.instance.__init__(*args, **kwargs)
        return cls.instance


class Database(Singleton):
    pass


db1 = Database()
db2 = Database()

db1 is db2
# True

7.1.11. Use Case - 3

class Singleton(type):
    instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls.instances:
            cls.instances[cls] = super().__call__(*args, **kwargs)
        return cls.instances[cls]


class Database(metaclass=Singleton):
    pass

class Settings(metaclass=Singleton):
    pass


db1 = Database()
db2 = Database()

db1 is db2
# True

7.1.12. Use Case - 4

Problem:

../../../_images/usecase4-problem.png
from typing import Any


class ConfigManager:
    settings: dict[str, Any]

    def __init__(self) -> None:
        self.settings = {}

    def set(self, key: str, value: Any) -> None:
        self.settings[key] = value

    def get(self, key: str) -> Any:
        return self.settings.pop(key)


if __name__ == '__main__':
    manager = ConfigManager()
    manager.set('name', 'Mark')

    other = ConfigManager()
    print(other.get('name'))
    # Traceback (most recent call last):
    # KeyError: 'name'

Solution:

../../../_images/usecase4-solution.png
from __future__ import annotations
from typing import Any


class ConfigManager:
    settings: dict[str, Any]
    instance: ConfigManager | None = None

    def __init__(self) -> None:
        self.settings = {}

    @classmethod
    def get_instance(cls) -> ConfigManager:
        if not cls.instance:
            cls.instance = super().__new__(cls)
            cls.instance.__init__()
        return cls.instance

    def set(self, key: str, value: Any) -> None:
        self.settings[key] = value

    def get(self, key: str) -> Any:
        return self.settings.pop(key)


if __name__ == '__main__':
    manager = ConfigManager.get_instance()
    manager.set('name', 'Mark')

    other = ConfigManager.get_instance()
    print(other.get('name'))
    # Mark

7.1.13. Assignments

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: DesignPatterns Creational SingletonSettings
# - Difficulty: easy
# - Lines: 7
# - Minutes: 5

# %% English
# 1. Create singleton class `Settings`
# 2. Use `get_instance()` classmethod
# 3. Run doctests - all must succeed

# %% Polish
# 1. Stwórz klasę singleton `Settings`
# 2. Użyj metody klasy `get_instance()`
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from pprint import pprint

>>> result_a = Settings.get_instance()
>>> result_b = Settings.get_instance()

>>> result_a is result_b
True
"""
from typing import Self


class Settings:
    pass


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: DesignPatterns Creational SingletonDatabaseConnection
# - Difficulty: easy
# - Lines: 7
# - Minutes: 5

# %% English
# 1. Create singleton class `Database`
# 2. Use `connect()` classmethod
# 3. Run doctests - all must succeed

# %% Polish
# 1. Stwórz klasę singleton `Database`
# 2. Użyj metody klasy `connect()`
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from pprint import pprint

>>> result_a = Database.connect()
>>> result_b = Database.connect()

>>> result_a is result_b
True
"""
from typing import Self


class Database:
    pass


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: DesignPatterns Creational SingletonQueue
# - Difficulty: medium
# - Lines: 7
# - Minutes: 5

# %% English
# 1. Create singleton class `Singleton`
# 2. Use `__new__()` object constructor
# 3. Run doctests - all must succeed

# %% Polish
# 1. Stwórz klasę singleton `Singleton`
# 2. Użyj konstruktora obiektu `__new__()`
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from pprint import pprint

>>> class Queue(Singleton):
...     pass

>>> result_a = Queue()
>>> result_b = Queue()

>>> result_a is result_b
True
"""
from typing import Self


class Singleton:
    pass


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: DesignPatterns Creational SingletonLogger
# - Difficulty: medium
# - Lines: 7
# - Minutes: 5

# %% English
# 1. Create singleton class `Singleton`
# 2. Use `Metaclass`
# 3. Run doctests - all must succeed

# %% Polish
# 1. Stwórz klasę singleton `Singleton`
# 2. Użyj `Metaclass`
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from pprint import pprint

>>> class Logger(metaclass=Singleton):
...     pass

>>> result_a = Logger()
>>> result_b = Logger()

>>> result_a is result_b
True
"""

class Singleton(type):
    pass