Создание синглтона в Python
Этот вопрос не для обсуждения того, является ли шаблон проектирования синглтона желательным, является ли он антипаттерном или для каких-либо религиозных войн, а для обсуждения того, как этот шаблон лучше всего реализовать в Python таким способом, который наиболее питоничен. В данном случае я определяю "самый питонический", чтобы обозначить, что он следует "принципу наименьшего удивления".
У меня есть несколько классов, которые могли бы стать синглетонами (мой вариант использования - для регистратора, но это не важно). Я не хочу загромождать несколько классов добавленной гитарой, когда я могу просто наследовать или украшать.
Лучшие методы:
Способ 1: декоратор
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(BaseClass):
pass
Pros
- Декораторы аддитивны таким образом, что зачастую более интуитивно понятны, чем множественное наследование.
Cons
- Хотя объекты, созданные с использованием MyClass(), будут настоящими одноэлементными объектами, сам MyClass является функцией, а не классом, поэтому вы не можете вызывать методы класса из него. Также для
m = MyClass(); n = MyClass(); o = type(n)();
затемm == n && m != o && n != o
Способ 2: базовый класс
class Singleton(object):
_instance = None
def __new__(class_, *args, **kwargs):
if not isinstance(class_._instance, class_):
class_._instance = object.__new__(class_, *args, **kwargs)
return class_._instance
class MyClass(Singleton, BaseClass):
pass
Pros
- Это настоящий класс
Cons
- Множественное наследование - тьфу!
__new__
может быть перезаписано при наследовании от второго базового класса? Нужно думать больше, чем нужно.
Метод 3: метакласс
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]
#Python2
class MyClass(BaseClass):
__metaclass__ = Singleton
#Python3
class MyClass(BaseClass, metaclass=Singleton):
pass
Pros
- Это настоящий класс
- Автомагически охватывает наследование
- Пользы
__metaclass__
для его надлежащего назначения (и заставил меня осознать это)
Cons
- Есть ли?
Метод 4: декоратор возвращает класс с тем же именем
def singleton(class_):
class class_w(class_):
_instance = None
def __new__(class_, *args, **kwargs):
if class_w._instance is None:
class_w._instance = super(class_w,
class_).__new__(class_,
*args,
**kwargs)
class_w._instance._sealed = False
return class_w._instance
def __init__(self, *args, **kwargs):
if self._sealed:
return
super(class_w, self).__init__(*args, **kwargs)
self._sealed = True
class_w.__name__ = class_.__name__
return class_w
@singleton
class MyClass(BaseClass):
pass
Pros
- Это настоящий класс
- Автомагически охватывает наследование
Cons
- Нет ли накладных расходов на создание каждого нового класса? Здесь мы создаем два класса для каждого класса, который мы хотим сделать синглтоном. Хотя в моем случае это нормально, я беспокоюсь, что это может не масштабироваться. Конечно, есть спор о том, должно ли быть слишком легко масштабировать эту модель...
- Какой смысл
_sealed
атрибут - Не могу вызывать методы с одинаковыми именами в базовых классах, используя
super()
потому что они будут возвращаться. Это означает, что вы не можете настроить__new__
и не может создать подкласс для класса, который требует от вас вызова__init__
,
43 ответа
Используйте метакласс
Я бы порекомендовал метод № 2, но лучше использовать метакласс, чем базовый класс. Вот пример реализации:
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 Logger(object):
__metaclass__ = Singleton
Или в Python3
class Logger(metaclass=Singleton):
pass
Если вы хотите запустить __init__
каждый раз, когда вызывается класс, добавьте
else:
cls._instances[cls].__init__(*args, **kwargs)
к if
заявление в Singleton.__call__
,
Несколько слов о метаклассах. Метакласс - это класс класса; то есть класс является экземпляром своего метакласса. Вы найдете метакласс объекта в Python с помощью type(obj)
, Обычные классы нового стиля имеют тип type
, Logger
в приведенном выше коде будет иметь тип class 'your_module.Singleton'
так же, как (единственный) экземпляр Logger
будет иметь тип class 'your_module.Logger'
, Когда вы звоните в логгер с Logger()
Python сначала спрашивает метакласс Logger
, Singleton
, что делать, позволяя создавать экземпляры заранее. Этот процесс аналогичен тому, как Python спрашивает класс, что делать, вызывая __getattr__
когда вы ссылаетесь на один из его атрибутов, делая myclass.attribute
,
Метакласс по существу решает, что означает определение класса и как реализовать это определение. Смотрите, например, http://code.activestate.com/recipes/498149/, который по сути воссоздает стиль C struct
в Python, используя метаклассы. Тема Каковы ваши (конкретные) варианты использования метаклассов в Python? также приводятся некоторые примеры, которые, как правило, связаны с декларативным программированием, особенно при использовании в ORM.
В этой ситуации, если вы используете ваш метод № 2, а подкласс определяет __new__
метод, он будет выполняться каждый раз, когда вы вызываете SubClassOfSingleton()
- потому что он отвечает за вызов метода, который возвращает сохраненный экземпляр. С метаклассом он будет вызываться только один раз, когда создается единственный экземпляр. Вы хотите настроить то, что означает вызывать класс, который определяется его типом.
В общем, имеет смысл использовать метакласс для реализации синглтона. Синглтон особенный, потому что он создается только один раз, а метакласс - это способ, которым вы настраиваете создание класса. Использование метакласса дает вам больше контроля в случае, если вам нужно настроить определения класса singleton другими способами.
Вашим синглетам не потребуется множественное наследование (поскольку метакласс не является базовым классом), но для подклассов созданного класса, использующего множественное наследование, необходимо убедиться, что класс синглтона является первым / левым классом с метаклассом, который переопределяет __call__
Это очень маловероятно, чтобы быть проблемой. Экземпляр dict не находитсяв пространстве имен экземпляра, поэтому он не будет случайно перезаписывать его.
Вы также услышите, что шаблон синглтона нарушает "Принцип единой ответственности" - каждый класс должен делать только одно. Таким образом, вам не нужно беспокоиться о том, чтобы испортить одну вещь, которую делает код, если вам нужно изменить другую, потому что они разделены и инкапсулированы. Реализация метакласса проходит этот тест. Метакласс отвечает за применение шаблона, и созданный класс и подклассы не должны знать, что они являются синглетонами.Метод #1 не проходит этот тест, как вы заметили, "MyClass сам по себе является функцией, а не классом, поэтому вы не можете вызывать методы класса из него".
Python 2 и 3 совместимая версия
Написание того, что работает как в Python2, так и в 3, требует использования немного более сложной схемы. Поскольку метаклассы обычно являются подклассами типаtype
можно использовать один для динамического создания промежуточного базового класса во время выполнения с его метаклассом, а затем использовать его в качестве базового класса общедоступного Singleton
Базовый класс. Это сложнее объяснить, чем сделать, как показано ниже:
# works in Python 2 & 3
class _Singleton(type):
""" A metaclass that creates a Singleton base class when called. """
_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 Singleton(_Singleton('SingletonMeta', (object,), {})): pass
class Logger(Singleton):
pass
Иронический аспект этого подхода заключается в том, что он использует подклассы для реализации метакласса. Одним из возможных преимуществ является то, что, в отличие от чистого метакласса, isinstance(inst, Singleton)
вернусь True
,
исправления
По другой теме вы, наверное, уже заметили это, но реализация базового класса в исходном посте неверна. _instances
нужно ссылаться на класс, вам нужно использовать super()
или вы повторяете, и __new__
на самом деле это статический метод, которому вы должны передать класс, а не метод класса, так как реальный класс еще не был создан при его вызове. Все это будет справедливо и для реализации метакласса.
class Singleton(object):
_instances = {}
def __new__(class_, *args, **kwargs):
if class_ not in class_._instances:
class_._instances[class_] = super(Singleton, class_).__new__(class_, *args, **kwargs)
return class_._instances[class_]
class MyClass(Singleton):
pass
c = MyClass()
Декоратор, возвращающий класс
Первоначально я писал комментарий, но это было слишком долго, поэтому я добавлю это здесь. Метод № 4 лучше, чем другая версия декоратора, но в нем больше кода, чем необходимо для одиночного, и не совсем понятно, что он делает.
Основные проблемы проистекают из того, что класс является его собственным базовым классом. Во-первых, не странно ли, чтобы класс был подклассом почти идентичного класса с тем же именем, которое существует только в его __class__
атрибут? Это также означает, что вы не можете определить любые методы, которые вызывают метод с тем же именем в их базовом классе с super()
потому что они будут возвращаться. Это означает, что ваш класс не может настроить __new__
и не может быть производным от любых классов, которые нуждаются __init__
призвал их.
Когда использовать шаблон синглтона
Ваш вариант использования - один из лучших примеров желания использовать синглтон. Вы говорите в одном из комментариев: "Для меня регистрация всегда казалась естественным кандидатом для синглетонов". Ты абсолютно прав.
Когда люди говорят, что одиночные игры плохие, самая распространенная причина - это неявное общее состояние. В то время как с глобальными переменными и импортом модулей верхнего уровня являются явным общим состоянием, другие объекты, которые передаются, обычно создаются. Это хороший момент, с двумя исключениями.
Первый и тот, который упоминается в разных местах, это когда синглтоны постоянны. Использование глобальных констант, особенно перечислений, является общепринятым и считается разумным, потому что, несмотря ни на что, никто из пользователей не может испортить их для любого другого пользователя. Это в равной степени верно и для постоянного синглтона.
Второе исключение, о котором упоминается меньше, является противоположным - когда синглтон является только приемником данных, а не источником данных (прямо или косвенно). Вот почему регистраторы чувствуют себя как "естественное" использование для одиночных игр. Поскольку различные пользователи не меняют регистраторы так, как о них будут заботиться другие пользователи, в действительности не существует общего состояния. Это сводит на нет основной аргумент против шаблона синглтона и делает их разумным выбором из-за их простоты использования для задачи.
Вот цитата из http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html:
Теперь есть один вид синглтона, который в порядке. Это единственный, где все достижимые объекты неизменны. Если все объекты неизменны, то у Синглтона нет глобального состояния, так как все постоянно. Но так легко превратить этот вид синглтона в изменчивый, это очень скользкий уклон. Поэтому я тоже против этих синглетонов не потому, что они плохие, а потому, что им очень легко пойти плохо. (В качестве дополнительного примечания перечисления Java являются именно такими синглетонами. Пока вы не помещаете состояние в свое перечисление, все в порядке, поэтому, пожалуйста, не делайте.)
Другой вид синглетонов, которые являются полу-приемлемыми, это те, которые не влияют на выполнение вашего кода, у них нет "побочных эффектов". Ведение журнала является прекрасным примером. Он загружен синглетами и глобальным состоянием. Это приемлемо (поскольку это не повредит вам), потому что ваше приложение не ведет себя по-разному, независимо от того, включен ли данный регистратор. Информация здесь течет одним способом: из вашего приложения в регистратор. Даже мыслительные регистраторы являются глобальным состоянием, поскольку никакая информация не поступает из регистраторов в ваше приложение, регистраторы приемлемы. Вы все равно должны внедрить свой регистратор, если хотите, чтобы ваш тест утверждал, что что-то регистрируется, но в целом регистраторы не являются вредными, несмотря на полное состояние.
class Foo(object):
pass
some_global_variable = Foo()
Модули импортируются только один раз, все остальное продуманно. Не используйте синглтоны и старайтесь не использовать глобалы.
Используйте модуль. Импортируется только один раз. Определите в нем некоторые глобальные переменные - они будут "атрибутами" синглтона. Добавьте некоторые функции - "методы" синглтона.
Вы, вероятно, никогда не нуждаетесь в синглтоне в Python. Просто определите все свои данные и функции в модуле, и у вас есть де-факто синглтон.
Если вам действительно нужно иметь синглтон-класс, то я бы пошел с:
class My_Singleton(object):
def foo(self):
pass
my_singleton = My_Singleton()
Использовать:
from mysingleton import my_singleton
my_singleton.foo()
где mysingleton.py - ваше имя файла, в котором определен My_Singleton. Это работает, потому что после первого импорта файла Python не выполняет повторно код.
Вам просто нужен декоратор, в зависимости от версии Python:
Питон 3.2+
Выполнение
from functools import lru_cache
@lru_cache(maxsize=None)
class CustomClass(object):
def __init__(self, arg):
print(f"CustomClass initialised with {arg}")
self.arg = arg
Применение
c1 = CustomClass("foo")
c2 = CustomClass("foo")
c3 = CustomClass("bar")
print(c1 == c2)
print(c1 == c3)
Выход
>>> CustomClass initialised with foo
>>> CustomClass initialised with bar
>>> True
>>> False
Обратите внимание, как
foo
был напечатан только один раз
Питон 3.9+
Реализация :
from functools import cache
@cache
class CustomClass(object):
...
Вот одна строка для вас:
singleton = lambda c: c()
Вот как вы используете это:
@singleton
class wat(object):
def __init__(self): self.x = 1
def get_x(self): return self.x
assert wat.get_x() == 1
Ваш объект получает экземпляр с нетерпением. Это может или не может быть то, что вы хотите.
Проверьте вопрос переполнения стека Есть ли простой, элегантный способ определения синглетонов в Python? с несколькими решениями.
Я настоятельно рекомендую посмотреть выступления Алекса Мартелли о шаблонах проектирования в python: часть 1 и часть 2. В частности, в части 1 он говорит о синглетонах / объектах общего состояния.
- Если кто-то хочет иметь несколько экземпляров одного и того же класса, но только если аргументы или kwargs различны, можно использовать это
- Ex.
- Если у вас есть обработка класса
serial
связи, и для создания экземпляра вы хотите отправить последовательный порт в качестве аргумента, тогда при традиционном подходе не будет работать - Используя вышеупомянутые декораторы, можно создать несколько экземпляров класса, если аргументы разные.
- Для тех же аргументов декоратор возвратит тот же экземпляр, который уже был создан.
- Если у вас есть обработка класса
>>> from decorators import singleton
>>>
>>> @singleton
... class A:
... def __init__(self, *args, **kwargs):
... pass
...
>>>
>>> a = A(name='Siddhesh')
>>> b = A(name='Siddhesh', lname='Sathe')
>>> c = A(name='Siddhesh', lname='Sathe')
>>> a is b # has to be different
False
>>> b is c # has to be same
True
>>>
Использовать атрибут функции также очень просто
def f():
if not hasattr(f, 'value'):
setattr(f, 'value', singletonvalue)
return f.value
Используйте переменную класса (без декоратора)
Путем отмены
__new__
чтобы вернуть тот же экземпляр класса. Логическое значение для инициализации класса только в первый раз:
class SingletonClass:
_instance = None
def __new__(cls, *args, **kwargs):
# If no instance of class already exits
if cls._instance is None:
cls._instance = object.__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self, *args, **kwargs):
if self._initialized:
return
self.attr1 = args[0]
# set the attribute to `True` to not initialize again
self.initialized = True
Я предпочитаю это решение, которое я нашел очень ясным и простым. Например, он использует двойную проверку, если какой-то другой поток уже создал его. Также следует учитывать, что десериализация не создает никаких других экземпляров. https://gist.github.com/werediver/4396488
import threading
# Based on tornado.ioloop.IOLoop.instance() approach.
# See https://github.com/facebook/tornado
class SingletonMixin(object):
__singleton_lock = threading.Lock()
__singleton_instance = None
@classmethod
def instance(cls):
if not cls.__singleton_instance:
with cls.__singleton_lock:
if not cls.__singleton_instance:
cls.__singleton_instance = cls()
return cls.__singleton_instance
if __name__ == '__main__':
class A(SingletonMixin):
pass
class B(SingletonMixin):
pass
a, a2 = A.instance(), A.instance()
b, b2 = B.instance(), B.instance()
assert a is a2
assert b is b2
assert a is not b
print('a: %s\na2: %s' % (a, a2))
print('b: %s\nb2: %s' % (b, b2))
Вот моя собственная реализация синглетонов. Все, что вам нужно сделать, это украсить класс; чтобы получить синглтон, вы должны использовать Instance
метод. Вот пример:
@Singleton
class Foo:
def __init__(self):
print 'Foo created'
f = Foo() # Error, this isn't how you get the instance of a singleton
f = Foo.Instance() # Good. Being explicit is in line with the Python Zen
g = Foo.Instance() # Returns already created instance
print f is g # True
И вот код:
class Singleton:
"""
A non-thread-safe helper class to ease implementing singletons.
This should be used as a decorator -- not a metaclass -- to the
class that should be a singleton.
The decorated class can define one `__init__` function that
takes only the `self` argument. Other than that, there are
no restrictions that apply to the decorated class.
To get the singleton instance, use the `Instance` method. Trying
to use `__call__` will result in a `TypeError` being raised.
Limitations: The decorated class cannot be inherited from.
"""
def __init__(self, decorated):
self._decorated = decorated
def Instance(self):
"""
Returns the singleton instance. Upon its first call, it creates a
new instance of the decorated class and calls its `__init__` method.
On all subsequent calls, the already created instance is returned.
"""
try:
return self._instance
except AttributeError:
self._instance = self._decorated()
return self._instance
def __call__(self):
raise TypeError('Singletons must be accessed through `Instance()`.')
def __instancecheck__(self, inst):
return isinstance(inst, self._decorated)
Я порекомендую элегантное решение с использованием метаклассов
class Singleton(type):
# Inherit from "type" in order to gain access to method __call__
def __init__(self, *args, **kwargs):
self.__instance = None # Create a variable to store the object reference
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
# if the object has not already been created
self.__instance = super().__call__(*args, **kwargs) # Call the __init__ method of the subclass (Spam) and save the reference
return self.__instance
else:
# if object (Spam) reference already exists; return it
return self.__instance
class Spam(metaclass=Singleton):
def __init__(self, x):
print('Creating Spam')
self.x = x
if __name__ == '__main__':
spam = Spam(100)
spam2 = Spam(200)
Выход:
Creating Spam
Как видно из вывода, создается только один объект.
from functools import cache
@cache
class xxx:
....
Мертвый легко и работает!
Может быть, я неправильно понимаю шаблон синглтона, но мое решение такое простое и прагматичное (питоническое?). Этот код выполняет две цели
- Сделайте пример
Foo
доступный везде (глобальный). - Только один экземпляр
Foo
может существовать.
Это код.
#!/usr/bin/env python3
class Foo:
me = None
def __init__(self):
if Foo.me != None:
raise Exception('Instance of Foo still exists!')
Foo.me = self
if __name__ == '__main__':
Foo()
Foo()
Выход
Traceback (most recent call last):
File "./x.py", line 15, in <module>
Foo()
File "./x.py", line 8, in __init__
raise Exception('Instance of Foo still exists!')
Exception: Instance of Foo still exists!
Я брошу свою на ринг. Это простой декоратор.
from abc import ABC
def singleton(real_cls):
class SingletonFactory(ABC):
instance = None
def __new__(cls, *args, **kwargs):
if not cls.instance:
cls.instance = real_cls(*args, **kwargs)
return cls.instance
SingletonFactory.register(real_cls)
return SingletonFactory
# Usage
@singleton
class YourClass:
... # Your normal implementation, no special requirements.
Я думаю, что это имеет преимущества перед некоторыми другими решениями:
- Это ясно и кратко (на мой взгляд;D).
- Его действие полностью заключено в капсулу. Вам не нужно ничего менять в реализации
YourClass
, Это включает в себя отсутствие необходимости использовать метакласс для вашего класса (обратите внимание, что метакласс выше находится на фабрике, а не на "реальном" классе). - Это не полагается на исправление обезьяны ничего.
- Это прозрачно для абонентов:
- Абоненты все еще просто импортируют
YourClass
, это похоже на класс (потому что это так), и они используют его как обычно. Нет необходимости адаптировать абонентов к заводским функциям. - Какие
YourClass()
экземпляры по-прежнему является истинным примеромYourClass
Вы реализовали, а не прокси любого рода, так что нет никаких шансов побочных эффектов в результате этого. isinstance(instance, YourClass)
и аналогичные операции все еще работают как положено (хотя этот бит требует abc, поэтому исключает Python <2.6).
- Абоненты все еще просто импортируют
У меня возникает один недостаток: методы класса и статические методы реального класса не могут быть прозрачно вызваны через скрытый класс фабричного класса. Я использовал это достаточно редко, чтобы мне никогда не приходилось сталкиваться с этой потребностью, но это было бы легко исправить, используя собственный метакласс на фабрике, которая реализует __getattr__()
делегировать все атрибуты доступа к реальному классу.
Связанный паттерн, который я на самом деле нашел более полезным (не то, чтобы я говорил, что такие вещи вообще нужны очень часто) - это паттерн "Уникальный", в котором создание экземпляра класса с одинаковыми аргументами приводит к получению одного и того же экземпляра. Т.е. "синглтон на аргументы". Вышеизложенное адаптируется к этому хорошо и становится еще более кратким:
def unique(real_cls):
class UniqueFactory(ABC):
@functools.lru_cache(None) # Handy for 3.2+, but use any memoization decorator you like
def __new__(cls, *args, **kwargs):
return real_cls(*args, **kwargs)
UniqueFactory.register(real_cls)
return UniqueFactory
С учетом всего вышесказанного я согласен с общим советом, что, если вы считаете, что вам нужна одна из этих вещей, вам, вероятно, следует остановиться на мгновение и спросить себя, действительно ли вы это делаете. 99% времени, ЯГНИ.
Метод 3 кажется очень аккуратным, но если вы хотите, чтобы ваша программа работала как на Python 2, так и на Python 3, он не работает. Даже защита отдельных вариантов с помощью тестов для версии Python терпит неудачу, потому что версия Python 3 дает синтаксическую ошибку в Python 2.
Спасибо Майку Уоткинсу: http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/. Если вы хотите, чтобы программа работала как в Python 2, так и в Python 3, вам нужно сделать что-то вроде:
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]
MC = Singleton('MC', (object), {})
class MyClass(MC):
pass # Code for the class implementation
Я предполагаю, что "объект" в назначении должен быть заменен "BaseClass", но я не пробовал это (я пробовал код, как показано на рисунке).
Метод: переопределить __new__ после однократного использования
class Singleton():
def __init__(self):
Singleton.instance = self
Singleton.__new__ = lambda _: Singleton.instance
Плюсы
- Предельно просто и лаконично
- Настоящий класс, модули не нужны
- Правильное использование исправлений лямбда и питонической обезьяны
Минусы
- __new__ может быть снова переопределен
Я просто случайно сделал простой и подумал, что поделюсь им ...
class MySingleton(object):
def __init__(self, *, props={}):
self.__dict__ = props
mything = MySingleton()
mything.test = 1
mything2 = MySingleton()
print(mything2.test)
mything2.test = 5
print(mything.test)
Ну, кроме как согласиться с общим предложением Pythonic о глобальном уровне модуля, как насчет этого:
def singleton(class_):
class class_w(class_):
_instance = None
def __new__(class2, *args, **kwargs):
if class_w._instance is None:
class_w._instance = super(class_w, class2).__new__(class2, *args, **kwargs)
class_w._instance._sealed = False
return class_w._instance
def __init__(self, *args, **kwargs):
if self._sealed:
return
super(class_w, self).__init__(*args, **kwargs)
self._sealed = True
class_w.__name__ = class_.__name__
return class_w
@singleton
class MyClass(object):
def __init__(self, text):
print text
@classmethod
def name(class_):
print class_.__name__
x = MyClass(111)
x.name()
y = MyClass(222)
print id(x) == id(y)
Выход:
111 # the __init__ is called only on the 1st time
MyClass # the __name__ is preserved
True # this is actually the same instance
Вы можете использовать
metaclass
если вы хотите использовать
instance
как собственность. Например;
class SingletonMeta(type):
def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)
cls._instance = None
cls._locker = threading.Lock()
@property
def instance(self, *args, **kwargs):
if self._instance is None:
with self._locker:
if self._instance is None:
self._instance = self(*args, **kwargs)
return self._instance
class MyClass(metaclass=SingletonMeta):
def __init__(self):
# init here
pass
# get the instance
my_class_instance = MyClass.instance
Плюсы
Это настоящий класс.Автомагически покрывает наследование. Использует метакласс по назначению (и я узнал об этом).
Есть такие?
Это будет проблема с сериализацией. Если вы попытаетесь десериализовать объект из файла (pickle), он не будет использовать
__call__
поэтому он создаст новый файл, вы можете использовать наследование базового класса с
__new__
чтобы предотвратить это.
Как насчет этого:
def singleton(cls):
instance=cls()
cls.__new__ = cls.__call__= lambda cls: instance
cls.__init__ = lambda self: None
return instance
Используйте его в качестве декоратора для класса, который должен быть одноэлементным. Как это:
@singleton
class MySingleton:
#....
Это похоже на singleton = lambda c: c()
декоратор в другом ответе. Как и другое решение, единственный экземпляр имеет имя класса (MySingleton
). Однако с этим решением вы все еще можете "создавать" экземпляры (фактически получая единственный экземпляр) из класса, выполнив MySingleton()
, Это также мешает вам создавать дополнительные экземпляры, выполняя type(MySingleton)()
(это также возвращает тот же экземпляр).
Это немного похоже на ответ от потрясающего, но не совсем то же самое.
Одноэлементный контракт не требует, чтобы мы могли вызывать конструктор несколько раз. Поскольку синглтон должен создаваться один раз и только один раз, разве он не должен создаваться только один раз? "Подмена" конструктора, возможно, ухудшает разборчивость.
Так что мое предложение как раз так:
class Elvis():
def __init__(self):
if hasattr(self.__class__, 'instance'):
raise Exception()
self.__class__.instance = self
# initialisation code...
@staticmethod
def the():
if hasattr(Elvis, 'instance'):
return Elvis.instance
return Elvis()
Это не исключает использование конструктора или поля instance
по коду пользователя:
if Elvis() is King.instance:
... если вы точно знаете, что Elvis
еще не был создан, и это King
есть.
Но это побуждает пользователей использовать the
Метод универсален:
Elvis.the().leave(Building.the())
Чтобы завершить это, вы также можете переопределить __delattr__()
вызвать исключение, если сделана попытка удалить instance
и переопределить __del__()
так что это вызывает исключение (если мы не знаем, что программа заканчивается...)
Дальнейшие улучшения
Спасибо всем, кто помог с комментариями и изменениями, из которых больше приветствуются. Хотя я использую Jython, это должно работать более широко и быть поточно-ориентированным.
try:
# This is jython-specific
from synchronize import make_synchronized
except ImportError:
# This should work across different python implementations
def make_synchronized(func):
import threading
func.__lock__ = threading.Lock()
def synced_func(*args, **kws):
with func.__lock__:
return func(*args, **kws)
return synced_func
class Elvis(object): # NB must be subclass of object to use __new__
instance = None
@classmethod
@make_synchronized
def __new__(cls, *args, **kwargs):
if cls.instance is not None:
raise Exception()
cls.instance = object.__new__(cls, *args, **kwargs)
return cls.instance
def __init__(self):
pass
# initialisation code...
@classmethod
@make_synchronized
def the(cls):
if cls.instance is not None:
return cls.instance
return cls()
Примечания:
- Если вы не сделаете подкласс от объекта в python2.x, вы получите класс старого стиля, который не использует
__new__
- При украшении
__new__
вы должны украсить @classmethod или__new__
будет несвязанный метод экземпляра - Это может быть улучшено путем использования метакласса, так как это позволит вам сделать
the
свойство уровня класса, возможно, переименовав его вinstance
Я также предпочитаю синтаксис декоратора производным от метакласса. Мои два цента:
from typing import Callable, Dict, Set
def singleton(cls_: Callable) -> type:
""" Implements a simple singleton decorator
"""
class Singleton(cls_): # type: ignore
__instances: Dict[type, object] = {}
__initialized: Set[type] = set()
def __new__(cls, *args, **kwargs):
if Singleton.__instances.get(cls) is None:
Singleton.__instances[cls] = super().__new__(cls, *args, **kwargs)
return Singleton.__instances[cls]
def __init__(self, *args, **kwargs):
if self.__class__ not in Singleton.__initialized:
Singleton.__initialized.add(self.__class__)
super().__init__(*args, **kwargs)
return Singleton
@singleton
class MyClass(...):
...
Это имеет некоторые преимущества по сравнению с другими декораторами:
-
isinstance(MyClass(), MyClass)
все равно будет работать (возврат функции из клаузуры вместо класса приведет к сбою isinstance) -
property
,classmethod
иstaticmethod
по-прежнему будет работать, как ожидалось -
__init__()
конструктор выполняется только один раз - Вы можете наследовать от вашего декорированного класса (бесполезно?), Снова используя @singleton
Минусы:
-
print(MyClass().__class__.__name__)
вернусьSingleton
вместо одMyClass
. Если вам это все еще нужно, я рекомендую использовать метакласс, как предложено выше.
Если вам нужен другой экземпляр, основанный на параметрах конструктора, это решение необходимо улучшить (решение, предоставленное Siddhesh Suhas Sathe предоставляет это).
Наконец, как предлагали другие, рассмотрите возможность использования модуля в Python. Модули - это объекты. Вы даже можете передавать их в переменные и вставлять в другие классы.
Этот ответ, вероятно, не то, что вы ищете. Я хотел синглтон в том смысле, что только этот объект имел свою идентичность, для сравнения. В моем случае это использовалось как Sentinel Value. На что ответ очень прост, сделайте любой предмет mything = object()
и по природе питона, только эта вещь будет иметь свою идентичность.
#!python
MyNone = object() # The singleton
for item in my_list:
if item is MyNone: # An Example identity comparison
raise StopIteration
Предыдущие ответы верны, но я не согласен с утверждением, которое было опубликовано как часть вопроса в методе 1 "MyClass сам по себе является функцией, а не классом, поэтому вы не можете вызывать методы класса". Смотрите мой пример ниже, этот метод вызывается несколько раз, в MyClass, который украшен тегом singleton.
Кроме того, обратите внимание, что это очень похоже на некоторые опубликованные ответы и основано на документе на python, но немного отличается, потому что класс и функция спроектированы таким образом, что они могут принимать 1 или 0 аргументов, и все еще работают синглтоном.
Вот доказательство того, что вы можете вызывать методы singleton несколько раз и показывает, что один экземпляр класса все еще используется и новые объекты не создаются.
#/usr/bin/env python
def singleton(cls):
instances = {}
def getinstance(anyArgs=None):
if cls not in instances:
instances[cls] = cls(anyArgs)
return instances[cls]
return getinstance
@singleton
class MyClass:
def __init__(self,count=None):
print("print argument if any exists",count)
def test(self, counter):
# time.sleep(1000)
print("-->",counter)
return counter
### create two objects to see if we get a singleton behavior !
a = MyClass(10000)
a.test(1)
b = MyClass()
b.test(2)
if a != b:
print("this is not a singleton")
#lets makesure it's still is the same object
if a!=b:
print("error")
Так как глава кабинета (человек, который разместил вопрос) дал некоторую обратную связь о его первоначальном вопросе, тогда я пошел и работал над новым решением, основанным на его обратной связи (я все еще сохранял свой предыдущий ответ, потому что я думаю, что это может быть полезно для некоторых, даже если это не на 100% точно то, о чем спрашивает глава кабины). Вот обновленный ответ:
вот код для копирования и вставки его:)
#/usr/bin/env python
from functools import wraps
def singleton(cls):
instances = {}
def getinstance(anyArgs=None):
if cls not in instances:
instances[cls] = cls(anyArgs)
return instances[cls]
return getinstance
def add_function(cls):
def outer_decorate_it(somefunction):
@wraps(somefunction)
def wrapper( *args, **kwargs):
return somefunction(*args, **kwargs)
setattr(cls, somefunction.__name__, wrapper)
return somefunction
return outer_decorate_it
@singleton
class MyClass():
def __init__(self,count=None):
print("print argument if any exists",count)
@add_function(MyClass)
def testit():
print("It's me the function from the class")
MyClass.testit()
Я не могу вспомнить, где я нашел это решение, но я считаю, что оно наиболее "элегантно" с моей точки зрения не Python-эксперта:
class SomeSingleton(dict):
__instance__ = None
def __new__(cls, *args,**kwargs):
if SomeSingleton.__instance__ is None:
SomeSingleton.__instance__ = dict.__new__(cls)
return SomeSingleton.__instance__
def __init__(self):
pass
def some_func(self,arg):
pass
Почему мне это нравится? Никаких декораторов, мета-классов, множественного наследования... и если вы решите, что больше не хотите, чтобы он был Singleton, просто удалите __new__
метод. Поскольку я новичок в Python (и ООП в целом), я ожидаю, что кто-то объяснит мне, почему это ужасный подход?
После некоторой борьбы с этим я в конце концов пришел к следующему, так что объект конфигурации загружался бы только один раз при вызове из отдельных модулей. Метакласс позволяет хранить экземпляр глобального класса во встроенном dict, что в настоящее время, по-видимому, является самым изящным способом хранения надлежащего глобального программного обеспечения.
import builtins
# -----------------------------------------------------------------------------
# So..... you would expect that a class would be "global" in scope, however
# when different modules use this,
# EACH ONE effectively has its own class namespace.
# In order to get around this, we use a metaclass to intercept
# "new" and provide the "truly global metaclass instance" if it already exists
class MetaConfig(type):
def __new__(cls, name, bases, dct):
try:
class_inst = builtins.CONFIG_singleton
except AttributeError:
class_inst = super().__new__(cls, name, bases, dct)
builtins.CONFIG_singleton = class_inst
class_inst.do_load()
return class_inst
# -----------------------------------------------------------------------------
class Config(metaclass=MetaConfig):
config_attr = None
@classmethod
def do_load(cls):
...<load-cfg-from-file>...
Код основан на ответе Толли.
#decorator, modyfies new_cls
def _singleton(new_cls):
instance = new_cls() #2
def new(cls):
if isinstance(instance, cls): #4
return instance
else:
raise TypeError("I can only return instance of {}, caller wanted {}".format(new_cls, cls))
new_cls.__new__ = new #3
new_cls.__init__ = lambda self: None #5
return new_cls
#decorator, creates new class
def singleton(cls):
new_cls = type('singleton({})'.format(cls.__name__), (cls,), {} ) #1
return _singleton(new_cls)
#metaclass
def meta_singleton(name, bases, attrs):
new_cls = type(name, bases, attrs) #1
return _singleton(new_cls)
Объяснение:
Создать новый класс, наследуя от заданного
cls
(это не меняетcls
в случае, если кто-то хочет, например,singleton(list)
)Создать экземпляр. Перед переопределением
__new__
это очень просто.- Теперь, когда мы легко создали экземпляр, переопределяем
__new__
используя метод, определенный момент назад. Функция возвращает
instance
только когда это то, что ожидает звонящий, иначе поднимаетTypeError
,
Условие не выполняется, когда кто-то пытается унаследовать от декорированного класса.Если
__new__()
возвращает экземплярcls
затем новый экземпляр__init__()
метод будет вызываться как__init__(self[, ...])
где self - это новый экземпляр, а остальные аргументы такие же, как были переданы__new__()
,instance
уже инициализирована, поэтому функция заменяет__init__
с функцией ничего не делая.