4.1. Singleton

  • To ensure a class has a single instance

  • Database connection pool

  • HTTP Gateway

  • Settings

  • Main game/program window

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.

4.1.1. 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>

4.1.2. 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>

4.1.3. 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

4.1.4. Solution: @classmethod

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

4.1.5. 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

4.1.6. Case Study

Problem:

>>> 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)
>>>
>>>
>>> manager = ConfigManager()
>>> manager.set('name', 'Mark')
>>>
>>> other = ConfigManager()
>>> print(other.get('name'))
Traceback (most recent call last):
KeyError: 'name'

Solution:

>>> 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)
>>>
>>>
>>> manager = ConfigManager.get_instance()
>>> manager.set('name', 'Mark')
>>>
>>> other = ConfigManager.get_instance()
>>> print(other.get('name'))
Mark

Diagram:

../../_images/singleton-casestudy-solution.png

4.1.7. Use Case - 1

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

4.1.8. Use Case - 2

>>> 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

4.1.9. Use Case - 3

>>> 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

4.1.10. Use Case - 4

>>> 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

4.1.11. Assignments

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

# %% 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

# %% 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ść

# %% Doctests
"""
>>> 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
"""

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

# %% Imports
from typing import Self

# %% Types

# %% Data

# %% Result
class Settings:
    ...

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

# %% 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

# %% 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ść

# %% Doctests
"""
>>> 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
"""

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

# %% Imports
from typing import Self

# %% Types

# %% Data

# %% Result
class Database:
    ...

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

# %% 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

# %% 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ść

# %% Doctests
"""
>>> 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
"""

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

# %% Imports
from typing import Self

# %% Types

# %% Data

# %% Result
class Singleton:
    ...

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

# %% 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

# %% 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ść

# %% Doctests
"""
>>> 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
"""

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

# %% Imports

# %% Types

# %% Data

# %% Result
class Singleton(type):
    ...