Этот вопрос предназначен не для обсуждения того, является ли шаблон проектирования синглтона желательным, является ли он антипаттерном или для каких-либо религиозных войн, а для обсуждения того, как этот шаблон лучше всего реализовать в Python таким способом, который наиболее питоничен. В данном случае я определяю «самый питонический» как означающий, что он следует «принципу наименьшего удивления». .
У меня есть несколько классов, которые могли бы стать синглетонами (мой вариант использования - для регистратора, но это не важно). Я не хочу загромождать несколько классов добавленной гитарой, когда я могу просто наследовать или украшать.
Лучшие методы:
Способ 1: декоратор
def singleton(class_):
instances = {}
def getinstance(*args, **kwargs):
if class_ not in instances:
instances[class_] = class_(*args, **kwargs)
return instances[class_]
return getinstance
@singleton
class MyClass(BaseClass):
pass
Pros
- Декораторы аддитивны таким образом, что зачастую более интуитивно понятны, чем множественное наследование.
Cons
- Хотя объекты, созданные с использованием MyClass (), будут настоящими одноэлементными объектами, сам MyClass является функцией, а не классом, поэтому вы не можете вызывать методы класса из него. Также на
m = MyClass(); n = MyClass(); o = type(n)();
потомm == n && m != o && n != o
Способ 2: базовый класс
class Singleton(object):
_instance = None
def __new__(class_, *args, **kwargs):
if not isinstance(class_._instance, class_):
class_._instance = object.__new__(class_, *args, **kwargs)
return class_._instance
class MyClass(Singleton, BaseClass):
pass
Pros
- Это настоящий класс
Cons
- Множественное наследование - тьфу!
__new__
может быть перезаписано при наследовании от второго базового класса? Нужно думать больше, чем нужно.
Метод 3: метакласс
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
#Python2
class MyClass(BaseClass):
__metaclass__ = Singleton
#Python3
class MyClass(BaseClass, metaclass=Singleton):
pass
Pros
- Это настоящий класс
- Автомагически охватывает наследование
- Использует
__metaclass__
для своей цели (и заставил меня осознать это)
Cons
- Есть ли такие?
Метод 4: декоратор возвращает класс с тем же именем
def singleton(class_):
class class_w(class_):
_instance = None
def __new__(class_, *args, **kwargs):
if class_w._instance is None:
class_w._instance = super(class_w,
class_).__new__(class_,
*args,
**kwargs)
class_w._instance._sealed = False
return class_w._instance
def __init__(self, *args, **kwargs):
if self._sealed:
return
super(class_w, self).__init__(*args, **kwargs)
self._sealed = True
class_w.__name__ = class_.__name__
return class_w
@singleton
class MyClass(BaseClass):
pass
Pros
- Это настоящий класс
- Автомагически охватывает наследование
Cons
- Нет ли накладных расходов на создание каждого нового класса? Здесь мы создаем два класса для каждого класса, который мы хотим сделать синглтоном. Хотя в моем случае это нормально, я беспокоюсь, что это может не масштабироваться. Конечно, есть спор о том, должно ли быть слишком легко масштабировать эту модель ...
- Какой смысл этого
_sealed
атрибута - Невозможно вызвать методы с тем же именем в базовых классах, используя их,
super()
потому что они будут повторяться. Это означает, что вы не можете настроить__new__
и не можете создать подкласс класса, к которому вам нужно обращаться__init__
.
Способ 5: модуль
файл модуля singleton.py
Pros
- Простое лучше сложного
Cons
foo.x
или, если вы настаиваетеFoo.x
вместоFoo().x
); используйте атрибуты класса и методы static / class (Foo.x
).Ответы:
Используйте метакласс
Я бы порекомендовал метод № 2 , но лучше использовать метакласс, чем базовый класс. Вот пример реализации:
Или в Python3
Если вы хотите запускать
__init__
каждый раз, когда вызывается класс, добавьтек
if
утверждение вSingleton.__call__
.Несколько слов о метаклассах. Метакласс - это класс класса ; то есть класс является экземпляром своего метакласса . Вы найдете метакласс объекта в Python с помощью
type(obj)
. Обычные классы нового стиля имеют типtype
.Logger
в приведенном выше коде будет иметь типclass 'your_module.Singleton'
, так же как (единственный) экземплярLogger
будет иметь типclass 'your_module.Logger'
. При вызове логгер сLogger()
, Python сначала спрашивает метаклассLogger
,Singleton
что же делать, что позволяет создание экземпляра быть предварительно опорожнить. Этот процесс аналогичен тому, как Python запрашивает у класса, что делать, вызывая,__getattr__
когда вы ссылаетесь на один из его атрибутов, выполняяmyclass.attribute
.Метакласс по существу решает, что означает определение класса и как реализовать это определение. См., Например, http://code.activestate.com/recipes/498149/ , который по сути воссоздает C-стиль
struct
в Python с использованием метаклассов. Поток Какие есть (конкретные) варианты использования для метаклассов? также приводятся некоторые примеры, которые, как правило, связаны с декларативным программированием, особенно при использовании в ORM.В этой ситуации, если вы используете свой метод № 2 , а подкласс определяет
__new__
метод, он будет выполняться каждый раз, когда вы вызываете,SubClassOfSingleton()
потому что он отвечает за вызов метода, который возвращает сохраненный экземпляр. С метаклассом он будет вызываться только один раз , когда создается единственный экземпляр. Вы хотите настроить, что значит называть класс , который определяется его типом.В общем случае имеет смысл использовать метакласс для реализации синглтона. Синглтон особенный, потому что он создается только один раз , а метакласс - это способ, которым вы настраиваете создание класса . Использование метакласса дает вам больше контроля в случае, если вам нужно настроить определения класса singleton другими способами.
Вашим синглетам не нужно многократное наследование (поскольку метакласс не является базовым классом), но для подклассов созданного класса , использующего множественное наследование, необходимо убедиться, что класс синглтона является первым / левым с метаклассом, который переопределяет
__call__
Это очень маловероятно, чтобы быть проблемой. Экземпляр dict не находится в пространстве имен экземпляра поэтому он не будет случайно перезаписывать его.Вы также услышите, что шаблон синглтона нарушает «Принцип единой ответственности» - каждый класс должен делать только одно . Таким образом, вам не нужно беспокоиться о том, чтобы испортить одну вещь, которую делает код, если вам нужно изменить другую, потому что они разделены и инкапсулированы. Реализация метакласса проходит этот тест . Метакласс отвечает за применение шаблона, и созданный класс и подклассы не должны знать, что они являются синглетонами . Метод # 1 не проходит этот тест, как вы заметили, «MyClass сам по себе является функцией, а не классом, поэтому вы не можете вызывать методы класса из него».
Python 2 и 3 совместимая версия
Написание чего-то, что работает как в Python2, так и в 3, требует использования немного более сложной схемы. Так как метаклассы, как правило , подклассы типа
type
, можно использовать один , чтобы динамически создать промежуточный базовый класс во время выполнения с ним , как его метаклассом , а затем использовать , что , как BaseClass общественногоSingleton
базового класса. Это сложнее объяснить, чем сделать, как показано ниже:Иронический аспект этого подхода заключается в том, что он использует подклассы для реализации метакласса. Одним из возможных преимуществ является то, что, в отличие от чистого метакласса,
isinstance(inst, Singleton)
вернетсяTrue
.исправления
По другой теме вы, наверное, уже заметили это, но реализация базового класса в исходном посте неверна.
_instances
На него нужно ссылаться в классе , нужно использоватьsuper()
или вы рекурсивны , и__new__
на самом деле это статический метод, которому вы должны передать класс , а не метод класса, поскольку реальный класс еще не был создан, когда он называется. Все это будет верно и для реализации метакласса.Декоратор, возвращающий класс
Первоначально я писал комментарий, но это было слишком долго, поэтому я добавлю это здесь. Метод № 4 лучше, чем другая версия декоратора, но в нем больше кода, чем нужно для одиночного, и не совсем понятно, что он делает.
Основные проблемы проистекают из того, что класс является его собственным базовым классом. Во-первых, не странно ли, чтобы класс был подклассом почти идентичного класса с тем же именем, которое существует только в его
__class__
атрибуте? Это также означает , что вы не можете определить , какие методы , которые вызывают метод с тем же именем на их базовый класс с ,super()
потому что они будут рекурсии. Это означает, что ваш класс не может настраиваться__new__
и не может быть производным от каких-либо классов, которые должны__init__
вызываться для них.Когда использовать шаблон синглтона
Ваш вариант использования - один из лучших примеров желания использовать синглтон. Вы говорите в одном из комментариев: «Для меня регистрация всегда казалась естественным кандидатом для синглетонов». Ты абсолютно прав .
Когда люди говорят, что одиночные игры плохие, самая распространенная причина - это неявное общее состояние . В то время как с глобальными переменными и импортом модулей верхнего уровня являются явным общим состоянием, другие объекты, которые передаются, обычно создаются. Это хороший момент, с двумя исключениями .
Первый и тот, который упоминается в разных местах, - это когда синглтоны постоянны . Использование глобальных констант, особенно перечислений, является общепринятым и считается нормальным, потому что, несмотря ни на что, ни один из пользователей не может испортить их для любого другого пользователя . Это в равной степени верно и для постоянного синглтона.
Второе исключение, о котором упоминается меньше, является противоположным - когда синглтон является только приемником данных , а не источником данных (прямо или косвенно). Вот почему регистраторы чувствуют себя как «естественное» использование для одиночных игр. Поскольку различные пользователи не меняют регистраторы так, как о них будут заботиться другие пользователи, в действительности не существует общего состояния . Это сводит на нет основной аргумент против шаблона синглтона и делает их разумным выбором из-за их простоты использования для задачи.
Вот цитата из http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :
источник
__new__
в метаклассе - это когда класс новый - когда он определен, а не когда экземпляр будет новым. Вызов class (MyClass()
) - это операция, которую вы хотите переопределить, а не определение класса. Если вы действительно хотите понять, как работает Python, лучшее, что вы можете сделать (кроме как продолжать его использовать), это прочитать docs.python.org/reference/datamodel.html . Хорошей ссылкой на метаклассы является eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example . Хорошая статья о синглетонах - это серия из блога Google, на которую я ссылаюсь в этом ответе.Модули импортируются только один раз, все остальное продуманно. Не используйте синглтоны и старайтесь не использовать глобалы.
источник
s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False
is
оператор проверяет равенство указателей. Я был бы довольно удивлен - вплоть до того, чтобы называть это ошибкой - если быpickle.loads
возвращал ссылку на ранее существующий объект, а не ссылку на вновь созданный объект. Таким образом, тестированиеs is s1
ничего не говорит о пригодности использования модулей в качестве синглтонов.pickle.loads()
уже делает это, например, для случаевbool
иNoneType
.pickle.loads(pickle.dumps(False)) is False
урожайностьTrue
True
,False
иNone
, и не имеет ничего общего с кодом позадиpickle.loads
. Кроме того, это безопасно делать только для объектов только для чтения. Еслиpickle.loads
бы возвращалась ссылка на уже существующий модифицируемый объект, такой как модуль, это было бы ошибкой. (И поэтому я поддерживаю свое предположение, что пример кодаdivbyzero ничего не доказывает.)Используйте модуль. Импортируется только один раз. Определите в нем некоторые глобальные переменные - они будут «атрибутами» синглтона. Добавьте некоторые функции - «методы» синглтона.
источник
Вы, вероятно, никогда не нуждаетесь в синглтоне в Python. Просто определите все свои данные и функции в модуле, и у вас есть де-факто синглтон.
Если вам действительно нужно иметь синглтон-класс, то я бы пошел с:
Использовать:
где mysingleton.py - ваше имя файла, в котором определен My_Singleton. Это работает, потому что после первого импорта файла Python не выполняет код повторно.
источник
Вот одна строка для вас:
Вот как вы используете это:
Ваш объект получает экземпляр с нетерпением. Это может или не может быть то, что вы хотите.
источник
type(wat)
илиwat.__class__
. Если вы действительно хотите достичь этого, лучше определите класс и создайте его экземпляр сразу, не нужно путаться с декоратором.wat2 = type(wat)()
, но это питон, мы все взрослые по соглашению и все такое. Вы не можете гарантировать, что будет только один экземпляр, но вы можете гарантировать, что если люди сделают второй, он будет выглядеть уродливо и - если они порядочные, порядочные люди - станет для них предупреждающим знаком. Что мне не хватает?Проверьте вопрос переполнения стека. Есть ли простой, элегантный способ определения синглетонов в Python?с несколькими решениями.
Я настоятельно рекомендую посмотреть выступления Алекса Мартелли о шаблонах проектирования в python: часть 1 и часть 2 . В частности, в части 1 он говорит о синглетонах / объектах общего состояния.
источник
Вот моя собственная реализация синглетонов. Все, что вам нужно сделать, это украсить класс; чтобы получить синглтон, вы должны использовать
Instance
метод. Вот пример:И вот код:
источник
SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)())
должен печататьTrue
в истинном синглтоне; с отпечатками вашего кодаFalse
Метод 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, вам нужно сделать что-то вроде:
Я предполагаю, что «объект» в назначении должен быть заменен «BaseClass», но я не пробовал это (я пробовал код, как показано).
источник
class MyClass(metaclass=Singleton)
Ну, кроме как согласиться с общим предложением Pythonic о глобальном уровне модуля, как насчет этого:
Выход:
источник
_sealed
атрибута? Насколько я вижу, это ничего не делает? Что-то беспокоит меня из-за того, что это не должно хорошо работать ... Позже на этой неделе я проведу несколько сравнительных тестов.__init__
который будет вызываться при каждой инициализации. Просто простое «Инициализация в классе. Метод». Что касается отступа - вы использовали табуляцию и пробелы - я исправил большую часть, но, кажется, пропустил одну, если вы хотите ее получить (просто проверьте журнал редактирования)Как насчет этого:
Используйте его как декоратор для класса, который должен быть одноэлементным. Нравится:
Это похоже на
singleton = lambda c: c()
декоратор в другом ответе. Как и другое решение, единственный экземпляр имеет имя класса (MySingleton
). Тем не менее, с помощью этого решения вы все равно можете «создавать» экземпляры (фактически получая единственный экземпляр) из классаMySingleton()
. Он также не позволяет вам создавать дополнительные экземплярыtype(MySingleton)()
(это также возвращает тот же экземпляр).источник
type(MySingleton)()
, вызывается ,MySingleton.__init__()
и объект инициализируется несколько раз; Вы можете исправить это, написавcls.__init__ = lambda self: pass
на своемsingleton
. Кроме того, переопределениеcls.__call__
кажется бессмысленным и даже вредным -__call__
определенные в этом контексте используются при вызовеMySingleton(any, list, of, arguments)
, а не при вызовеtype(MySingleton)(any, list, of, arguments)
.__init__()
снова вызывается при выполненииtype(MySingleton)()
. Предлагаемое вами решение (добавлениеcls.__init__ = lambda self: pass
) дает синтаксическую ошибку, поскольку последняя часть лямбда-выражения должна быть выражением, а не выражением. Однако добавлениеcls.__init__ = lambda self: None
работает, поэтому я добавил это к своему ответу.__call__
. я намеревался сделать обаtype(MySingleton)()
иMySingleton()
вернуть экземпляр. Так что делаю то, что хотел. Вы можете думать о MySingleton как о типе синглтона или экземпляре синглтона (или обоих).Я брошу свою в ринг. Это простой декоратор.
Я думаю, что это имеет преимущества перед некоторыми другими решениями:
YourClass
. Это включает в себя отсутствие необходимости использовать метакласс для вашего класса (обратите внимание, что метакласс выше находится на фабрике, а не на «реальном» классе).YourClass
, это выглядит как класс (потому что это так), и они используют его как обычно. Нет необходимости адаптировать абонентов к заводским функциям.YourClass()
что создается, все еще является истинным экземпляромYourClass
реализованного вами, а не каким-либо прокси-сервером, поэтому никаких побочных эффектов в результате этого нет.isinstance(instance, YourClass)
и аналогичные операции все еще работают как положено (хотя этот бит требует abc, поэтому исключает Python <2.6).У меня возникает один недостаток: методы класса и статические методы реального класса не прозрачно вызываются через скрывающий их класс фабрики. Я использовал это достаточно редко, чтобы мне никогда не приходилось сталкиваться с этой потребностью, но это было бы легко исправить с помощью пользовательского метакласса на фабрике, который реализует
__getattr__()
делегирование доступа к атрибуту all-ish реальному классу.Связанный паттерн, который я на самом деле нашел более полезным (не то чтобы я говорил, что такие вещи вообще нужны очень часто) - это «уникальный» паттерн, в котором создание экземпляра класса с одинаковыми аргументами приводит к получению одного и того же экземпляра. Т.е. "синглтон на аргументы". Вышесказанное адаптируется к этому хорошо и становится еще более кратким:
С учетом всего вышесказанного я согласен с общим советом, что, если вы считаете, что вам нужна одна из этих вещей, вам действительно стоит остановиться и спросить себя, действительно ли вы это делаете. 99% времени, ЯГНИ.
источник
serial
связь, и для создания экземпляра вы хотите отправить последовательный порт в качестве аргумента, то при традиционном подходе работать не будетисточник
Код основан на ответе Толли .
Объяснение:
Создать новый класс, наследуя от заданного
cls
(он не изменяется
cls
в случае, если кто-то хочет, напримерsingleton(list)
)Создать экземпляр. До переопределения
__new__
это так просто.__new__
метод, определенный момент назад.Функция возвращает
instance
только когда это то, что ожидает вызывающая сторона, иначе вызываетTypeError
.Условие не выполняется, когда кто-то пытается унаследовать от декорированного класса.
instance
уже инициализирована, поэтому функция заменяется__init__
на функцию, которая ничего не делает.Посмотрите, как работает онлайн
источник
Это немного похоже на ответ от потрясающего, но не совсем то же самое.
Контракт синглтона не требует , чтобы мы смогли вызвать конструктор несколько раз. Поскольку синглтон должен создаваться один раз и только один раз, разве он не должен создаваться только один раз? «Подмена» конструктора, возможно, ухудшает разборчивость.
Так что мое предложение как раз так:
Это не исключает использования конструктора или поля
instance
пользовательским кодом:... если вы знаете наверняка, что
Elvis
еще не было создано, и этоKing
имеет.Но это поощряет пользователей использовать
the
метод повсеместно:Для этого вы также можете переопределить,
__delattr__()
чтобы вызвать исключение, если была сделана попытка удалитьinstance
, и переопределить__del__()
чтобы оно вызвало Исключение (если мы не знаем, что программа заканчивается ...)Дальнейшие улучшения
Я благодарю тех, кто помог с комментариями и правками, из которых больше приветствуются. Хотя я использую Jython, это должно работать более широко и быть поточно-ориентированным.
Примечания:
__new__
__new__
вы должны декорировать с помощью @classmethod или__new__
вы будете использовать метод несвязанного экземпляра.the
свойство уровня класса, возможно, переименовав его вinstance
источник
__new
__ вместо__init__
, так как он действует исключительно на атрибуты класса, и это не дает возможности кратко быть вторым экземпляром. Разница между этим и методом 2 заключается в том, возвращает ли попытка инициализации более одного раза один экземпляр или вызывает исключение. Я думаю, что я счастлив, что либо удовлетворяю одноэлементному шаблону, один проще в использовании, а другой более явный, чем одноэлементный.__init__
предотвращает__init__
так, чтобы, надеюсь, это было подклассифицировано ...the
возможно, выиграет от того, чтобы быть классным методом по тем же причинамОдин лайнер (я не горжусь, но он делает свою работу):
источник
Если вам не нужна ленивая инициализация экземпляра Singleton, то следующее должно быть простым и поточно-ориентированным:
Этот способ
A
является одиночным инициализированным при импорте модуля.источник
Может быть, я неправильно понимаю шаблон синглтона, но мое решение такое простое и прагматичное (питоническое?). Этот код выполняет две цели
Foo
доступным везде (глобально).Foo
Может существовать только один экземпляр .Это код.
Вывод
источник
Поработав с этим в течение некоторого времени, я, в конце концов, придумал следующее, чтобы объект конфигурации загружался только один раз при вызове из отдельных модулей. Метакласс позволяет хранить экземпляр глобального класса во встроенном файле dict, который в настоящее время представляется наиболее подходящим способом хранения надлежащей глобальной программы.
источник
Я не могу вспомнить, где я нашел это решение, но я нахожу его наиболее «элегантным» с моей точки зрения не Python-эксперта:
Почему мне это нравится? Никаких декораторов, мета-классов, множественного наследования ... и если вы решите, что больше не хотите, чтобы он был Singleton, просто удалите
__new__
метод. Поскольку я новичок в Python (и ООП в целом), я ожидаю, что кто-то объяснит мне, почему это ужасный подход?источник
__new__
. Не повторяйся .*args
и**kwargs
, а затем ничего не делает с ними? Передайте их вdict.__new__
таким образом:dict.__new__(cls, *args, **kwargs)
.__init__
метод каждый раз, когда вызывается класс. Если ваш__init__
метод действительно что-то сделал, вы заметите проблему. Всякий раз, когда вы делаетеSomeSingleton()
, состояние вашего синглтона сбрасывается__init__
методом.Это мой предпочтительный способ реализации синглетонов:
источник
Этот ответ, вероятно, не то, что вы ищете. Я хотел синглтон в том смысле, что только этот объект имел свою идентичность, для сравнения. В моем случае это использовалось как Sentinel Value . На что ответ очень прост, сделайте любой объект
mything = object()
и по природе Python, только эта вещь будет иметь свою идентичность.источник
eval
илиimportlib.reload
.Это решение вызывает некоторое загрязнение пространства имен на уровне модуля (три определения, а не одно), но мне легко следовать.
Я хотел бы иметь возможность написать что-то вроде этого (ленивая инициализация), но, к сожалению, классы не доступны в теле их собственных определений.
Поскольку это невозможно, мы можем разбить инициализацию и статический экземпляр в
Стремительная инициализация:
Ленивая инициализация:
Стремительная инициализация:
источник