Создание синглтона в 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

Плюсы

  • Декораторы являются аддитивными, зачастую более интуитивно понятными, чем множественное наследование.

Минусы

  • Хотя объекты, созданные с помощью MyClass (), будут истинными одноэлементными объектами, 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

Плюсы

  • Это настоящий класс
  • Авто-магия покрывает наследование

Минусы

  • Нет ли накладных расходов на создание каждого нового класса? Здесь мы создаем два класса для каждого класса, который хотим сделать синглтоном. Хотя в моем случае это нормально, я беспокоюсь, что это может не масштабироваться. Конечно, есть споры о том, будет ли масштабирование этого паттерна слишком простым ...
  • В чем смысл атрибута _sealed
  • Невозможно вызывать методы с тем же именем в базовых классах, используя super(), потому что они будут рекурсивными. Это означает, что вы не можете настроить __new__ и не можете создать подкласс класса, который требует от вас вызова до __init__.

Метод 5: модуль

файл модуля singleton.py

Плюсы

  • Лучше простое, чем сложное

Минусы

  • Не лениво создан

person theheadofabroom    schedule 20.07.2011    source источник
comment
Еще три метода: используйте вместо него модуль (часто - как правило, я думаю - это более подходящий шаблон для Python, но это немного зависит от того, что вы с ним делаете); создайте единичный экземпляр и вместо этого разберитесь с ним (foo.x или, если вы настаиваете, Foo.x вместо Foo().x); использовать атрибуты класса и статические методы / методы класса (Foo.x).   -  person Chris Morgan    schedule 20.07.2011
comment
@ChrisMorgan: Если вы собираетесь использовать только классовые / статические методы, то не беспокойтесь о создании класса.   -  person Cat Plus Plus    schedule 20.07.2011
comment
Дубликат stackoverflow.com/questions/42558/ и stackoverflow.com/questions/31875/.   -  person agf    schedule 20.07.2011
comment
@Cat: ага. Третий случай лучше выразить как первый или второй.   -  person Chris Morgan    schedule 20.07.2011
comment
@Cat: Эффект аналогичен, однако причиной создания глобальной переменной может быть что угодно, в том числе незнание чего-либо лучшего. Зачем создается синглтон? Если тебе нужно спросить, тебя здесь не должно быть. Эта явность не только более питонична, но и значительно упрощает обслуживание. Да, синглтоны - это синтаксический сахар для глобальных объектов, но тогда классы - это синтаксический сахар для целой кучи неприглядных вещей, и я не думаю, что кто-то скажет вам, что вам всегда лучше без них.   -  person theheadofabroom    schedule 20.07.2011
comment
Может быть, мне поможет, если я объясню цель. Я хочу иметь несколько разных классов ведения журнала, которые по-разному работают с множеством разных источников / мест назначения. Теперь я хочу применить эти регистраторы к множеству различных функций в качестве декоратора, и мне нужен только один регистратор каждого типа, запускаемый для множества различных функций. Т.е. Один, который присоединяется к текстовому файлу, один печатает stdout, один печатает stderr, а другой устанавливает трассировку для pdb. Тогда все они будут запускаться по-разному на разных уровнях ведения журнала. Для меня лесозаготовка всегда казалась естественным кандидатом в синглтоны.   -  person theheadofabroom    schedule 20.07.2011
comment
@Cat В любом случае, вопрос о том, являются ли синглтоны хорошей идеей, никогда не стоял. Вопрос должен был заключаться в том, как их лучше всего реализовать. Я предполагаю, что мой вопрос больше не будет открыт, что очень досадно. Во всяком случае, я бы не назвал вас грубым - я думаю, что оживленные дебаты всегда интересны, это скорее не суть stackoverflow.   -  person theheadofabroom    schedule 20.07.2011
comment
@BiggAl Вы скопировали неправильный код из моего сообщения. Это мои исправления для метода № 2, а не моего метакласса. Я видел, что ты хочешь бегать __init__ каждый раз; Я добавил этот код в свой пост. Я также прокомментировал метод №4. Резюме: плохо, чтобы класс был собственным базовым классом.   -  person agf    schedule 27.07.2011
comment
@agf Приветствую вас за редактирование, так как я не видел проблемы, так как метод, который я использовал, был вашей настройкой. Честно говоря, кроме s / object / type / g, я не вижу проблемы - в вашем dict используется всеми классами Singleton type, да? И метод __new__ выбирает правильный, используя класс в качестве своего хэша, создавая его, если это еще не сделано. В моей версии каждый класс имел доступ только к своему собственному экземпляру или базовому экземпляру, если он был создан, а затем метод __new__ проверяет, является ли он экземпляром правильного класса. Это наверняка разделяет меньше государственной эргономики, поэтому более желательно?   -  person theheadofabroom    schedule 29.07.2011
comment
@BiggAl Другая проблема - __call__ против __new__. В метаклассе __new__ означает, что класс новый, а не когда экземпляр новый. Вы хотите, чтобы метод вызывался каждый раз, когда кто-то пытается создать новый экземпляр, поэтому вам нужно использовать __call__. Даже если вы исправите свою версию для использования type и super вместо object, она не будет создавать синглтоны, если вы не используете __call__ - проверьте это. _instance против _instances не было проблемой. Ваш путь может быть даже лучше, я должен об этом подумать.   -  person agf    schedule 29.07.2011
comment
Круто, я этого не заметил. Метаклассы кажутся очень мощными, знаете ли вы какие-либо ресурсы, которые могли бы рассказать о них немного больше? Я нашел этот вопрос, но знаете ли вы о каких-либо других замечательных ресурсах?   -  person theheadofabroom    schedule 30.07.2011
comment
Ознакомьтесь с другими вопросами SO, которые возникают для Metaclass Python. Прочтите все docs.python.org/reference/datamodel.html. Недавно я видел bitshaq.com/2011/07/ 14 / basic-intro-to-python-meta-programming, которая выглядит неплохо. Прочтите secure.wikimedia.org/wikipedia/en/wiki/Metaclass. Просто продолжайте думать, что это класс, экземпляры которого являются определениями классов.   -  person agf    schedule 31.07.2011
comment
Настроение против вывески - это худшее из проявлений культа карго. То же самое и с людьми, которые слышат (немногие удосужились прочитать) оператор Goto, который считается вредным, и думают, что goto являются признаком плохого кода независимо от контекста.   -  person Hejazzman    schedule 30.11.2015
comment
Почему я не могу сказать, что класс, имеющий только методы класса и атрибуты класса, является одноэлементным классом?   -  person deepak    schedule 21.06.2016
comment
@deepak, потому что, строго говоря, это статический класс, или настолько близкий, насколько это возможно в python. Вы по-прежнему можете добавлять атрибуты экземпляра, если не запретите создание экземпляра, после чего он становится более статичным.   -  person theheadofabroom    schedule 21.06.2016
comment
@theheadofabroom, но в python вы всегда можете использовать setattr (объект, имя, значение) для добавления переменной экземпляра, я не вижу конкретной причины, почему следует использовать любой из этих методов, описанных ниже, для реализации синглтона, когда сам python по умолчанию поддерживает Это. Сообщите мне, если я что-то упустил.   -  person deepak    schedule 21.06.2016
comment
С синглтоном у вас есть только один экземпляр, следовательно, вы можете установитьattr без каких-либо несоответствий. Вот почему я сказал, что для статического класса вам нужно будет предотвратить создание экземпляра   -  person theheadofabroom    schedule 21.06.2016
comment
@agf Мне нужно сделать синглтон на основе аргументов вместо имен классов. Я разместил здесь вопрос о SO - ›stackoverflow.com/questions / 39033946 /   -  person Pankaj Singhal    schedule 19.08.2016
comment
Привет, спасибо за подробный пост. Я новичок в программировании шаблонов и в Python, и меня удивляет, что, хотя метод 2 кажется наиболее известным (он везде), почти никто не упоминает, что, несмотря на то, что создается только один объект, init __ () вызывается каждый раз, когда в любом месте используются Singleton () или MyClass (). Я не пробовал, но, AFAIK, это верно и для всех других методов. Это вряд ли кажется желательным при реализации синглтона, или я что-то упускаю? Конечно, решение состоит в установке атрибута, чтобы избежать двойного выполнения __init. Просто любопытно   -  person chrisvp    schedule 04.09.2016
comment
@chrisvp действительно, вам, вероятно, нужна какая-то форма защиты вокруг init, хотя я бы также сказал, что вы очень редко хотите синглтон, за годы, прошедшие с момента написания этого вопроса, мне действительно нужен синглтон только дважды, и один из них был удален, когда я сделал вещи более функциональными, и поэтому это общее состояние стало нежелательным   -  person theheadofabroom    schedule 04.09.2016
comment
Как бы вы убить объект 3-м методом?   -  person Lior Magen    schedule 05.12.2016
comment
@LiorMagen Вы бы не ... как вы узнали, что он не будет снова создан?   -  person theheadofabroom    schedule 05.12.2016
comment
@theheadofabroom Я бы не стал, это будет актуально, если я захочу удалить существующий объект и создать вместо него новый. Нет способа сделать это?   -  person Lior Magen    schedule 06.12.2016
comment
Как насчет реализации его в виде модуля?   -  person gaurav parashar    schedule 31.01.2018
comment
Метод 3 решил мою проблему с потоками: class MyClass(threading.Thread, metaclass=Singleton):   -  person tammoj    schedule 06.05.2019
comment
@hejazzman далее, метод метакласса отрицает причину, по которой синглтоны считаются плохими. Для тестирования вы можете убрать одноэлементность объекта, чтобы у вас больше не было общего состояния.   -  person JeffCharter    schedule 15.11.2019
comment
@JeffCharter Я не уверен, что это единственная причина, по которой синглтоны иногда считаются антипаттерном, но вы правы в том, что это позволяет вам свести на нет эту конкретную проблему. Во многих случаях проблема заключается не в том, что вы используете синглтон, а в том, что в итоге получается архитектура, в которой синглтон полезен, часто указывает на другие архитектурные проблемы. Очевидно, это практическое правило, а не что-то твердое и быстрое, поэтому я все еще поддерживаю полезность своего вопроса все эти годы;)   -  person theheadofabroom    schedule 16.11.2019
comment
Пила (следующая). Тем не менее, еще один способ реализации шаблона singleton - помимо ответов на этот пост. Надеюсь, это поможет! tutorialspoint.com/python_design_patterns/   -  person dvlper    schedule 16.10.2020
comment
Есть ли какие-либо недостатки в реализации одноэлементного класса как обычного класса, а затем просто сообщить пользователям, что создавать несколько его экземпляров не очень полезно? Как и в духе мы все здесь взрослые по согласию.   -  person Jeyekomon    schedule 17.06.2021
comment
@Jeyekomon только постольку, поскольку без принудительного исполнения легко совершать ошибки, которые затем часто бывает трудно отладить   -  person theheadofabroom    schedule 19.06.2021


