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

Pros

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

Против

  • Хотя объекты, созданные с использованием 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

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

Против

  • Множественное наследование - тьфу! __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__ для своей цели (и заставил меня осознать это)

Против

  • Есть ли?

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

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

Против

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

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

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

Доводы

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

Cons

Ответ 1

Использование метакласса

Я бы рекомендовал Метод # 2, но вам лучше использовать метакласс, чем базовый класс. Вот пример реализации:

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Logger(object):
    __metaclass__ = Singleton

Или в Python3

class Logger(metaclass=Singleton):
    pass

Если вы хотите запускать __init__ при каждом вызове класса, добавьте

        else:
            cls._instances[cls].__init__(*args, **kwargs)

в оператор if в Singleton.__call__.

Несколько слов о метаклассах. Метакласс - класс класса; то есть класс является экземпляром своего метакласса. Вы находите метакласс объекта в Python с type(obj). Обычные классы нового стиля имеют тип type. Logger в приведенном выше коде будет иметь тип class 'your_module.Singleton', так же, как экземпляр (только) Logger будет иметь тип class 'your_module.Logger'. Когда вы вызываете logger с Logger(), Python сначала запрашивает метакласс Logger, Singleton, что делать, позволяя создать экземпляр. Этот процесс такой же, как Python, запрашивающий класс, что делать, вызывая __getattr__, когда вы ссылаетесь на один из его атрибутов, делая myclass.attribute.

Метакласс по существу решает то, что определение класса означает и как реализовать это определение. См. Например http://code.activestate.com/recipes/498149/, который по существу воссоздает C-style struct в Python с использованием метаклассов. В потоке Каковы ваши (конкретные) прецеденты для метаклассов в Python? также приводятся некоторые примеры, они, как правило, связаны с декларативным программированием, тем более, что используются в ORM.

В этой ситуации, если вы используете Метод # 2, а подкласс определяет метод __new__, он будет выполняться каждый раз, вы вызываете SubClassOfSingleton() - потому что он отвечает за вызов метода, который возвращает сохраненный экземпляр. С метаклассом он будет вызываться только один раз, когда создается единственный экземпляр. Вы хотите настроить, что означает вызов класса, который определяется этим типом.

В целом, он имеет смысл использовать метакласс для реализации синглета. Синглтон особенный, поскольку создан только один раз, а метакласс - это способ настройки создания класса. Использование метакласса дает вам больше контроля, если вам нужно настроить определения класса singleton другими способами.

Вашим синглонам не потребуется множественное наследование (поскольку метакласс не является базовым классом), но для подклассов созданного класса, которые используют множественное наследование, вам нужно чтобы убедиться, что класс singleton - это first/leftmost с метаклассом, который переопределяет __call__. Это вряд ли будет проблемой. Экземпляр dict не в пространстве имен экземпляров, поэтому он не будет случайно перезаписывать его.

Вы также услышите, что одноэлементный шаблон нарушает "принцип единой ответственности" - каждый класс должен выполнять только одну вещь. Таким образом, вам не нужно беспокоиться о том, чтобы испортить одно, что делает код, если вам нужно изменить другое, потому что они раздельны и инкапсулированы. Реализация метакласса проходит этот тест. Метакласс отвечает за принуждение шаблона, и созданный класс и подклассы не должны быть осведомлены о том, что они являются одиночными.. Метод # 1 не выполняет этот тест, как вы отметили с помощью "MyClass" - это функция, а не класс, поэтому вы не можете вызывать методы класса из нее ".

Совместимая версия Python 2 и 3

Написание чего-то, что работает в Python2 и 3, требует использования немного более сложной схемы. Поскольку метаклассы обычно являются подклассами типа type, можно использовать один для динамического создания промежуточного базового класса во время выполнения с ним в качестве его метакласса, а затем использовать его как базовый класс общедоступного базового класса Singleton. Это труднее объяснить, чем делать, как показано ниже:

# works in Python 2 & 3
class _Singleton(type):
    """ A metaclass that creates a Singleton base class when called. """
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Singleton(_Singleton('SingletonMeta', (object,), {})): pass

class Logger(Singleton):
    pass

Иронический аспект этого подхода заключается в том, что он использует подклассы для реализации метакласса. Одно из возможных преимуществ заключается в том, что, в отличие от чистого метакласса, isinstance(inst, Singleton) вернет True.

Исправления

В другой теме вы, вероятно, уже заметили это, но реализация базового класса в вашем исходном сообщении неверна. _instances должен быть указан в классе, вам нужно использовать super() или вы рекурсивно, а __new__ - фактически статический метод, который у вас есть до передать класс, а не метод класса, поскольку фактический класс не был создан, но когда он вызывается. Все это будет справедливо и для реализации метакласса.

class Singleton(object):
  _instances = {}
  def __new__(class_, *args, **kwargs):
    if class_ not in class_._instances:
        class_._instances[class_] = super(Singleton, class_).__new__(class_, *args, **kwargs)
    return class_._instances[class_]

class MyClass(Singleton):
  pass

c = MyClass()

Декоратор, возвращающий класс

Я изначально писал комментарий, но он был слишком длинным, поэтому я добавлю это здесь. Метод # 4 лучше, чем у другой версии декоратора, но это больше кода, чем нужно для синглтона, и это не так ясно, что он делает.

