Этот шаблон помогает ограничить создание только одного экземпляра класса. Это полезно в тех случаях, когда… вы правильно догадались — требуется только один экземпляр класса, например, один экземпляр класса базы данных, который будет использоваться везде.

Есть много способов создать Singleton в Python:

  1. Распределитель синглтона
  2. Одиночный декоратор
  3. Метакласс синглтона
  4. моногосударство

Распределитель синглтона

class Singleton:
    _instance = None
    def __init__(self):
        print("I am being called.")
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super(Singleton, cls)\
                .__new__(cls, *args, **kwargs)
        return cls._instance

if __name__ == '__main__':
    s1 = Singleton()
    s2 = Singleton()
    print(s1 == s2)


# OUTPUT
# I am being called.
# I am being called.
# True

Сначала вызывается метод __new__, и он создает экземпляр класса только в том случае, если он еще не существует.

Но после этого, если есть метод __init__, он вызывается.

Таким образом, при каждом вызове Singleton() также будет вызываться __init__, и, таким образом, мы видим две строки, напечатанные как «Мне звонят».

Эту проблему можно решить другими способами создания синглтона.

Синглтон Декоратор

def singleton(class_): # pass the class for which singleton instance needed.
    singletons = {}

    def get_instance(*args, **kwargs):
        if class_ not in singletons:
            singletons[class_] = class_(*args, **kwargs) # creates object
        return singletons[class_]
    return get_instance

@singleton
class Singleton:
    def __init__(self):
        print('Getting called.')

if __name__ == '__main__':
    s1 = Singleton()
    s2 = Singleton()
    print(s1 == s2)

# OUTPUT
# Getting called.
# True

В этом примере мы создали декоратор под названием singleton. Любой класс, который мы хотим сделать одноэлементным, может быть просто аннотирован этим.

Это сэкономило нам усилия при написании одного и того же метода __new__ для каждого создаваемого синглтона, а также решило проблему с распределителем синглтона. Теперь мы видим, что __init__ вызывается только один раз.

Синглтон Метакласс

class SingleType(type):
    _instances = {}

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


class Singleton(metaclass=SingleType):
    def __init__(self):
        print('Getting called.')

if __name__ == '__main__':
    s1 = Singleton()
    s2 = Singleton()
    print(s1 == s2)

моногосударство

class Singleton:
    __state = {
        'name':'Unknown',
        'age':21
    }

    def __init__(self):
        self.__dict__ = self.__state

    def __str__(self):
         return f'The person with name {self.name} is {self.age} years old'

if __name__ == '__main__':
    s1 = Singleton()
    s2 = Singleton()
    print(s1)
    print(s2)

# OUTPUT
# The person with name Unknown is 21 years old
# The person with name Unknown is 21 years old

Мы создали состояние класса с некоторыми значениями по умолчанию, и в __init__ мы передаем ссылку на это __state атрибуту __dict__. Это сохранит значения переменных одинаковыми, независимо от того, сколько раз вызывается этот класс. Здесь важно отметить, что s1 и s2 не являются одними и теми же объектами, но мы манипулируем атрибутами, которые они хранят, чтобы они были равны по значению. Я не буду считать это синглтоном, но выбор за вами ;)

Я имею в виду это, вы тоже можете -

https://www.udemy.com/course/design-patterns-python/

Следуйте моему списку шаблонов дизайна Python, чтобы узнать больше — https://medium.com/@rjrichajain00/list/python-design-patterns-4dbd16f87858

Я все еще учусь этому, наверное, как и вы. Давайте обсудим и расширим наше понимание этих удивительных закономерностей. Пожалуйста, оставьте комментарий, если у вас есть вопрос / предложение / исправление. До скорой встречи :)