Создание синглтона в Python - PullRequest
770 голосов
/ 20 июля 2011

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

Плюсы

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

Против

  • Множественное наследование - тьфу! __new__ может быть перезаписано при наследовании от второго базового класса? Нужно думать больше, чем нужно.

Метод 3: A метакласс

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

Ответы [ 20 ]

534 голосов
/ 23 июля 2011

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

Я бы порекомендовал Метод # 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 struct s в Python, используя метаклассы. Поток Каковы ваши (конкретные) сценарии использования метаклассов в Python? также предоставляет некоторые примеры, которые, как правило, связаны с декларативным программированием, особенно в том виде, в котором они используются в ORM.

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

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

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

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

Ваш вариант использования один из лучших примеров желания использовать синглтон. Вы говорите в одном из комментариев: «Для меня регистрация всегда казалась естественным кандидатом для синглетонов». Ты абсолютно прав .

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

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

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

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

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

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

78 голосов
/ 20 июля 2011
class Foo(object):
     pass

some_global_variable = Foo()

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

60 голосов
/ 20 июля 2011

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

24 голосов
/ 11 декабря 2014

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

14 голосов
/ 19 октября 2013

Вот вам одна строчка:

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

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

7 голосов
/ 20 июля 2011

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

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

3 голосов
/ 20 сентября 2011

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

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

1 голос
/ 13 октября 2018

Предыдущие ответы верны, но я не согласен с утверждением, которое было опубликовано как часть вопроса в методе 1 «MyClass сам по себе является функцией, а не классом, поэтому вы не можете вызывать методы класса».Посмотрите мой пример ниже, этот метод вызывается несколько раз, в MyClass, который украшен тегом singleton.

Также обратите внимание, что это очень похоже на некоторые опубликованные ответы и основано на документе python но это немного отличается, потому что класс и функция спроектированы таким образом, что могут принимать 1 или 0 аргументов, и все же синглтон работает.

enter image description here Вот доказательство того, что вы можетеВызовите метод singleton несколько раз и показывает, что один экземпляр класса все еще используется, и новые объекты не создаются.

#/usr/bin/env python

def singleton(cls):
    instances = {}
    def getinstance(anyArgs=None):
        if cls not in instances:
            instances[cls] = cls(anyArgs)
        return instances[cls]
    return getinstance

@singleton
class MyClass:
    def __init__(self,count=None):
        print("print argument if any exists",count)

    def test(self, counter):
        # time.sleep(1000)
        print("-->",counter)
        return counter

### create two objects to see if we get a singleton behavior !
a = MyClass(10000)
a.test(1)
b = MyClass()
b.test(2)
if a != b:
    print("this is not a singleton")

#lets makesure it's still is the same object
if a!=b:
    print("error")

Поскольку глава кабинета (человек, который разместил вопрос) дал отзыв о своем оригиналеЗатем я начал работать над новым решением, основываясь на его отзывах (я все еще сохранил свой предыдущий ответ, потому что думаю, что он может быть полезен для некоторых, даже если это не на 100% точно то, о чем спрашивает хедфаб).Вот обновленный ответ:

enter image description here

Вот код для его копирования и вставки:)

#/usr/bin/env python
from functools import wraps 

def singleton(cls):
    instances = {}
    def getinstance(anyArgs=None):
        if cls not in instances:
            instances[cls] = cls(anyArgs)
            return instances[cls]
    return getinstance

def add_function(cls):
    def outer_decorate_it(somefunction):
        @wraps(somefunction) 
        def wrapper( *args, **kwargs): 
            return somefunction(*args, **kwargs)
        setattr(cls, somefunction.__name__, wrapper)
        return somefunction
    return outer_decorate_it

@singleton
class MyClass():
    def __init__(self,count=None):
        print("print argument if any exists",count)

@add_function(MyClass)
def testit():
    print("It's me the function from the class")


MyClass.testit()
1 голос
/ 18 мая 2014

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

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

...