Паттерн Singleton

Опубликовано 12 October 2015 в Python

Singleton не так часто встречается в коде на питоне. Есть большая вероятность, что если потребовался синглтон, то в проекте есть проблемы с архитектурой. В первую очередь это касается небольших и средних проектов. Да и в больших проектах синглтон в чистом виде встретишь не часто.

Когда я готовился к этой статье, я планировал написать статью о вариантах реализации паттерна. Но когда я начал исследовать, первым делом я наткнулся на следующий код в Википедии:

class Singleton:
    def __new__(cls):
        if not hasattr(cls, 'instance'):
             cls.instance = super(Singleton, cls).__new__(cls)
        return cls.instance
>>> a = Singleton()
>>> b = Singleton()
>>> a is b
True

Код, казалось бы, рабочий. Но это не так. Во первых, если нужны разные синглтоны, то придется наследоваться от этого класса. Это вполне нормальное требование для проекта с более или менее сложной логикой. И по моему опыту, если в проекте завелся синглтон, то жди целый выводок синглтонов-наследников. Так вот наследование сломано начисто.

class NewSingleton(Singleton):
    pass
>>> c = NewSingleton()
>>> c is a
True
>>> type(c)
__main__.Singleton

Если повезет и будет создан экземпляр в базовом классе, то просто мы получим синглтон базового класса. А если первым будет создан экземпляр наследника... То ничего мы не получим. В новой сессии восстановим иерархию:

class Singleton:
    def __new__(cls):
        if not hasattr(cls, 'instance'):
             cls.instance = super(Singleton, cls).__new__(cls)
        return cls.instance


class NewSingleton(Singleton):
    pass

И попробуем создать экземпляр наследника:

>>> с = NewSingleton()
>>> type(c)
NameError: name 'c' is not defined

Впрочем, починить наследование достаточно просто:

class Singleton:
    def __new__(cls):
        if not hasattr(cls, 'instance') or not isinstance(cls.instance, cls):
             cls.instance = super(Singleton, cls).__new__(cls)
        return cls.instance


class NewSingleton(Singleton):
    pass
>>> a = Singleton()
>>> b = Singleton()
>>> a is b
True
>>> c = NewSingleton()
>>> d = NewSingleton()
>>> c is d
True
>>> c is a
False
>>> type(c)
__main__.NewSingleton
>>> type(a)
__main__.Singleton

Все прекрасно работает. Выглядит более или менее неплохо. Более того, этот класс может сделать любой класс синглтоном. В питоне же есть множественное наследование. И это круто, значит можно сделать синглтоном любой класс, подмешав ему в предков синглтон.

Хотя можно обойтись и без множественного наследования. Тем более, что не всегда это полезно для кода. Небольшие модификации и наш синглтон превращается в метакласс (я уже писал о своей нелюбви к метаклассам, но иногда метакласс - то что нужно).

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class IAmSingleton(metaclass=Singleton):
    pass
>>> a = IAmSingleton()
>>> b = IAmSingleton()
>>> a is b
True
>>> type(a)
__main__.IAmSingleton
>>> Singleton._instances
{__main__.IAmSingleton: <__main__.IAmSingleton at 0x1053196a0>}

Пожалуй не рассмотренным остался только вариант с декоратором. Такой вариант подойдет, когда надо сделать синглтоном какой-то отдельный класс из иерархии. Экзотика, конечно, но для полноты картины нужно рассмотреть и его:

def singleton(class_):
  instances = {}
  def getinstance(*args, **kwargs):
    if class_ not in instances:
        instances[class_] = class_(*args, **kwargs)
    return instances[class_]
  return getinstance


@singleton
class MyClass:
  pass
>>> a = MyClass()
>>> b = MyClass()
>>> a is b
True

Пользуйтесь любым из приведенных рабочих вариантов в зависимости от проекта. Буду рад увидеть в комментариях ваши реализации этого паттерна.

---
Возник вопрос? Мне всегда можно написать в Twitter: avkorablev