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