Используйте метакласс
Я бы порекомендовал метод 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/, который по существу воссоздает struct в стиле C в Python с помощью метаклассов. Поток Какие (конкретные) варианты использования metaclasses? также предоставляет несколько примеров, они обычно, кажется, связаны с декларативным программированием, особенно используемым в ORM.
В этой ситуации, если вы используете свой метод 2, а подкласс определяет метод __new__, он будет выполняться каждый раз, когда вы вызываете SubClassOfSingleton(), поскольку он отвечает за вызов метода, возвращающего сохраненный экземпляр. В случае метакласса он будет вызываться только один раз при создании единственного экземпляра. Вы хотите настроить то, что означает вызов класса, в зависимости от его типа.
В общем, имеет смысл использовать метакласс для реализации синглтона. Синглтон является особенным, потому что создается только один раз, а метакласс - это способ настройки создания класса. Использование метакласса дает вам больше контроля на случай, если вам потребуется настроить определения одноэлементного класса другими способами.
Вашим синглетонам не потребуется множественное наследование (поскольку метакласс не является базовым классом), но для подклассов созданного класса, которые используют множественное наследование, необходимо убедиться, что одноэлементный класс - это первый / крайний левый класс с метаклассом, который переопределяет __call__. Это маловероятно. Экземпляр dict не находится в пространстве имен экземпляра, поэтому он случайно не перезапишет его.
Вы также услышите, что шаблон singleton нарушает принцип единой ответственности - каждый класс должен делать только одно. Таким образом, вам не нужно беспокоиться о том, что код может испортить одно, если вам нужно изменить другое, потому что они разделены и инкапсулированы. Реализация метакласса проходит этот тест. Метакласс отвечает за соблюдение шаблона, и созданный класс и подклассы не должны быть осведомлены о том, что они одиночные. Метод №1 не проходит этот тест, поскольку вы заметили, что MyClass сам по себе является функцией, а не классом, поэтому вы не можете вызывать из него методы класса.
Версия, совместимая с Python 2 и 3
Чтобы написать что-то, что работает как на Python2, так и на Python 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 к, а не к методу класса, поскольку фактический класс еще не был создан на момент его вызова. Все это будет справедливо и для реализации метакласса.
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__ вызов.
Когда использовать шаблон singleton
Ваш вариант использования - один из лучших примеров использования синглтона. Вы говорите в одном из комментариев. «Мне логгинг всегда казался естественным кандидатом в синглтоны». Вы абсолютно правы.
Когда люди говорят, что синглтоны - это плохо, наиболее частая причина - это неявное разделяемое состояние. В то время как глобальные переменные и импорт модулей верхнего уровня являются явным общим состоянием, другие объекты, которые передаются, обычно создаются. Это хороший момент, за двумя исключениями.
Первый и тот, который упоминается в разных местах, - это когда синглтоны постоянны. Использование глобальных констант, особенно перечислений, широко распространено и считается разумным, потому что, несмотря ни на что, ни один из пользователей не может испортить их для любого другого пользователя. Это в равной степени верно и для постоянного синглтона.
Второе исключение, о котором меньше упоминается, является противоположным - когда синглтон является только приемником данных, а не источником данных (прямо или косвенно). Вот почему логгеры кажутся естественным применением для одиночных игр. Поскольку различные пользователи не меняют регистраторы так, как другие пользователи будут интересоваться, существует состояние, не являющееся общедоступным. Это опровергает основной аргумент против одноэлементного шаблона и делает их разумным выбором из-за их простоты использования для этой задачи.
Вот цитата из http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html:
Теперь есть один вид синглтонов, который подходит. Это синглтон, в котором все доступные объекты неизменяемы. Если все объекты неизменяемы, то синглтон не имеет глобального состояния, так как все постоянно. Но такой синглтон настолько легко превратить в изменяемый, что это очень скользкая дорожка. Поэтому я тоже против этих синглтонов не потому, что они плохие, а потому, что им очень легко испортиться. (В качестве побочного примечания перечисление Java - это просто такие синглтоны. Пока вы не помещаете состояние в свое перечисление, все в порядке, поэтому, пожалуйста, не делайте этого.)
Другой тип синглтонов, которые являются полуприемлемыми, - это те, которые не влияют на выполнение вашего кода. Они не имеют побочных эффектов. Ведение журнала - прекрасный пример. Он загружен синглетонами и глобальным состоянием. Это приемлемо (так как это не повредит вам), потому что ваше приложение не ведет себя иначе, независимо от того, включен ли данный регистратор. Информация здесь течет в одном направлении: из вашего приложения в регистратор. Даже мысли, что регистраторы являются глобальным состоянием, поскольку никакая информация не поступает от регистраторов в ваше приложение, регистраторы приемлемы. Вам все равно следует ввести свой регистратор, если вы хотите, чтобы ваш тест утверждал, что что-то регистрируется, но в целом регистраторы не вредны, несмотря на то, что они заполнены состоянием.
person
agf
schedule
23.07.2011
foo.xили, если вы настаиваете,Foo.xвместоFoo().x); использовать атрибуты класса и статические методы / методы класса (Foo.x). - person Chris Morgan   schedule 20.07.2011__init__каждый раз; Я добавил этот код в свой пост. Я также прокомментировал метод №4. Резюме: плохо, чтобы класс был собственным базовым классом. - person agf   schedule 27.07.2011Singletontype, да? И метод__new__выбирает правильный, используя класс в качестве своего хэша, создавая его, если это еще не сделано. В моей версии каждый класс имел доступ только к своему собственному экземпляру или базовому экземпляру, если он был создан, а затем метод__new__проверяет, является ли он экземпляром правильного класса. Это наверняка разделяет меньше государственной эргономики, поэтому более желательно? - person theheadofabroom   schedule 29.07.2011__call__против__new__. В метаклассе__new__означает, что класс новый, а не когда экземпляр новый. Вы хотите, чтобы метод вызывался каждый раз, когда кто-то пытается создать новый экземпляр, поэтому вам нужно использовать__call__. Даже если вы исправите свою версию для использованияtypeиsuperвместоobject, она не будет создавать синглтоны, если вы не используете__call__- проверьте это._instanceпротив_instancesне было проблемой. Ваш путь может быть даже лучше, я должен об этом подумать. - person agf   schedule 29.07.2011class MyClass(threading.Thread, metaclass=Singleton):- person tammoj   schedule 06.05.2019