python3 - шаблоны проектирования singleton python




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

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

Я бы порекомендовал Method # 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 в приведенном выше коде будет иметь тип type class 'your_module.Singleton' , так же, как экземпляр (только) Logger будет иметь тип type class 'your_module.Logger' . Когда вы вызываете регистратор с помощью Logger() , Python сначала запрашивает метакласс Logger , Singleton , что делать, позволяя создавать экземпляры. Этот процесс такой же, как Python, __getattr__ класс, что делать, вызывая __getattr__ когда вы ссылаетесь на один из его атрибутов, выполняя myclass.attribute .

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

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

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

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

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

Совместимость с 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 и делает их разумным выбором из-за их простоты использования для задачи.

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

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

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

Этот вопрос заключается не в обсуждении того, желателен ли шаблон однократной конструкции , либо в виде анти-шаблона, либо для любых религиозных войн, но для обсуждения того, как этот шаблон лучше всего реализован на Python таким образом, который является наиболее pythonic. В этом случае я определяю «самый пифонический», чтобы означать, что он следует «принципу наименьшего удивления» .

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

Лучшие методы:

Способ 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 сам является функцией aa, а не классом, поэтому вы не можете вызывать методы класса из него. Также для m = MyClass(); n = MyClass(); o = type(n)(); 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

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

Способ 3: metaclass

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

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

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


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

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


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


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

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

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

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


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

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

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

Контракт синглтона не требует , чтобы мы смогли вызвать конструктор несколько раз. Поскольку одноэлемент должен создаваться один раз и один раз, разве это не должно быть создано только один раз? «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()

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

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

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

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

# 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

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

#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__функцию, которая ничего не делает.

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


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

#!python
MyNone = object()  # The singleton

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

class Foo(object):
     pass

some_global_variable = Foo()

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





metaclass