Создание синглтона в Python

946

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

Pros

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

Cons

  • Хотя объекты, созданные с использованием MyClass (), будут настоящими одноэлементными объектами, сам MyClass является функцией, а не классом, поэтому вы не можете вызывать методы класса из него. Также на m = MyClass(); n = MyClass(); o = type(n)();потомm == n && m != o && n != o

Способ 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

Pros

  • Это настоящий класс

Cons

  • Множественное наследование - тьфу! __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

Pros

  • Это настоящий класс
  • Автомагически охватывает наследование
  • Использует __metaclass__для своей цели (и заставил меня осознать это)

Cons

  • Есть ли такие?

Метод 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

Pros

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

Cons

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

Способ 5: модуль

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

Pros

  • Простое лучше сложного

Cons

theheadofabroom
источник
12
Еще три метода: вместо этого используйте модуль (часто - как мне кажется, - это более подходящий шаблон для Python, но он немного зависит от того, что вы с ним делаете); сделайте один экземпляр и разберитесь с ним ( foo.xили, если вы настаиваете Foo.xвместо Foo().x); используйте атрибуты класса и методы static / class ( Foo.x).
Крис Морган
10
@ChrisMorgan: Если вы собираетесь использовать только классовые / статические методы, не стоит создавать классы.
Cat Plus Plus
2
@Cat: Эффект похожий, однако причины создания глобальной переменной могут быть чем угодно, в том числе незнанием. Почему один создает синглтон? Если вам нужно спросить, вы не должны быть здесь. Эта явность не только более питонна, но и делает обслуживание намного проще. Да, синглтоны - это синтаксический сахар для глобалов, но тогда классы - это синтаксический сахар для целого ряда неприглядных вещей, и я не думаю, что кто-то скажет вам, что вам всегда лучше без них.
глава кабинета
14
@BiggAl: Синглтоны не Pythonic, независимо от того, как вы их реализуете. В лучшем случае они являются признаком некорректного дизайна.
Cat Plus Plus
8
Настроение против подписи - худшее программирование культа грузов. То же самое происходит с людьми, которые слышат (мало кто даже на самом деле читает) «заявление Goto считается вредным» и думают, что gotos является признаком плохого кода независимо от контекста.
Hejazzman

Ответы:

658

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

Я бы порекомендовал метод № 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 являются именно такими синглетонами. Пока вы не помещаете состояние в свое перечисление, все в порядке, поэтому, пожалуйста, не делайте.)

К другим видам синглетонов, которые являются частично приемлемыми, относятся те, которые не влияют на выполнение вашего кода. У них нет «побочных эффектов». Ведение журнала является прекрасным примером. Он загружен синглетами и глобальным состоянием. Это приемлемо (поскольку это не повредит вам), потому что ваше приложение не ведет себя по-разному, независимо от того, включен ли данный регистратор. Информация здесь течет одним способом: из вашего приложения в регистратор. Даже мыслительные регистраторы являются глобальным состоянием, поскольку никакая информация не поступает из регистраторов в ваше приложение, регистраторы приемлемы. Вы все равно должны внедрить свой регистратор, если хотите, чтобы ваш тест утверждал, что что-то регистрируется, но в целом регистраторы не являются вредными, несмотря на полное состояние.

AGF
источник
5
Нет, одиночки никогда не бывают хорошими. Ведение журнала может быть хорошим кандидатом на то, чтобы стать глобальным (каким бы ужасным он ни был), но, конечно, не единственным.
Cat Plus Plus
11
Посмотрите на googletesting.blogspot.com/2008/08/… . Как правило, это анти-синглтон (по уважительной причине), но в нем есть хорошее объяснение того, почему неизменные синглтоны и синглтоны без побочных эффектов не имеют таких же проблем, если вы осторожны. Я собираюсь процитировать это немного в конце моего поста.
agf
4
Моя проблема с синглетонами - глупая предпосылка «только одного экземпляра». Это и тонна проблем безопасности ниток. И зависимость прячется. Глобалы плохие, а синглтоны - просто глобалы с большим количеством проблем.
Cat Plus Plus
4
@Cat Есть очень хорошее применение для одиночных игр. Ленивые экземпляры аппаратных модулей (особенно в однопоточных приложениях) являются одним из них (но существуют поточно-ориентированные синглтоны).
Пол Манта
3
@Alcott __new__в метаклассе - это когда класс новый - когда он определен, а не когда экземпляр будет новым. Вызов class ( MyClass()) - это операция, которую вы хотите переопределить, а не определение класса. Если вы действительно хотите понять, как работает Python, лучшее, что вы можете сделать (кроме как продолжать его использовать), это прочитать docs.python.org/reference/datamodel.html . Хорошей ссылкой на метаклассы является eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example . Хорошая статья о синглетонах - это серия из блога Google, на которую я ссылаюсь в этом ответе.
AGF
91
class Foo(object):
     pass

