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

1 голос
/ 25 июля 2011

Ну, кроме как согласиться с общим предложением 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
0 голосов
/ 29 сентября 2016

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

#!python
MyNone = object()  # The singleton

for item in my_list:
    if item is MyNone:  # An Example identity comparison
        raise StopIteration
0 голосов
/ 28 августа 2016

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

from abc import ABC

def singleton(real_cls):

    class SingletonFactory(ABC):

        instance = None

        def __new__(cls, *args, **kwargs):
            if not cls.instance:
                cls.instance = real_cls(*args, **kwargs)
            return cls.instance

    SingletonFactory.register(real_cls)
    return SingletonFactory

# Usage
@singleton
class YourClass:
    ...  # Your normal implementation, no special requirements.

Преимущества Я думаю, что он имеет некоторые другие решения:

  • Это ясно и кратко (на мой взгляд; D).
  • Его действие полностью заключено в капсулу.Вам не нужно ничего менять в реализации YourClass.Это включает в себя отсутствие необходимости использовать метакласс для вашего класса (обратите внимание, что метакласс выше находится на фабрике, а не на «реальном» классе).
  • Он не полагается на патч-обезьяны.
  • Это прозрачно для абонентов:
    • Абоненты все еще просто импортируют YourClass, это похоже на класс (потому что это так), и они используют его как обычно.Нет необходимости адаптировать вызывающие к заводской функции.
    • То, что создает экземпляр YourClass(), все еще является истинным экземпляром YourClass, который вы реализовали, а не прокси-сервером любого вида, так что нет никаких побочных эффектов, возникающих в результате этого..
    • isinstance(instance, YourClass) и подобные операции по-прежнему работают должным образом (хотя этот бит требует abc, поэтому исключает Python <2.6). </li>

Один недостаток действительно имеет местодля меня: методы класса и методы static реального класса не могут быть прозрачно вызваны через скрытый класс фабрики.Я использовал это достаточно редко, чтобы мне никогда не приходилось сталкиваться с этой потребностью, но это было бы легко исправить с помощью пользовательского метакласса на фабрике, который реализует __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% времени, ЯГНИ.

0 голосов
/ 21 февраля 2016

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

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

Поэтому я предлагаю следующее:

class Elvis():
    def __init__(self):
        if hasattr(self.__class__, 'instance'):
            raise Exception()
        self.__class__.instance = self
        # initialisation code...

    @staticmethod
    def the():
        if hasattr(Elvis, 'instance'):
            return Elvis.instance
        return Elvis()

Это не исключает использования конструктора или поля instance в пользовательском коде:

if Elvis() is King.instance:

... если вы точно знаете, что Elvis еще не создано, а King имеет.

Но это поощряет пользователи могут использовать метод the повсеместно:

Elvis.the().leave(Building.the())

Для этого вы также можете переопределить __delattr__(), чтобы вызвать исключение, если сделана попытка удалить instance, и переопределить __del__()так что это вызывает исключение (если мы не знаем, что программа заканчивается ...)

Дальнейшие улучшения


Благодарю тех, кто помог с комментариями и правками, из которых большеДобро пожаловатьХотя я использую Jython, это должно работать более широко и быть поточно-ориентированным.

try:
    # This is jython-specific
    from synchronize import make_synchronized
except ImportError:
    # This should work across different python implementations
    def make_synchronized(func):
        import threading
        func.__lock__ = threading.Lock()

        def synced_func(*args, **kws):
            with func.__lock__:
                return func(*args, **kws)

        return synced_func

class Elvis(object): # NB must be subclass of object to use __new__
    instance = None

    @classmethod
    @make_synchronized
    def __new__(cls, *args, **kwargs):
        if cls.instance is not None:
            raise Exception()
        cls.instance = object.__new__(cls, *args, **kwargs)
        return cls.instance

    def __init__(self):
        pass
        # initialisation code...

    @classmethod
    @make_synchronized
    def the(cls):
        if cls.instance is not None:
            return cls.instance
        return cls()

Примечания:

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

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

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

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

0 голосов
/ 01 ноября 2016

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

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

# 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
0 голосов
/ 07 марта 2018

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

class Myclass:
  def __init__(self):
      # do your stuff
      globals()[type(self).__name__] = lambda: self # singletonify
0 голосов
/ 30 октября 2014

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

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
0 голосов
/ 01 января 2019

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

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

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

0 голосов
/ 11 ноября 2014

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

...