Ответы (27)


Используйте метакласс

Я бы порекомендовал метод 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
comment
Нет, одиночные игры никогда не годятся. Ведение журнала может быть хорошим кандидатом на роль глобального (сколь бы ужасного) оного, но, конечно, не синглтона. - person Cat Plus Plus; 23.07.2011
comment
Моя проблема с синглтонами - это глупая посылка только одного экземпляра. Это и тонна проблем с безопасностью потоков. И скрытие зависимости. Глобальные объекты - это плохо, а синглтоны - это просто глобальные объекты с большим количеством проблем. - person Cat Plus Plus; 23.07.2011
comment
@agf Я, вероятно, приму и награду этот ответ, но уделю немного времени другим. Раньше я не работал с метаклассами, и хотя у меня был быстрый поиск в Google, ответ может быть более ценным для будущих читателей, если можно будет дать более подробное объяснение того, как они используются, на что они влияют и на какие подводные камни стоит обратить внимание. из за. - person theheadofabroom; 24.07.2011
comment
@BiggAl - Вы неправильно скопировали мой код в свой пост - это мои исправления к №2, а не мой метакласс. Кроме того, вы упустили важную проблему с конкретной реализацией декоратора, возвращающего класс. - person agf; 28.07.2011
comment
@Cat Есть очень хорошее применение синглтонов. Ленивое создание аппаратных модулей (особенно в однопоточных приложениях) является одним из них (но также существуют потокобезопасные синглтоны). - person Paul Manta; 06.08.2011
comment
@Cat На ум пришло другое использование (это пример из игры, которую я пишу для банкомата). У меня есть класс EventManager, написанный на C ++ и доступный для Python. Мне нужны оба языка для постановки событий в очередь в одном и том же диспетчере. Я мог бы каким-то образом передать один и тот же экземпляр менеджера в качестве параметра функциям на обоих языках, но для этого потребовалось бы много работы, и преимущества были бы буквально нулевыми. На самом деле, это может затруднить соблюдение кода ... - person Paul Manta; 06.08.2011
comment
@agf, почему при реализации синглтона через метакласс вы использовали call, а не new? и какая разница? - person Alcott; 20.09.2011
comment
Также см. stackoverflow.com/questions/31875/ для большего количества синглтонов, если вы еще этого не нашли. - person agf; 20.09.2011
comment
@martineau Это простая ошибка. Python 3 улучшил синтаксис метакласса, сделав его аргументом ключевого слова: Logger(metaclass = Singleton). - person agf; 10.12.2012
comment
Вместо class Singleton(_Singleton('SingletonMeta', (object,), {})): pass вы можете удалить один класс, используя присвоение: Singleton = _Singleton('Singleton', (object,), {}). - person FichteFoll; 01.10.2014
comment
@agf: Могу я попросить вас уточнить, что, по вашему мнению, является правильным способом создания экземпляра внутреннего состояния класса Logger при использовании метакласса Синглтон? В частности, я начинаю болтать о том, лучше ли реализовать Logger.__init__ для ленивого создания экземпляра состояния Logger или инициализировать состояние Logger как член класса (не ленивый, а более понятный). - person Malina; 24.02.2015
comment
Лучше использовать Singletone._instances вместо cls._instances в функции __call__? В противном случае classes из Singletone может определять свою собственную переменную класса _instances и конфликтовать с Singletone. - person Eastsun; 24.05.2015
comment
@Eastsun Если это вас беспокоит, то правильнее всего будет использовать __instances. См. документы по изменению имен для получения дополнительной информации. Это предотвратит его случайное переопределение вне зависимости от имени класса. - person agf; 24.05.2015
comment
@Christophe, рассмотрите также наследование. Возможно, одноэлементное приложение и множество различных типов приложений, связанных с ним (LinuxApp, MobileApp, DesktopApp) и т. Д. - person Jonathan; 03.09.2015
comment
@agf Я хочу вернуть объект на основе аргументов, а не на основе имени класса. Может ли кто-нибудь предоставить решение моего вопроса, который я разместил здесь - ›stackoverflow.com/questions/39033946/ - person Pankaj Singhal; 19.08.2016
comment
@agf вы сказали, что _instances нужно вызывать на уровне cls, и сказали, что это не определено в cls namespace. Как такое возможно? в каком пространстве имен определен атрибут cls._instances? - person Chen A.; 24.09.2017
comment
@Vinny Я не совсем уверен, что понимаю ваш вопрос, не стесняйтесь уточнить. В моем примере я говорю, что вы не можете случайно сделать self._instances и переопределить его в экземпляре, например, Logger. Вам нужно будет сделать Logger._instances или _4 _._ экземпляров или что-то подобное. Поэтому, если другие классы в множественном наследовании также используют имя _instances, вы все равно будете защищены от случайного переопределения. Также, как я упоминал в комментарии, если вы удвоите подчеркивание, Python еще больше скроет переменную и сделает ее еще безопаснее. - person agf; 24.09.2017
comment
Кроме того, @Vinny - это сообщение было связано с каким-то сообщением в блоге? Похоже, в последнее время он набирает много голосов; до этого я совсем забыл об этом. - person agf; 24.09.2017
comment
@agf Получил сейчас, по истечении некоторого срока. Я сомневался в том, где фактически сохраняется переменная _instances (пространство имен), и я понимаю, что вы имеете в виду в своем комментарии. Я дошел до этого по другому вашему ответу, где вы связались с этой веткой для дальнейшего объяснения. Об этом достойно упоминания в блоге :-) - person Chen A.; 24.09.2017
comment
@MichaelBerdyshev Это должно быть в Python 3. - person agf; 18.12.2017
comment
Как насчет использования класса Singleton для управления аппаратными компонентами? Представьте, что у вас есть ЖК-дисплей (например, на RPi), и вы написали небольшой скрипт на Python под названием displayManager.py. Я думаю, что это полностью вписывается в наиболее распространенную причину, по которой здесь упоминается неявное определение общего состояния. Если многие другие компоненты используют этот ЖК-дисплей, они каким-то образом используют общее состояние. Почему бы не сделать это через класс Singleton. - person user2128702; 25.08.2018
comment
Дополнительный вопрос: следует ли использовать weakref.WeakValueDictionary для _instances поэтому что экземпляры автоматически удаляются, когда они больше не используются? Представьте себе синглтон для оболочек DLL, в котором экземпляры можно различать по пути к DLL. - person astafan8; 07.03.2019
comment
Я думаю, вы можете получить тот же эффект, определив базовый класс BaseSingleton(metaclass=Singleton), а затем используя Logger(BaseSingleton). Одно из возможных преимуществ - это все, что мне нужно, определенное с помощью BaseSingleton, вместо того, чтобы иметь как базовый класс, так и метакласс. Имеет ли это смысл? - person M Juckes; 06.04.2021
comment
Я нашел синглтон полезным при создании модуля для Syllogism. Я имею в виду, что python bool уже синглтон. - person Inyoung Kim 김인영; 26.05.2021

