From 308cf47b4832f0a680c3f9423a4cbf468686f57d Mon Sep 17 00:00:00 2001 From: Andrii Liekariev Date: Sat, 6 Jun 2020 00:40:42 +0300 Subject: [PATCH] fix Singleton inheritance - Fix the previous behavior of Singleton, when the same instance is returned for each possible subclass. Now one instance for each subclass. - Add an explanation about possible changes to the value of the `__init__` argument. - Remove `from __future__ import annotations` because of Python3.7+ requirements declared in `README`. - Fix according to PEP-8 (80 symbols). --- .../Conceptual/NonThreadSafe/main.py | 27 +++++++++++-------- src/Singleton/Conceptual/ThreadSafe/main.py | 27 ++++++++++++------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/Singleton/Conceptual/NonThreadSafe/main.py b/src/Singleton/Conceptual/NonThreadSafe/main.py index 0ee9c1d..f1fd8b3 100644 --- a/src/Singleton/Conceptual/NonThreadSafe/main.py +++ b/src/Singleton/Conceptual/NonThreadSafe/main.py @@ -2,19 +2,16 @@ EN: Singleton Design Pattern Intent: Lets you ensure that a class has only one instance, while providing a -global access point to this instance. +global access point to this instance. One instance per each subclass (if any). RU: Паттерн Одиночка Назначение: Гарантирует, что у класса есть только один экземпляр, и -предоставляет к нему глобальную точку доступа. +предоставляет к нему глобальную точку доступа. У каждого наследника класса тоже +будет по одному экземпляру. """ -from __future__ import annotations -from typing import Optional - - class SingletonMeta(type): """ EN: The Singleton class can be implemented in different ways in Python. Some @@ -26,12 +23,20 @@ class SingletonMeta(type): метаклассом, поскольку он лучше всего подходит для этой цели. """ - _instance: Optional[Singleton] = None + _instances = {} - def __call__(self) -> Singleton: - if self._instance is None: - self._instance = super().__call__() - return self._instance + def __call__(cls, *args, **kwargs): + """ + EN: Possible changes to the value of the `__init__` argument do not + affect the returned instance. + + RU: Данная реализация не учитывает возможное изменение передаваемых + аргументов в `__init__`. + """ + if cls not in cls._instances: + instance = super().__call__(*args, **kwargs) + cls._instances[cls] = instance + return cls._instances[cls] class Singleton(metaclass=SingletonMeta): diff --git a/src/Singleton/Conceptual/ThreadSafe/main.py b/src/Singleton/Conceptual/ThreadSafe/main.py index f79eb69..9dfbd1c 100644 --- a/src/Singleton/Conceptual/ThreadSafe/main.py +++ b/src/Singleton/Conceptual/ThreadSafe/main.py @@ -2,18 +2,16 @@ EN: Singleton Design Pattern Intent: Lets you ensure that a class has only one instance, while providing a -global access point to this instance. +global access point to this instance. One instance per each subclass (if any). RU: Паттерн Одиночка Назначение: Гарантирует, что у класса есть только один экземпляр, и -предоставляет к нему глобальную точку доступа. +предоставляет к нему глобальную точку доступа. У каждого наследника класса тоже +будет по одному экземпляру. """ - -from __future__ import annotations from threading import Lock, Thread -from typing import Optional class SingletonMeta(type): @@ -23,7 +21,7 @@ class SingletonMeta(type): RU: Это потокобезопасная реализация класса Singleton. """ - _instance: Optional[Singleton] = None + _instances = {} _lock: Lock = Lock() """ @@ -35,6 +33,13 @@ class SingletonMeta(type): """ def __call__(cls, *args, **kwargs): + """ + EN: Possible changes to the value of the `__init__` argument do not + affect the returned instance. + + RU: Данная реализация не учитывает возможное изменение передаваемых + аргументов в `__init__`. + """ # EN: Now, imagine that the program has just been launched. # Since there's no Singleton instance yet, multiple threads can # simultaneously pass the previous conditional and reach this @@ -63,9 +68,10 @@ def __call__(cls, *args, **kwargs): # экземпляр одиночки уже будет создан и поток не сможет # пройти через это условие, а значит новый объект не будет # создан. - if not cls._instance: - cls._instance = super().__call__(*args, **kwargs) - return cls._instance + if cls not in cls._instances: + instance = super().__call__(*args, **kwargs) + cls._instances[cls] = instance + return cls._instances[cls] class Singleton(metaclass=SingletonMeta): @@ -101,7 +107,8 @@ def test_singleton(value: str) -> None: # RU: Клиентский код. print("If you see the same value, then singleton was reused (yay!)\n" - "If you see different values, then 2 singletons were created (booo!!)\n\n" + "If you see different values, " + "then 2 singletons were created (booo!!)\n\n" "RESULT:\n") process1 = Thread(target=test_singleton, args=("FOO",))