Использовать метакласс
Я бы порекомендовал Метод # 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 с помощью Logger()
, Python сначала спрашивает метакласс Logger
, Singleton
, что делать, что позволяет прервать создание экземпляра. Этот процесс аналогичен тому, как Python запрашивает у класса, что делать, вызывая __getattr__
, когда вы ссылаетесь на один из его атрибутов, выполняя myclass.attribute
.
Метакласс по существу решает , что означает определение класса и как реализовать это определение. Смотрите, например, http://code.activestate.com/recipes/498149/,, который по существу воссоздает стиль C struct
s в 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 являются именно такими синглетонами. Пока вы не помещаете состояние в свое перечисление, все в порядке, поэтому, пожалуйста, не делайте.)
К другим видам синглетонов, которые являются полуприемлемыми, относятся те, которые не влияют на выполнение вашего кода. У них нет «побочных эффектов».Ведение журнала является прекрасным примером.Он загружен синглетами и глобальным состоянием.Это приемлемо (поскольку это не повредит вам), потому что ваше приложение не ведет себя по-разному, независимо от того, включен ли данный регистратор.Информация здесь течет одним способом: из вашего приложения в регистратор.Даже мыслительные регистраторы являются глобальным состоянием, поскольку никакая информация не поступает из регистраторов в ваше приложение, регистраторы приемлемы.Вы все равно должны внедрить свой регистратор, если хотите, чтобы ваш тест утверждал, что что-то регистрируется, но в целом регистраторы не представляют опасности, несмотря на полное состояние.