class Foo(object):
     pass

some_global_variable = Foo()

Модули импортируются только один раз, все остальное зацикливается. Не используйте синглтоны и старайтесь не использовать глобальные переменные.

person Cat Plus Plus    schedule 20.07.2011
comment
почему вы сказали «Не использовать синглтоны»? Какой-либо причине? - person Alcott; 20.09.2011
comment
Это не сработает, если нужно мариновать синглтон. Используя приведенный вами пример: s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False - person dividebyzero; 20.01.2012
comment
@dividebyzero: оператор is проверяет равенство указателя. Я был бы довольно удивлен - если бы назвал это ошибкой - если pickle.loads вернул бы ссылку на уже существующий объект, а не ссылку на вновь созданный. Таким образом, проверка того, s is s1 ничего не говорит вам о пригодности использования модулей в качестве синглтонов. - person Jonas Kölker; 17.02.2014
comment
@ JonasKölker pickle.loads() это уже делает, например для экземпляров bool и NoneType. pickle.loads(pickle.dumps(False)) is False дает True - person Dan Passaro; 28.04.2014
comment
@ leo-the-manic: справедливая точка; однако это всего лишь побочный эффект того, что Python интернирует объекты True, False и None, и не имеет ничего общего с кодом, стоящим за pickle.loads. Кроме того, это безопасно делать только с объектами, доступными только для чтения. Если бы pickle.loads вернул ссылку на уже существующий изменяемый объект - такой как модуль - это было бы ошибкой. (И поэтому я придерживаюсь своего предположения, что пример кода diverbyzero ничего не доказывает.) - person Jonas Kölker; 03.05.2014
comment
@Cat Plus Plus Думаю, это значительно увеличит время импорта модуля. Всегда лучше лениться создавать объект, когда это необходимо. - person deepak; 21.06.2016
comment
Это работает, только если весь импорт выполняется одинаково. import project.module и import .module выполнят код дважды. - person Jan DB; 11.10.2017
comment
Если some_global_variable используется в some_module.py и это был тестируемый модуль, возможно ли имитировать some_global_variable? - person Naveen; 24.04.2018
comment
должно Foo быть __Foo, чтобы пользователи случайно не использовали это? - person joel; 04.08.2019
comment
Это должен был быть принятый ответ, учитывая специфику этого вопроса. Примечательно, что именно так фактическая стандартная библиотека Python реализует модуль ведения журнала. См. Код: github.com/python/cpython/blob/ master / Lib / logging / __ init__.py. И он по-прежнему настраивается, поэтому, похоже, исходный запросчик не хотел, чтобы глобальные объекты инициализировались во время импорта модуля. - person Adam Acosta; 03.01.2021
comment
Это очень неоднозначный ответ, могу ли я предложить его полностью отредактировать в свете обсуждения. - person matanster; 01.03.2021

