8.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(Singleton, cls).__new__(cls, *args, **kwargs)
...         return cls._instance
...
>>> s1 = Singleton()
>>> s2 = Singleton()
>>> print(s1 is s2)
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.

8.1.1. Pattern

  • To ensure a class has a single instance

  • Database connection pool

  • HTTP Gateway

  • Settings

  • Main game/program window

../../_images/designpatterns-singleton-pattern.png

8.1.2. Problem

../../_images/designpatterns-singleton-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'

8.1.3. Solution

../../_images/designpatterns-singleton-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

8.1.4. Use Case - 0x01

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

8.1.5. Use Case - 0x02

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

8.1.6. Use Case - 0x03

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

8.1.7. Assignments

Code 8.51. Solution
"""
* Assignment: DesignPatterns Creational SingletonSettings
* Complexity: easy
* Lines of code: 7 lines
* Time: 5 min

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

Polish:
    TODO: Polish translation

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> 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


Code 8.52. Solution
"""
* Assignment: DesignPatterns Creational SingletonDatabaseConnection
* Complexity: easy
* Lines of code: 7 lines
* Time: 5 min

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

Polish:
    TODO: Polish translation

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> 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


Code 8.53. Solution
"""
* Assignment: DesignPatterns Creational SingletonQueue
* Complexity: medium
* Lines of code: 7 lines
* Time: 5 min

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

Polish:
    TODO: Polish translation

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> 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


Code 8.54. Solution
"""
* Assignment: DesignPatterns Creational SingletonLogger
* Complexity: medium
* Lines of code: 7 lines
* Time: 5 min

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

Polish:
    TODO: Polish translation

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from pprint import pprint

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

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

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


class Singleton(type):
    pass