some_global_variable = Foo()

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

Cat Plus Plus
источник
14
почему вы сказали "Не используйте синглтоны"? Любая причина?
Олкотт
3
Это не сработает, если синглтон нужно мариновать. Используя пример вы дали:s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False
dividebyzero
4
@dividebyzero: isоператор проверяет равенство указателей. Я был бы довольно удивлен - вплоть до того, чтобы называть это ошибкой - если бы pickle.loadsвозвращал ссылку на ранее существующий объект, а не ссылку на вновь созданный объект. Таким образом, тестирование s is s1ничего не говорит о пригодности использования модулей в качестве синглтонов.
Йонас Кёлкер
1
@ JonasKölker pickle.loads()уже делает это, например, для случаев boolи NoneType. pickle.loads(pickle.dumps(False)) is FalseурожайностьTrue
Дан Пассаро
2
@ leo-the-manic: справедливая точка зрения; однако это всего лишь побочный эффект от интернирования объектов Python True, Falseи None, и не имеет ничего общего с кодом позади pickle.loads. Кроме того, это безопасно делать только для объектов только для чтения. Если pickle.loadsбы возвращалась ссылка на уже существующий модифицируемый объект, такой как модуль, это было бы ошибкой. (И поэтому я поддерживаю свое предположение, что пример кодаdivbyzero ничего не доказывает.)
Йонас Кёлкер
69

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

warvariuc
источник
11
Так что вы в конечном итоге ... Не класс. Вы не можете использовать его как класс, вы не можете основывать на нем другие классы, вы используете синтаксис импорта, и внезапно вы теряете все преимущества ООП ...
theheadofabroom
16
если вы можете основывать на нем другие классы, то это может быть не одиночка. Вы могли бы создать один из производного класса, но также и один из базового класса, но производный класс также является членом базы, и у вас есть два базовых класса, какой из них вы должны использовать?
SingleNegationElimination
Это не работает через модули. В моем "основном" модуле я установил значение. Затем я ссылаюсь на него в другом модуле и его ноль. Вздох.
Пол Кенджора
1
@PaulKenjora У вас должна быть ошибка в вашем коде. Если вы определяете глобальную переменную в модуле, то при обращении к ней из другого модуля она должна иметь значение.
варварюк
Это имеет значение, но когда я изменяю его, оно не сохраняется. Я закончил тем, что использовал класс со свойствами в модуле, который работает. Простые типы, такие как глобальные переменные, не работали для меня (они теряли значения, как только изменялась область действия).
Пол Кенджора
29

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

Если вам действительно нужно иметь синглтон-класс, то я бы пошел с:

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 не выполняет код повторно.

Алан Дайк
источник
4
В основном верно, но иногда этого недостаточно. Например, у меня есть проект с необходимостью регистрировать экземпляры многих классов на уровне DEBUG. Мне нужно, чтобы параметры командной строки анализировались при запуске, чтобы установить заданный пользователем уровень ведения журнала перед созданием этих классов. Реализация на уровне модуля делает это проблематичным. Возможно, я мог тщательно структурировать приложение, чтобы все эти классы не импортировались до тех пор, пока не будет выполнена обработка CLI, но естественная структура моего приложения важнее догматической приверженности «синглетонам плохо», поскольку они могут быть сделано довольно чисто.
CryingCyclops
если бы вы тестировали свой код, исправляя my_singleton, это было бы возможно? поскольку этот my_singleton может быть создан в каком-то другом модуле.
Навин
@Naveen - my_singleton - это отдельный объект. Если вы «исправите» его, это изменение повлияет на все будущие ссылки, даже в других модулях.
Алан Дайк
16

Вот одна строка для вас:

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

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