Используйте модуль. Импортируется только один раз. Определите в нем несколько глобальных переменных - они будут «атрибутами» синглтона. Добавьте несколько функций - «методы» синглтона.

person warvariuc    schedule 20.07.2011
comment
Так что в итоге вы получите ... Не класс. Вы не можете использовать его как класс, вы не можете основывать на нем другие классы, вы используете синтаксис импорта, и внезапно вы теряете все преимущества ООП ... - person theheadofabroom; 25.07.2011
comment
если вы можете основывать на нем другие классы, то это может быть не синглтон. вы можете создать один из производных классов, но также и один из базовых классов, но производный класс также является членом базового класса, и у вас есть два базовых класса, какой из них вы должны использовать? - person SingleNegationElimination; 25.07.2011
comment
Это не работает с модулями. В моем основном модуле я установил значение. Затем я ссылаюсь на него в другом модуле и на его null. Вздох. - person Paul Kenjora; 30.03.2017
comment
@PaulKenjora У вас должна быть ошибка в вашем коде. Если вы определяете глобальную переменную в модуле, при доступе к ней из другого модуля она должна иметь значение. - person warvariuc; 30.03.2017
comment
У него есть значение, но когда я его меняю, оно не сохраняется. В итоге я использовал класс со свойствами в работающем модуле. Простые типы, такие как глобальные, у меня не работали (они теряли значения, как только изменилась область видимости). - person Paul Kenjora; 13.04.2017
comment
@theheadofabroom, ты мог бы _1 _... переосмыслить ООП, мой друг! хахаха - person polvoazul; 07.03.2018
comment
Как можно инициализировать одноэлементный объект с аргументами в модуле? - person nn0p; 10.06.2019
comment
@ nn0p довольно старый, но вы можете ввести третий модуль config.py, в котором вы можете назначить переменную: import c c.param = 10 import b b будет импортировать c и использовать c.param, и это будет 10, что не идеально для некоторых людей, но работает - person igorgue; 23.09.2020

Вероятно, вам никогда не понадобится синглтон в Python. Просто определите все свои данные и функции в модуле, и у вас будет де-факто синглтон:

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 My_Singleton(object):
        def foo(self):
            pass

    my_singleton = My_Singleton()

Использовать:

    from mysingleton import my_singleton
    my_singleton.foo()

где mysingleton.py - ваше имя файла, в котором определен My_Singleton. Это работает, потому что после первого импорта файла Python не выполняет код повторно.

