Паттерн 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