Йонас Кёлкер
источник
5
-1. Это очень плохо и глупо. Вы не определяете класс для использования в качестве объекта. Вы больше не можете получить доступ к классу, не будучи очень уродливым, как type(wat)или wat.__class__. Если вы действительно хотите достичь этого, лучше определите класс и создайте его экземпляр сразу, не нужно путаться с декоратором.
0xc0de
2
Почему вы должны знать, использовать класс синглтон? Просто используйте объект-одиночка ..
Толли
1
Это не одноэлементный шаблон , поэтому IMO функция должна называться по-разному.
GingerPlusPlus
8
Википедия: «шаблон синглтона - это шаблон проекта, который ограничивает создание экземпляра класса одним объектом». Я бы сказал, что мое решение делает именно это. Хорошо, я думаю, что можно было бы сделать wat2 = type(wat)(), но это питон, мы все взрослые по соглашению и все такое. Вы не можете гарантировать, что будет только один экземпляр, но вы можете гарантировать, что если люди сделают второй, он будет выглядеть уродливо и - если они порядочные, порядочные люди - станет для них предупреждающим знаком. Что мне не хватает?
Йонас Кёлькер
7

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

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

Антон
источник
2
Хотя это не совсем ответ на мой вопрос, ресурсы, на которые вы указываете, очень полезны. Я неохотно даю вам +1
головной убор
3

Вот моя собственная реализация синглетонов. Все, что вам нужно сделать, это украсить класс; чтобы получить синглтон, вы должны использовать 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)
Пол Манта
источник
2
Это не настоящий синглтон. SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)())должен печатать Trueв истинном синглтоне; с отпечатками вашего кодаFalse
GingerPlusPlus
@GingerPlusPlus Я знал о нескольких ограничениях, но не о том, на который вы указали. Спасибо за упоминание этого. К сожалению, на данный момент у меня нет времени размышлять над решением этой проблемы.
Пол Манта
2

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

Тим
источник
конечно, это не метакласс - в python3 использовать метакласс для создания MyClass вы бы сделалиclass MyClass(metaclass=Singleton)
theheadofabroom
Mikewatkins.ca звено (эффективно) сломана.
Питер Мортенсен
1

Ну, кроме как согласиться с общим предложением 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
охрана
источник
Какой смысл этого _sealedатрибута? Насколько я вижу, это ничего не делает? Что-то беспокоит меня из-за того, что это не должно хорошо работать ... Позже на этой неделе я проведу несколько сравнительных тестов.
головной кабинет
_sealed гарантирует, что ваша инициализация запускается только один раз; Я не вижу смысла, почему он должен работать хуже, чем обычный подобный функции декоратор - функция выполняется только один раз для каждого класса и возвращает новый унаследованный класс
Guard
Кстати, ваша правка содержит вкладки, которые разбивают отступы. Вы также говорите «мы создаем 2 класса» - вы имеете в виду, что мы создаем «1 дополнительный класс»?
Страж
Да, я имел в виду один дополнительный класс. Я намереваюсь включить материал, __init__который будет вызываться при каждой инициализации. Просто простое «Инициализация в классе. Метод». Что касается отступа - вы использовали табуляцию и пробелы - я исправил большую часть, но, кажется, пропустил одну, если вы хотите ее получить (просто проверьте журнал редактирования)
глава
re init : конечно, это ваше дело, я просто попытался имитировать поведение синглтона в других языках, где код конструктора (который не совсем init , но очень близок по своему смыслу) вызывается только один раз, если вы хотите init был вызывается каждый раз, просто убейте все ссылки на _sealed re пробелы / табуляции - ну, тогда мой emacs нуждается в исправлении. в любом случае, выше исправленная версия
Guard
1

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

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

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

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

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. Это включает в себя отсутствие необходимости использовать метакласс для вашего класса (обратите внимание, что метакласс выше находится на фабрике, а не на «реальном» классе).
  • Это не полагается на исправление обезьяны ничего.
  • Это прозрачно для абонентов:
    • Вызывающие по-прежнему просто импортируют YourClass, это выглядит как класс (потому что это так), и они используют его как обычно. Нет необходимости адаптировать абонентов к заводским функциям.
    • То, 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% времени, ЯГНИ.

CryingCyclops
источник
1
  • Если кто-то хочет иметь несколько экземпляров одного и того же класса, но только если аргументы или kwargs различны, можно использовать это
  • Ex.
    1. Если у вас есть класс, обрабатывающий serialсвязь, и для создания экземпляра вы хотите отправить последовательный порт в качестве аргумента, то при традиционном подходе работать не будет
    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
>>>
Сиддхеш Сухас Сатхе
источник
0

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

#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. Если __new__()возвращает экземпляр класса cls, то __init__()будет вызываться метод нового экземпляра, например __init__(self[, ...]), где self - это новый экземпляр, а остальные аргументы такие же, как были переданы __new__().

    instanceуже инициализирована, поэтому функция заменяется __init__на функцию, которая ничего не делает.