person Alan Dyke    schedule 10.12.2014
comment
В основном это правда, но иногда этого недостаточно. Например. У меня есть проект, в котором необходимо регистрировать экземпляры многих классов на уровне DEBUG. Мне нужны параметры командной строки, анализируемые при запуске, чтобы установить указанный пользователем уровень ведения журнала до создания экземпляров этих классов. Создание экземпляров на уровне модуля делает это проблематичным. Возможно, я мог бы тщательно структурировать приложение так, чтобы все эти классы не импортировались, пока обработка CLI не будет завершена, но естественная структура моего приложения более важна, чем догматическая приверженность синглтонам - это плохо, поскольку их можно сделать довольно чисто. - person mikenerone; 28.08.2016
comment
если бы вы протестировали свой код при исправлении my_singleton, было бы это возможно? поскольку этот my_singleton может быть создан в другом модуле. - person Naveen; 24.04.2018
comment
@Naveen - my_singleton - это отдельный объект. Если вы исправите его, это изменение повлияет на все будущие ссылки, даже в других модулях. - person Alan Dyke; 24.04.2018
comment
В некоторых случаях это может сработать, но иногда важна отложенная инициализация. В моем случае инициализация стоит 400 мс, поэтому я не хочу брать на себя это только потому, что я импортировал модуль. Это должно происходить только тогда, когда синглтон действительно нужен. - person joanis; 08.04.2021
comment
@joanis. Мы согласны с тем, что не существует идеального решения для всех возможных вариантов использования. Возможно, вы все еще можете использовать ленивую инициализацию для трудоемкой части вашего кода, не помещая ее в свой конструктор. Или, может быть, вам нужно одно из других более сложных предложений на этой странице. - person Alan Dyke; 08.04.2021
comment
Действительно, я выбрал решение на основе метаклассов. Это помогло мне. Хотя мне это кажется многословным. В C ++ я использовал X* theX = NULL; X* getTheX() {if (theX == NULL) theX = new X(); return theX;}. Не очень питонический, но немного короче. Я мог бы переключиться на это с помощью None и if theX is None вместо кода, который, как мне кажется, будет легче читать в будущем. - person joanis; 08.04.2021

Вот вам краткое изложение:

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

Ваш объект с нетерпением будет инстанциирован. Это может быть то, что вы хотите, а может и не быть.

person Jonas Kölker    schedule 19.10.2013
comment
Зачем нужно знать, как использовать класс синглтона? Просто используйте одноэлементный объект .. - person Tolli; 18.10.2014
comment
Это не шаблон singleton, поэтому IMO функция должна называться по-другому. - person GingerPlusPlus; 31.12.2014
comment
Википедия: шаблон singleton - это шаблон проектирования, который ограничивает создание экземпляра класса одним объектом. Я бы сказал, что мое решение именно так. Хорошо, я думаю, можно было бы сделать wat2 = type(wat)(), но это питон, мы все взрослые по согласию и все такое. Вы не можете гарантировать, что будет только один экземпляр, но вы можете гарантировать, что если люди сделают второй, он будет выглядеть некрасиво и - если они порядочные, порядочные люди - как предупреждение подпишитесь на них. Что мне не хватает? - person Jonas Kölker; 02.01.2015
comment
если вы действительно ищете однострочное решение, попробуйте модуль Python как синглтон, который на самом деле является решением с нулевой строкой. - person Sławomir Lenart; 05.04.2021

Ознакомьтесь с вопросом о переполнении стека Есть ли простой и элегантный способ определения синглтонов в Python? с несколькими решениями .

Я настоятельно рекомендую посмотреть доклады Алекса Мартелли о шаблонах проектирования в Python: часть 1 и часть 2. В частности, в части 1 он говорит об одиночных объектах / объектах общего состояния.

person Anton    schedule 20.07.2011
comment
Хотя это не совсем ответ на мой вопрос, ресурсы, на которые вы указываете, очень полезны. Я неохотно даю вам +1 - person theheadofabroom; 25.07.2011

  • Если кто-то хочет иметь несколько экземпляров одного и того же класса, но только если аргументы или kwargs разные, можно использовать сторонний пакет python Удобные декораторы (пакет decorators).
  • Ex.
    1. If you have a class handling serial communication, and to create an instance you want to send the serial port as an argument, then with traditional approach won't work
    2. Используя вышеупомянутые декораторы, можно создать несколько экземпляров класса, если аргументы разные.
    3. Для тех же аргументов декоратор вернет тот же экземпляр, который уже был создан.
>>> 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
>>>
person Siddhesh Suhas Sathe    schedule 01.08.2019
comment
Необходимость сделать этот синглтон - вот что привело меня к этому вопросу. Очень признателен! Я попробовал pip install handy-decorators и получил ERROR: Could not find a version that satisfies the requirement handy-decorators. Любое предложение? - person Ben Kovitz; 08.04.2021
comment
Я продолжил и скопировал исходный код с - person Ben Kovitz; 08.04.2021

Вот моя собственная реализация синглтонов. Все, что вам нужно сделать, это украсить класс; чтобы получить синглтон, вам нужно использовать метод 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)
person Paul Manta    schedule 20.09.2011
comment
Это не настоящий синглтон. SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)()) должен печатать True в истинном синглтоне; с распечатками вашего кода False - person GingerPlusPlus; 31.12.2014
comment
@GingerPlusPlus Я знал о нескольких ограничениях, но не о том, на что вы указали. Спасибо, что упомянули об этом. К сожалению, на данный момент у меня нет времени размышлять о решении этой проблемы. - person Paul Manta; 31.12.2014
comment
Я даю этому ответу –1 на основании комментария @ GingerPlusPlus. Если вы когда-нибудь это исправите, дайте мне знать, и я удалю –1. - person Ben Kovitz; 09.04.2021

Метод 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», но я этого не пробовал (я пробовал код, как показано).

person Tim    schedule 24.07.2013
comment
конечно, это не метакласс - в python3, чтобы использовать метакласс для создания MyClass, вы бы сделали class MyClass(metaclass=Singleton) - person theheadofabroom; 30.07.2013
comment
Ссылка mikewatkins.ca (фактически) не работает. - person Peter Mortensen; 15.03.2016

Я брошу свой на ринг. Это простой декоратор.

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. Это включает в себя отсутствие необходимости использовать метакласс для вашего класса (обратите внимание, что метакласс выше находится на фабрике, а не в «реальном» классе).
  • Он не полагается ни на что от обезьяны-исправления.
  • It's transparent to callers:
    • Callers still simply import YourClass, it looks like a class (because it is), and they use it normally. No need to adapt callers to a factory function.
    • То, что YourClass() создает экземпляры, по-прежнему является истинным экземпляром реализованного вами YourClass, а не прокси-сервером любого типа, поэтому никаких шансов на побочные эффекты в результате этого нет.
    • isinstance(instance, YourClass) и подобные операции по-прежнему работают, как ожидалось (хотя этот бит требует abc, что исключает Python ‹2.6).

