Каков наилучший способ реализации singleton в Python
Этот вопрос предназначен не для обсуждения того, желателен ли шаблон проектирования singleton, является ли он антишаблоном или для каких-либо религиозных войн, а для обсуждения того, как этот шаблон лучше всего реализовать в Python таким образом, чтобы он был наиболее питонистским. В данном случае я определяю "наиболее питонический" как означающий, что он следует "принципу наименьшего удивления".
У меня есть несколько классов, которые стали бы синглетонами (мой вариант использования - для регистратора, но это не важно). Я не хочу загромождать несколько классов добавлением gumph, когда я могу просто наследовать или украсить.
Лучшие методы:
Метод 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
Плюсы
- Декораторы являются аддитивными способом, который часто более интуитивно понятен, чем множественное наследование.
Минусы
Хотя объекты, созданные с помощью
MyClass()
, были бы настоящими объектами singleton,MyClass
сам по себе является функцией, а не классом, поэтому вы не можете вызывать из него методы класса. Также дляx = MyClass();
y = MyClass();
t = type(n)();
тогда x == y
но x != t && y != t
Метод 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
Плюсы
- Это настоящий класс
Минусы
- Множественное наследование - ого!
__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
Плюсы
- Это настоящий класс
- Автоматически-волшебным образом покрывает наследование
- Использует
__metaclass__
по назначению (и поставил меня в известность об этом)
Минусы
- Существуют ли какие-либо?
Метод 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
Плюсы
- Это настоящий класс
- Автоматически-волшебным образом покрывает наследование
Минусы
- Нет ли накладных расходов на создание каждого нового класса? Здесь мы создаем два класса для каждого класса, который мы хотим сделать singleton. Хотя в моем случае это нормально, я беспокоюсь, что это может не масштабироваться. Конечно, есть вопрос о том, не слишком ли просто масштабировать этот шаблон...
- В чем смысл
_sealed
атрибута - Нельзя вызывать методы с тем же именем в базовых классах, используя
super()
потому что они будут повторяться. Это означает, что вы не можете настроить__new__
и не можете создать подкласс класса, к которому вам нужно обратиться__init__
.
Метод 5: модуль
файл модуля singleton.py
Плюсы
- Простое лучше сложного
Минусы
- Не создается лениво
Переведено автоматически
Ответ 1
Use a Metaclass
I would recommend Method #2, but you're better off using a metaclass than a base class. Here is a sample implementation:
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
Or in Python3
class Logger(metaclass=Singleton):
pass
If you want to run __init__
every time the class is called, add
else:
cls._instances[cls].__init__(*args, **kwargs)
to the if
statement in Singleton.__call__
.
A few words about metaclasses. A metaclass is the class of a class; that is, a class is an instance of its metaclass. You find the metaclass of an object in Python with type(obj)
. Normal new-style classes are of type type
. Logger
in the code above will be of type class 'your_module.Singleton'
, just as the (only) instance of Logger
will be of type class 'your_module.Logger'
. When you call logger with Logger()
, Python first asks the metaclass of Logger
, Singleton
, what to do, allowing instance creation to be pre-empted. This process is the same as Python asking a class what to do by calling __getattr__
when you reference one of its attributes by doing myclass.attribute
.
A metaclass essentially decides what the definition of a class means and how to implement that definition. See for example http://code.activestate.com/recipes/498149/, which essentially recreates C-style struct
s in Python using metaclasses. The thread What are some (concrete) use-cases for metaclasses? also provides some examples, they generally seem to be related to declarative programming, especially as used in ORMs.
In this situation, if you use your Method #2, and a subclass defines a __new__
method, it will be executed every time you call SubClassOfSingleton()
-- because it is responsible for calling the method that returns the stored instance. With a metaclass, it will only be called once, when the only instance is created. You want to customize what it means to call the class, which is decided by its type.
In general, it makes sense to use a metaclass to implement a singleton. A singleton is special because is created only once, and a metaclass is the way you customize the creation of a class. Using a metaclass gives you more control in case you need to customize the singleton class definitions in other ways.
Your singletons won't need multiple inheritance (because the metaclass is not a base class), but for subclasses of the created class that use multiple inheritance, you need to make sure the singleton class is the first / leftmost one with a metaclass that redefines __call__
This is very unlikely to be an issue. The instance dict is not in the instance's namespace so it won't accidentally overwrite it.
You will also hear that the singleton pattern violates the "Single Responsibility Principle" -- each class should do only one thing. That way you don't have to worry about messing up one thing the code does if you need to change another, because they are separate and encapsulated. The metaclass implementation passes this test. The metaclass is responsible for enforcing the pattern and the created class and subclasses need not be aware that they are singletons. Method #1 fails this test, as you noted with "MyClass itself is a a function, not a class, so you cannot call class methods from it."
Python 2 and 3 Compatible Version
Writing something that works in both Python2 and 3 requires using a slightly more complicated scheme. Since metaclasses are usually subclasses of type type
, it's possible to use one to dynamically create an intermediary base class at run time with it as its metaclass and then use that as the baseclass of the public Singleton
base class. It's harder to explain than to do, as illustrated next:
# 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
An ironic aspect of this approach is that it's using subclassing to implement a metaclass. One possible advantage is that, unlike with a pure metaclass, isinstance(inst, Singleton)
will return True
.
Corrections
On another topic, you've probably already noticed this, but the base class implementation in your original post is wrong. _instances
needs to be referenced on the class, you need to use super()
or you're recursing, and __new__
is actually a static method that you have to pass the class to, not a class method, as the actual class hasn't been created yet when it is called. All of these things will be true for a metaclass implementation as well.
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()
Decorator Returning A Class
I originally was writing a comment but it was too long, so I'll add this here. Method #4 is better than the other decorator version, but it's more code than needed for a singleton, and it's not as clear what it does.
Основные проблемы проистекают из того, что класс является его собственным базовым классом. Во-первых, не странно ли, что класс является подклассом почти идентичного класса с тем же именем, который существует только в своем __class__
атрибуте? Это также означает, что вы не можете определить какие-либо методы, которые вызывают одноименный метод в своем базовом классе с super()
помощью, потому что они будут повторяться. Это означает, что ваш класс не может настраиваться __new__
и не может быть производным от каких-либо классов, которые нужно __init__
вызывать в них.
Когда использовать шаблон singleton
Ваш вариант использования - один из лучших примеров того, что вы хотите использовать singleton. В одном из комментариев вы говорите: "Мне ведение журнала всегда казалось естественным кандидатом для Singletons". Вы абсолютно правы.
Когда люди говорят, что синглтоны плохие, наиболее распространенной причиной является то, что они являются неявным общим состоянием. В то время как глобальные переменные и импорт модулей верхнего уровня являются явным общим состоянием, обычно создаются экземпляры других объектов, которые передаются по кругу. Это хороший момент, за двумя исключениями.
Первый, который упоминается в разных местах, - это когда синглтоны являются постоянными. Использование глобальных констант, особенно перечислений, широко распространено и считается разумным, потому что, несмотря ни на что, ни один из пользователей не может испортить их для любого другого пользователя. Это в равной степени верно и для постоянного синглтона.
Второе исключение, о котором упоминается меньше, противоположно - когда singleton является только приемником данных, а не источником данных (прямо или косвенно). Вот почему регистраторы считают "естественным" использование singletons. Поскольку различные пользователи не меняют регистраторы так, как это будет интересно другим пользователям, на самом деле нет общего состояния. Это сводит на нет основной аргумент против шаблона singleton и делает их разумным выбором из-за их простоты использования для решения задачи.
Вот цитата из http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html:
Итак, есть один тип Singleton, который подходит. Это singleton, в котором все доступные объекты неизменяемы. Если все объекты неизменяемы, то Singleton не имеет глобального состояния, поскольку все постоянно. Но превратить этот тип singleton в изменяемый так легко, что это очень скользкий путь. Поэтому я тоже против этих одиночек, не потому, что они плохие, а потому, что им очень легко испортиться. (В качестве дополнительного примечания Java enumeration - это как раз такие одиночки. Пока вы не вводите state в свое перечисление, все в порядке, поэтому, пожалуйста, не делайте этого.)
Другой вид Singletons, который является полу-приемлемым, - это те, которые не влияют на выполнение вашего кода, у них нет "побочных эффектов". Ведение журнала - прекрасный пример. Он загружается одиночными элементами и глобальным состоянием. Это приемлемо (поскольку вам это не повредит), потому что ваше приложение не будет вести себя по-другому, независимо от того, включен данный регистратор или нет. Информация здесь поступает в одну сторону: из вашего приложения в регистратор. Даже если регистраторы считаются глобальными, поскольку информация из регистраторов в ваше приложение не поступает, регистраторы приемлемы. Вам все равно следует внедрить свой регистратор, если вы хотите, чтобы ваш тест утверждал, что что-то регистрируется, но в целом регистраторы не вредны, несмотря на то, что они заполнены состоянием.
Ответ 2
class Foo(object):
pass
some_global_variable = Foo()
Модули импортируются только один раз, все остальное требует переосмысления. Не используйте синглтоны и старайтесь не использовать глобальные значения.
Ответ 3
Используйте модуль. Он импортируется только один раз. Определите в нем некоторые глобальные переменные - они будут "атрибутами" singleton. Добавьте некоторые функции - "методы" singleton.
Ответ 4
Вам, вероятно, никогда не понадобится singleton в Python. Просто определите все свои данные и функции в модуле, и у вас де-факто будет singleton:
import datetime
file_name=None
def set_file_name(new_file_name: str):
global file_name
file_name=new_file_name
def write(message: str):
global file_name
if file_name:
with open(file_name, 'a+') as f:
f.write("{} {}\n".format(datetime.datetime.now(), message))
else:
print("LOG: {}", message)
Как использовать:
import log
log.set_file_name("debug.log")
log.write("System starting")
...
Если вам действительно абсолютно необходим одноэлементный класс, я бы выбрал:
class MySingleton(object):
def foo(self):
pass
my_singleton = MySingleton()
Как использовать:
from mysingleton import my_singleton
my_singleton.foo()
где mysingleton.py
ваше имя файла, которое MySingleton
определено в. Это работает, потому что после первого импорта файла Python не выполняет код повторно.