Посмотрите, как работает онлайн

GingerPlusPlus
источник
0

Это немного похоже на ответ от потрясающего, но не совсем то же самое.

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

Так что мое предложение как раз так:

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
Майк Грызун
источник
Хотя это немного другая интерпретация шаблона синглтона, я почти уверен, что он все еще действителен, хотя у меня может возникнуть соблазн использовать __new__ вместо __init__, так как он действует исключительно на атрибуты класса, и это не дает возможности кратко быть вторым экземпляром. Разница между этим и методом 2 заключается в том, возвращает ли попытка инициализации более одного раза один экземпляр или вызывает исключение. Я думаю, что я счастлив, что либо удовлетворяю одноэлементному шаблону, один проще в использовании, а другой более явный, чем одноэлементный.
The Headofabroom
Очевидно, что использование имени класса __init__предотвращает
создание
Спасибо ... ах, да, мгновенная вторая инстанция до того, как будет сгенерировано Исключение. Я изменил __init__так, чтобы, надеюсь, это было подклассифицировано ...
Майк Грызун
Круто, theвозможно, выиграет от того, чтобы быть классным методом по тем же причинам
theheadofabroom
Да, ты прав. Тогда вы можете иметь синглтон подкласса SuperElvis и (например) синглтон подкласса ImaginaryElvis ... и они могут сосуществовать. Смотрите дополнительные мысли. Пожалуйста, не стесняйтесь улучшать мой код.
Майк Грызун
0

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

class Myclass:
  def __init__(self):
      # do your stuff
      globals()[type(self).__name__] = lambda: self # singletonify
polvoazul
источник
Это может делать эту работу до тех пор, пока ваш класс не был импортирован ни в какие другие модули ...
Аран-Фей,
Правда. Если вы можете оплатить стоимость инициации класса, вы можете запустить Myclass () сразу после определения класса, чтобы избежать этого.
полвоазул
0

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

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

Этот способ Aявляется одиночным инициализированным при импорте модуля.

Серж Рогач
источник
0

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

  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!
buhtz
источник
0

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

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

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

2cynykyl
источник
2
почему это ужасный подход? когда вы хотите создать другой одноэлементный класс, вы должны скопировать и вставить __new__. Не повторяйся .
GingerPlusPlus
Кроме того, почему ваши новые берет *argsи **kwargs, а затем ничего не делает с ними? Передайте их в dict.__new__таким образом: dict.__new__(cls, *args, **kwargs).
GingerPlusPlus
Это будет вызывать __init__метод каждый раз, когда вызывается класс. Если ваш __init__метод действительно что-то сделал, вы заметите проблему. Всякий раз, когда вы делаете SomeSingleton(), состояние вашего синглтона сбрасывается __init__методом.
Аран-Фей
-2

Это мой предпочтительный способ реализации синглетонов:

class Test(object):
    obj = None

    def __init__(self):
        if Test.obj is not None:
            raise Exception('A Test Singleton instance already exists')
        # Initialization code here

    @classmethod
    def get_instance(cls):
        if cls.obj is None:
            cls.obj = Test()
        return cls.obj

    @classmethod
    def custom_method(cls):
        obj = cls.get_instance()
        # Custom Code here
потрясающий
источник
1
Это не строго одноэлементный файл, поскольку он позволяет существовать более чем одному экземпляру класса. Улучшение было бы в том, чтобы сделать класс неспособным инициализироваться, и чтобы все методы класса воздействовали на атрибуты класса
theheadofabroom
-2

Этот ответ, вероятно, не то, что вы ищете. Я хотел синглтон в том смысле, что только этот объект имел свою идентичность, для сравнения. В моем случае это использовалось как 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
ThorSummoner
источник
Я узнал, что модули на самом деле могут быть импортированы несколько раз, в таком случае это только локальный синглтон, который на самом деле не является синглтоном в любом качестве.
ThorSummoner
Можете ли вы рассказать, как модуль можно импортировать несколько раз? Единственный раз, когда я видел, когда во время загрузки модуля возникает исключение, пользователь может все же загрузить модуль позже, но побочные эффекты уже будут иметь место, поэтому некоторые действия могут быть выполнены во второй раз.
sleblanc
Когда модуль полностью загружен, я не вижу способа запустить этот модуль снова, кроме как принудить интерпретатор сделать это с помощью evalили importlib.reload.
sleblanc
-3

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

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

# 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
Грегори Нисбет
источник