Мне приходит в голову один недостаток: методы классов и статические методы реального класса не могут быть прозрачно вызваны через фабричный класс, скрывающий его. Я использовал это достаточно редко, чтобы никогда не столкнуться с этой потребностью, но это было бы легко исправить, используя настраиваемый метакласс на фабрике, который реализует __getattr__() для делегирования доступа к атрибутам all-ish реальному классу.

Связанный шаблон, который я на самом деле нашел более полезным (я не говорю, что такого рода вещи требуются вообще очень часто), является «Уникальным» шаблоном, при котором создается экземпляр класса с одинаковыми аргументами. в возвращении того же экземпляра. Т.е. «синглтон на аргументы». Вышеупомянутое хорошо адаптируется к этому и становится еще более кратким:

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% случаев, ЯГНИ.

person mikenerone    schedule 27.08.2016

Использовать атрибут функции также очень просто

def f():
    if not hasattr(f, 'value'):
        setattr(f, 'value', singletonvalue)
    return f.value
person Ruben Decrop    schedule 02.07.2020

Я предпочитаю это решение, которое я нашел очень ясным и простым. Например, он использует двойную проверку, если какой-то другой поток уже создал его. Еще одна вещь, которую следует учитывать, - это убедиться, что десериализация не создает никаких других экземпляров. https://gist.github.com/edediver/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))
person Andrei R.    schedule 26.10.2020
comment
Простите за незнание, но зачем вам дважды проверять __singleton_instance? Не могли бы вы просто всегда брать __singleton_lock, а потом проверять только один раз? - person theheadofabroom; 27.10.2020
comment
Как я упоминал ранее, он нам нужен, чтобы убедиться, что пока мы выполняем if и используем блокировку, какой-то другой поток еще не создал этот экземпляр en.wikipedia.org/wiki/Double-checked_locking Это довольно популярная концепция, которую задают на собеседовании :) - person Andrei R.; 27.10.2020
comment
Но, конечно же, затраты на получение неоспоримой блокировки достаточно низки, и если бы она была значительной, вам было бы лучше реализовать ее на C? IIRC стоимость получения блокировки составляет примерно половину стоимости вызова функции, поэтому лучшая оптимизация здесь может заключаться в том, чтобы избежать использования диспетчера контекста и получить блокировку вручную. Если это ненужная оптимизация, я бы сказал, что двойная проверка еще более важна. - person theheadofabroom; 27.10.2020
comment
Двойная проверка - это не оптимизация, она предназначена для того, чтобы убедиться, что мы не создаем два экземпляра синглтона. Также стоит отметить, что эти проверки будут выполняться только один раз при первой инициализации. После этого он просто возвращает экземпляр. Так что любые оптимизации будут бессмысленными. - person Andrei R.; 28.10.2020
comment
Это то, чего я, кажется, не понимаю. Конечно, пока вы проверяете, удерживая замок, вам нужно проверять только один раз? Вот для чего нужна блокировка, чтобы синхронизировать доступ. - person theheadofabroom; 28.10.2020
comment
Нужна ли вообще двойная проверка для Python? этот шаблон касается не затрат или оптимизации, а модели памяти среды. Дело в том, не обеспечивает ли GIL правильную блокировку при создании объекта? Есть языки, в которых использование шаблона двойной блокировки неверно, так как есть переупорядочивание, к тому же оно вам не всегда нужно. - person Luiz Felipe; 27.05.2021

Ну, кроме согласия с общим предложением 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
person Guard    schedule 25.07.2011
comment
В чем смысл атрибута _sealed? Насколько я понимаю, это ничего не делает? Что-то меня беспокоит по этому поводу, что говорит, что он не должен работать хорошо ... Позже на этой неделе я проведу несколько сравнительных тестов. - person theheadofabroom; 25.07.2011
comment
_sealed гарантирует, что ваш init будет запущен только один раз; Я не вижу смысла, почему он должен работать хуже, чем обычный функционально-подобный декоратор - функция выполняется только один раз для каждого класса и возвращает новый унаследованный класс. - person Guard; 25.07.2011
comment
Кстати, ваше редактирование содержит вкладки, которые нарушают отступы. Вы также говорите «мы создаем 2 класса» - вы имеете в виду, что мы создаем «1 дополнительный класс»? - person Guard; 25.07.2011
comment
Да, я имел в виду один дополнительный класс. Я намерен включить в __init__ материал, который будет вызываться каждый раз при его инициализации. Просто простая инициализация в class.method. что касается отступов - вы использовали табуляции и пробелы - я исправил большую часть этого, но, похоже, пропустил один, если вы хотите его получить (просто проверьте журнал редактирования) - person theheadofabroom; 26.07.2011
comment
re init: конечно, решать вам, я просто попытался имитировать одноэлементное поведение на других языках, где код конструктора (который не совсем init, но очень близок к его смысл) вызывается только один раз, если вы хотите, чтобы init вызывался каждый раз, просто убейте все ссылки на _sealed re пробелы / вкладки - ну, тогда мой emacs нуждается в исправлении. в любом случае, выше это исправленная версия - person Guard; 26.07.2011
comment
@Guard Я прокомментировал ваш метод в своем посте. По сути, классу не нужно знать / заботиться о том, что это синглтон. Он должен иметь возможность настраивать __new__ и безопасно вызывать super() - что ваш метод не позволяет, поскольку вызов super() для метода с тем же именем будет рекурсивным. - person agf; 27.07.2011
comment
@agf, я думаю, вы немного преувеличиваете насчет имен, связанных с цепочкой наследования: gist.github.com/1108823 Мы просто меняем имя строки, а не переменную класса - person Guard; 27.07.2011
comment
Дело не в названии класса. Попробуйте свой собственный код: MyClass.__init__(text) вызовите super(MyClass, self).__init__(text) или добавьте MyClass.__new__(cls) метод и пусть он вызовет return super(self, MyClass).__new__(cls). Вы будете повторять бесконечно. - person agf; 27.07.2011
comment
@agf Не могли бы вы объяснить, почему super(MyClass, self).__init__(text) повторяется? Насколько я понимаю, super(MyClass, self) должно быть object. - person ovgolovin; 05.08.2012
comment
@agf Я задал для этого специальный вопрос: stackoverflow.com / questions / 11811935 / - person ovgolovin; 05.08.2012

