Используйте метакласс
Я бы порекомендовал метод № 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 с использованием метаклассов. Поток Какие есть (конкретные) варианты использования для метаклассов? также приводятся некоторые примеры, которые, как правило, связаны с декларативным программированием, особенно при использовании в ORM.
В этой ситуации, если вы используете свой метод № 2 , а подкласс определяет __new__
метод, он будет выполняться каждый раз, когда вы вызываете, SubClassOfSingleton()
потому что он отвечает за вызов метода, который возвращает сохраненный экземпляр. С метаклассом он будет вызываться только один раз , когда создается единственный экземпляр. Вы хотите настроить, что значит называть класс , который определяется его типом.
В общем случае имеет смысл использовать метакласс для реализации синглтона. Синглтон особенный, потому что он создается только один раз , а метакласс - это способ, которым вы настраиваете создание класса . Использование метакласса дает вам больше контроля в случае, если вам нужно настроить определения класса singleton другими способами.
Вашим синглетам не нужно многократное наследование (поскольку метакласс не является базовым классом), но для подклассов созданного класса , использующего множественное наследование, необходимо убедиться, что класс синглтона является первым / левым с метаклассом, который переопределяет __call__
Это очень маловероятно, чтобы быть проблемой. Экземпляр dict не находится в пространстве имен экземпляра поэтому он не будет случайно перезаписывать его.
Вы также услышите, что шаблон синглтона нарушает «Принцип единой ответственности» - каждый класс должен делать только одно . Таким образом, вам не нужно беспокоиться о том, чтобы испортить одну вещь, которую делает код, если вам нужно изменить другую, потому что они разделены и инкапсулированы. Реализация метакласса проходит этот тест . Метакласс отвечает за применение шаблона, и созданный класс и подклассы не должны знать, что они являются синглетонами . Метод # 1 не проходит этот тест, как вы заметили, «MyClass сам по себе является функцией, а не классом, поэтому вы не можете вызывать методы класса из него».
Python 2 и 3 совместимая версия
Написание чего-то, что работает как в Python2, так и в 3, требует использования немного более сложной схемы. Так как метаклассы, как правило , подклассы типа type
, можно использовать один , чтобы динамически создать промежуточный базовый класс во время выполнения с ним , как его метаклассом , а затем использовать , что , как BaseClass общественного 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 являются именно такими синглетонами. Пока вы не помещаете состояние в свое перечисление, все в порядке, поэтому, пожалуйста, не делайте.)
К другим видам синглетонов, которые являются частично приемлемыми, относятся те, которые не влияют на выполнение вашего кода. У них нет «побочных эффектов». Ведение журнала является прекрасным примером. Он загружен синглетами и глобальным состоянием. Это приемлемо (поскольку это не повредит вам), потому что ваше приложение не ведет себя по-разному, независимо от того, включен ли данный регистратор. Информация здесь течет одним способом: из вашего приложения в регистратор. Даже мыслительные регистраторы являются глобальным состоянием, поскольку никакая информация не поступает из регистраторов в ваше приложение, регистраторы приемлемы. Вы все равно должны внедрить свой регистратор, если хотите, чтобы ваш тест утверждал, что что-то регистрируется, но в целом регистраторы не являются вредными, несмотря на полное состояние.
foo.x
или, если вы настаиваетеFoo.x
вместоFoo().x
); используйте атрибуты класса и методы static / class (Foo.x
).