Основные проблемы связаны с тем, что класс является базовым классом. Во-первых, разве не странно, чтобы класс был подклассом почти идентичного класса с тем же именем, который существует только в его атрибуте __class__? Это также означает, что вы не можете определить любые методы, которые вызывают метод с тем же именем в базовом классе с super(), потому что они будут рекурсивно. Это означает, что ваш класс не может настроить __new__ и не может выводить из каких-либо классов, которым требуется __init__, вызываемых на них.

Когда использовать одиночный шаблон

Ваш прецедент - это один из лучших примеров, который хочет использовать одноэлемент. Вы говорите в одном из комментариев: "Для меня журнал всегда казался естественным кандидатом на синглтонов". Вы абсолютно правы.

Когда люди говорят, что одиночные игры плохи, наиболее распространенная причина заключается в том, что они неявное разделяемое состояние. Хотя глобальные переменные и импорт модулей верхнего уровня являются явным общим состоянием, другие объекты, которые передаются, обычно создаются. Это хорошая точка, с двумя исключениями.

Первый, и тот, который упоминается в разных местах, - это когда синглеты постоянны. Использование глобальных констант, особенно перечислений, широко принято и считается разумным, потому что ни в коем случае ни один из пользователей не может испортить их для любого другого пользователя. Это одинаково справедливо и для константы singleton.

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

Вот цитата из http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html:

Теперь есть один вид Singleton, который в порядке. Это одноэлемент, где все доступные объекты неизменяемы. Если все объекты неизменяемы, чем Singleton не имеет глобального состояния, поскольку все является постоянным. Но так легко превратить этот синглтон в изменчивый, он очень скользкий. Поэтому я против этих синглтонов тоже не потому, что они плохие, а потому, что им очень легко погубить. (В качестве дополнительной заметки перечисление Java - это именно такие синглтоны. Пока вы не ставите состояние в свое перечисление, вы в порядке, поэтому, пожалуйста, не делайте этого.)

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

Ответ 2

class Foo(object):
     pass

some_global_variable = Foo()

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

Ответ 3

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

Ответ 4

Вам, вероятно, не нужен синглтон в 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 не выполняет повторный запуск кода.

Ответ 5

Здесь вам нужен один лайнер:

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

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

Ответ 6

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

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

Ответ 7

Здесь моя собственная реализация синглетонов. Все, что вам нужно сделать, это украсить класс; чтобы получить синглтон, вы должны использовать метод 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)

Ответ 9

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

Ответ 10

Ну, а не соглашаясь с общим питоническим предложением о глобальном уровне модуля, как насчет этого:

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

Ответ 11

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

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

Ответ 12

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

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. Это означает, что вам не нужно использовать метакласс для вашего класса (обратите внимание, что метакласс выше находится в factory, а не в "реальном" классе).
  • Он не полагается на обезвреживание обезьян.
  • Он прозрачен для вызывающих:
    • Абоненты по-прежнему просто импортируют YourClass, это похоже на класс (потому что это так), и они обычно используют его. Нет необходимости адаптировать вызывающих абонентов к функции factory.
    • То, что YourClass() создает экземпляр, по-прежнему является истинным экземпляром YourClass, который вы внедрили, а не прокси-сервером любого типа, поэтому нет никаких побочных эффектов, возникающих в результате этого.
    • isinstance(instance, YourClass) и подобные операции все еще работают как ожидалось (хотя этот бит требует abc, поэтому исключает Python < 2.6).

Один недостаток имеет место для меня: classmethods и staticmethods реального класса не могут быть прозрачно вызываемыми через класс factory, скрывающий его. Я использовал это достаточно редко, так что я никогда не сталкивался с этой необходимостью, но его можно было бы легко исправить, используя специальный метакласс в factory, который реализует __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% времени, YAGNI.

Ответ 13

Код на основе Ответ Tolli.

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

Пояснение:

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

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

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

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

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

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

Ответ 14

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

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

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

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

Точки обзора:

  • Если вы не подклассом объекта в python2.x, вы получите класс старого стиля, который не использует __new__
  • При декорировании __new__ вы должны украсить с помощью @classmethod, или __new__ будет методом несвязанного экземпляра
  • Возможно, это возможно улучшить с помощью метакласса, поскольку это позволит вам сделать the свойство класса, возможно, переименовать его в instance

Ответ 15

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

class Myclass:
  def __init__(self):
      # do your stuff
      globals()[type(self).__name__] = lambda: self # singletonify

Ответ 16

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

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

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

Ответ 17

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

Ответ 18

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

  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!

Ответ 19

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

#  This also allows the existing code to use the class attributes without modification.

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>...

Ответ 20

Я не могу вспомнить, где я нашел это решение, но я считаю его самым "элегантным" с моей точки зрения не с точки зрения 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 (и ООП в целом), я ожидаю, что кто-то задаст мне, почему это ужасный подход?

Ответ 21

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

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

Ответ 22

Этот ответ скорее всего не то, что вы ищете. Мне нужен синглтон в том смысле, что только этот объект имел свою идентичность для сравнения. В моем случае он использовался как Sentinel Value. Для чего ответ очень прост, сделайте любой объект mything = object() и природой питона, только эта вещь будет иметь свою идентичность.

#!python
MyNone = object()  # The singleton

for item in my_list:
    if item is MyNone:  # An Example identity comparison
        raise StopIteration

Ответ 23

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

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

# 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