Как насчет этого:

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)() (который также возвращает тот же экземпляр).

person Tolli    schedule 17.05.2014
comment
Вы не определяете класс для использования в качестве объекта. - person 0xc0de; 16.10.2014
comment
Каждый раз, когда вы вызываете type(MySingleton)(), вызывается MySingleton.__init__(), и объект инициализируется несколько раз; вы можете исправить это, написав cls.__init__ = lambda self: pass в вашем singleton. Кроме того, переопределение cls.__call__ кажется бессмысленным и даже вредным - __call__, определенный в этом контексте, используется, когда вы вызываете MySingleton(any, list, of, arguments), а не когда вы вызываете type(MySingleton)(any, list, of, arguments). - person GingerPlusPlus; 31.12.2014
comment
@GingerPlusPlus, Спасибо, что указали, что __init__() снова вызывается при выполнении type(MySingleton)(). Предложенное вами решение (добавление cls.__init__ = lambda self: pass) дает синтаксическую ошибку, потому что последняя часть лямбда-выражения должна быть выражением, а не утверждением. Однако добавление cls.__init__ = lambda self: None работает, поэтому я добавил это к своему ответу. - person Tolli; 02.01.2015
comment
@GingerPlusPlus, по поводу использования __call__. я намеревался заставить оба type(MySingleton)() и MySingleton() возвращать экземпляр. Так что он делает то, что я хотел. Вы можете думать о MySingleton как о типе синглтона или как о экземпляре синглтона (или обоих). - person Tolli; 02.01.2015

Код основан на ответе Толли.

#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)

Объяснение:

  1. Создать новый класс, унаследованный от заданного cls
    (он не изменяет cls на случай, если кто-то захочет, например singleton(list))

  2. Создать экземпляр. Прежде чем переопределить __new__, это так просто.

  3. Теперь, когда мы легко создали экземпляр, переопределяет __new__, используя метод, определенный момент назад.
  4. Функция возвращает instance только тогда, когда это то, что ожидает вызывающий, иначе вызывает TypeError.
    Условие не выполняется, когда кто-то пытается наследовать от декорированного класса.

  5. # P6 #
    # P7 #

Посмотрите, как это работает в Интернете

person GingerPlusPlus    schedule 30.12.2014

Это немного похоже на ответ fab, но не совсем то же самое.

Одноэлементный контракт не требует, чтобы мы могли вызывать конструктор несколько раз. Поскольку синглтон должен создаваться только один раз, разве не следует видеть, что он создается только один раз? «Подмена» конструктора, вероятно, ухудшает читаемость.

Итак, мое предложение таково:

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()

Примечания:

  1. Если вы не подклассифицируете объект в python2.x, вы получите класс старого стиля, который не использует __new__
  2. При декорировании __new__ вы должны украсить @classmethod, иначе __new__ будет несвязанным методом экземпляра
  3. Возможно, это можно улучшить с помощью метакласса, так как это позволит вам сделать the свойство уровня класса, возможно, переименовав его в instance
person mike rodent    schedule 21.02.2016
comment
Хотя это немного другая интерпретация одноэлементного шаблона, я почти уверен, что он все еще действителен, хотя у меня может возникнуть соблазн использовать __new__, а не __init__, поскольку он действует исключительно на атрибуты класса, и это не позволяет ему ненадолго быть вторым экземпляром . Разница между этим и методом 2 заключается в том, возвращает ли попытка инициализации более одного раза один экземпляр или возникает исключение. Я думаю, я счастлив, что либо удовлетворяет шаблону синглтона, один проще в использовании, а другой более явный, что это синглтон. - person theheadofabroom; 21.02.2016
comment
Очевидно, что использование имени класса в __init__ предотвращает создание подклассов, но, хотя это упрощает задачу, это не требуется. - person theheadofabroom; 21.02.2016
comment
Спасибо ... ах, да, мгновенный второй экземпляр до того, как будет сгенерировано исключение. Я изменил __init__ так, чтобы, надеюсь, он был подклассифицирован ... - person mike rodent; 21.02.2016
comment
Круто, the, вероятно, может выиграть от использования метода класса по аналогичным причинам - person theheadofabroom; 21.02.2016
comment
Да, ты прав. Затем у вас может быть синглтон подкласса SuperElvis и (например) синглтон подкласса ImaginaryElvis ... и они могут сосуществовать. См. Дополнительные мысли. Пожалуйста, не стесняйтесь улучшать мой код. - person mike rodent; 21.02.2016
comment
Спасибо за улучшения кода. Только одна небольшая придирка / сомнение: создание instance поля класса, подобного этому, означает, что King.instance оценивается как None до создания синглтона, вместо того, чтобы вызывать исключение без атрибута ... Но неважно. Возможно, более желательно: я не знаю, какие аргументы применить ... - person mike rodent; 22.02.2016
comment
упс, также пришлось настроить вашу the версию, чтобы отразить использование поля класса - person mike rodent; 22.02.2016
comment
2017-12-23 голос против ... пожалуйста, будьте любезны, добавьте комментарий, чтобы объяснить свое мнение: это может быть полезно для меня, или вы можете обнаружить, что вы чему-то научились - person mike rodent; 23.12.2017

Один лайнер (я не горжусь, но он выполняет свою работу):

class Myclass:
  def __init__(self):
      # do your stuff
      globals()[type(self).__name__] = lambda: self # singletonify
person polvoazul    schedule 07.03.2018
comment
Это может сработать, если ваш класс не был импортирован ни в какие другие модули ... - person Aran-Fey; 24.10.2018
comment
Правда. Если вы можете заплатить стоимость инициализации класса, вы можете запустить Myclass () сразу после определения класса, чтобы избежать этого. - person polvoazul; 26.10.2018

Если вам не нужна ленивая инициализация экземпляра синглтона, тогда следующее должно быть простым и потокобезопасным:

class A:
    instance = None
    # Methods and variables of the class/object A follow
A.instance = A()

Таким образом, A - это синглтон, инициализируемый при импорте модуля.

person Serge Rogatch    schedule 01.01.2019

Возможно, я неправильно понимаю шаблон singleton, но мое решение настолько простое и прагматичное (питоническое?). Этот код преследует две цели

  1. Сделайте экземпляр Foo доступным везде (глобально).
  2. Может существовать только один экземпляр 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!
person buhtz    schedule 18.08.2019

После некоторой борьбы с этим я в конце концов пришел к следующему, так что объект конфигурации загружался бы только один раз при вызове из отдельных модулей. Метакласс позволяет хранить экземпляр глобального класса во встроенном 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>...
person Den-Jason    schedule 18.10.2019

Я порекомендую элегантное решение с использованием метаклассов

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

Как видно из вывода, создается только один объект.

person Dorcioman    schedule 19.08.2020

Плюсы

Это настоящий класс. Авто-магическим образом покрывает наследование. Использует метакласс по назначению (и заставил меня об этом знать) Минусы

Есть ли?

Это будет проблемой при сериализации. Если вы попытаетесь десериализовать объект из файла (pickle), он не будет использовать __call__, поэтому он создаст новый файл, вы можете использовать наследование базового класса с __new__, чтобы предотвратить это.

person Vintage    schedule 06.03.2021

Вы можете использовать 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
person omerfarukdogan    schedule 10.03.2021
comment
И что происходит, когда мы дважды вызываем MyClass? У меня здесь два разных адреса ... Кажется, не избежать новых экземпляров - person jossefaz; 11.07.2021

Я также предпочитаю синтаксис декоратора заимствовать из метакласса. Мои два цента:

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. Модули являются объектами. Вы даже можете передавать их в переменные и вставлять в другие классы.

person Boriel    schedule 07.12.2020

Я не могу вспомнить, где я нашел это решение, но я считаю его наиболее «элегантным» с моей точки зрения, не являющейся экспертом по 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

Почему мне это нравится? Никаких декораторов, метаклассов, множественного наследования ... и если вы решите, что больше не хотите, чтобы он был синглтоном, просто удалите метод __new__. Поскольку я новичок в Python (и в ООП в целом), я ожидаю, что кто-то объяснит мне, почему это ужасный подход?

person 2cynykyl    schedule 11.11.2014
comment
почему это ужасный подход? когда вы хотите создать еще один одноэлементный класс, вам нужно скопировать и вставить __new__. Не повторяйся. - person GingerPlusPlus; 30.12.2014
comment
Кроме того, почему ваш новый берет *args и **kwargs, а потом ничего с ними не делает? Передайте их в dict.__new__ следующим образом: dict.__new__(cls, *args, **kwargs). - person GingerPlusPlus; 31.12.2014
comment
Это будет вызывать метод __init__ каждый раз при вызове класса. Если бы ваш __init__ метод действительно что-то сделал, вы бы заметили проблему. Каждый раз, когда вы делаете SomeSingleton(), состояние вашего синглтона сбрасывается методом __init__. - person Aran-Fey; 24.10.2018

Скорее всего, это не то, что вы ищете. Я хотел синглтон в том смысле, что только этот объект имел свою идентичность для сравнения. В моем случае оно использовалось как Sentinel Value. Ответ на этот вопрос очень прост: создайте любой объект mything = object(), и по природе Python только этот объект будет иметь свою идентичность.

#!python
MyNone = object()  # The singleton

for item in my_list:
    if item is MyNone:  # An Example identity comparison
        raise StopIteration
person ThorSummoner    schedule 29.09.2016
comment
Я узнал, что модули на самом деле можно импортировать несколько раз, в этом случае это только локальный синглтон, который на самом деле не является синглтоном ни в каком качестве. - person ThorSummoner; 23.03.2018
comment
Не могли бы вы подробнее рассказать, как модуль можно импортировать несколько раз? Единственный раз, когда я видел, когда возникает исключение во время загрузки модуля, пользователь может загрузить модуль позже, но побочные эффекты уже возникли, поэтому некоторые действия могут быть выполнены во второй раз. - person sleblanc; 16.03.2019
comment
После полной загрузки модуля я не вижу другого способа запустить этот модуль, кроме как заставить интерпретатор сделать это с помощью eval или importlib.reload. - person sleblanc; 16.03.2019
comment
@sleblanc Я думал, что у меня есть сообщение по этой теме, но не смог его найти; вот лучший результат Google: stackoverflow.com/a/55910681/1695680 IIRC Мне это нужно, чтобы обезьяна исправила некорректное поведение в цепочке утверждений доверия ssl-сертификатов stdlib в python, когда несколько доменов использовались определенным образом, что позволяет некоторым модулям заменять свои ssl-интерфейсы на версию с monkeypatched, а другим - нет, и иметь возможность менять их местами по мере необходимости. Я не рекомендую патчить обезьяну, но я уверен, что такая возможность существует :) - person ThorSummoner; 01.11.2020

Это решение вызывает некоторое загрязнение пространства имен на уровне модуля (три определения, а не одно), но мне легко следовать.

Я хотел бы иметь возможность написать что-то вроде этого (ленивая инициализация), но, к сожалению, классы недоступны в теле их собственных определений.

# wouldn't it be nice if we could do this?
class Foo(object):
    instance = None

    def __new__(cls):
        if cls.instance is None:
            cls.instance = object()
            cls.instance.__class__ = Foo
        return cls.instance

Поскольку это невозможно, мы можем разделить инициализацию и статический экземпляр в

Жадная инициализация:

import random


class FooMaker(object):
    def __init__(self, *args):
        self._count = random.random()
        self._args = args


class Foo(object):
    def __new__(self):
        return foo_instance


foo_instance = FooMaker()
foo_instance.__class__ = Foo

Ленивая инициализация:

Жадная инициализация:

import random


class FooMaker(object):
    def __init__(self, *args):
        self._count = random.random()
        self._args = args


class Foo(object):
    def __new__(self):
        global foo_instance
        if foo_instance is None:
            foo_instance = FooMaker()
        return foo_instance


foo_instance = None
person Community    schedule 31.10.2016