Створення синглтона в Python


945

Це питання не для обговорення того, бажана чи не однотонна модель дизайну , є антидіаграмою чи для будь-яких релігійних війн, а для обговорення того, як цей шаблон найкраще реалізується в 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 є функцією aa, а не класом, тому ви не можете викликати з нього методи класу. Також для 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: Метаклас

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

Метод 5: модуль

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

Плюси

  • Просте - краще, ніж складне

Мінуси


12
Ще три методи: замість цього використовуйте модуль (часто - як правило, я думаю - це більш підходящий зразок для Python, але це трохи залежить від того, що ви робите з ним); зробіть один екземпляр і обробіть його замість цього ( foo.xабо якщо ви наполягаєте Foo.xзамість нього Foo().x); використовувати атрибути класу та статичні / класичні методи ( Foo.x).
Кріс Морган

10
@ChrisMorgan: Якщо ви збираєтеся використовувати лише методи класу / статики, то не поспішайте робити клас.
Cat Plus Plus

2
@Cat: Ефект схожий, проте причини, що створюють глобальну змінну, можуть бути майже в усьому, у тому числі і не знати нічого кращого. Чому один створює одиночку? Якщо ви повинні запитати, ви не повинні бути тут. Ця явність не тільки більше пітонічна, але робить технічне обслуговування набагато простішим. Так, одинаки - це синтаксичний цукор для глобальних, але тоді класи - це синтаксичний цукор для цілого ряду непривабливих речей, і я не думаю, що ніхто скаже вам, що вам завжди краще без них.
theheadofabroom

14
@BiggAl: Синглтони не є пітонічними, незалежно від того, як ви їх реалізуєте. Вони в кращому випадку є ознакою хибного дизайну.
Cat Plus Plus

8
Настрій проти знаків - це найгірше програмування культового культу. Так само і з людьми, які чують (мало кого турбує насправді читати) "Заява Goto вважається шкідливою" і вважають, що готос є ознакою поганого коду незалежно від контексту.
Hejazzman

Відповіді:


658

Використовуйте метаклас

Я б рекомендував метод №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(), Python спочатку запитує метаклассом Logger, Singletonщо ж робити, що дозволяє створення екземпляра бути попередньо спорожнити. Цей процес такий же, як і Python, запитуючи клас, що робити, зателефонувавши, __getattr__коли ви посилаєтесь на один з його атрибутів, виконуючи завдання myclass.attribute.

Метаклас по суті визначає, що означає визначення класу і як реалізувати це визначення. Дивіться, наприклад, http://code.activestate.com/recipes/498149/ , який по суті відтворює C-стилі structв Python, використовуючи метакласи. Нитка Які деякі (конкретні) випадки використання для метакласів? також наводить деякі приклади, вони, як правило, пов'язані з декларативним програмуванням, особливо, як це використовується в ORM.

У цій ситуації, якщо ви використовуєте ваш метод №2 , а підклас визначає __new__метод, він буде виконуватися щоразу, коли ви викликаєте SubClassOfSingleton()- тому що він відповідає за виклик методу, який повертає збережений екземпляр. З метакласом він буде викликаний лише один раз , коли буде створений єдиний екземпляр. Ви хочете налаштувати, що означає називати клас , який визначається його типом.

Взагалі, для реалізації синглтона має сенс використовувати метаклас. Синглтон особливий тим, що створюється лише один раз , а метаклас - це спосіб налаштувати створення класу . Використання метакласу дає вам більше контролю у випадку, якщо вам потрібно налаштувати визначення однокласного класу іншими способами.

Вашим одиночним клавішам не буде потрібно багатократне успадкування (оскільки метаклас не є базовим класом), але для підкласів створеного класу, які використовують множинне успадкування, вам потрібно переконатися, що клас однотонних є першим / лівим лівим з метакласом, який визначає повторно __call__Це навряд чи буде проблемою. Дікт екземпляра відсутній у просторі імен екземпляра, тому його випадково не перезаписати.

Ви також почуєте, що одинарний зразок порушує "Принцип єдиної відповідальності" - кожен клас повинен робити лише одне . Таким чином, вам не доведеться турбуватися про те, щоб зіпсувати одне, що робить код, якщо вам потрібно змінити інше, оскільки вони є окремими та інкапсульованими. Реалізація метакласу проходить цей тест . Метаклас відповідає за виконання шаблону, і створений клас та підкласи не повинні знати про те, що вони є однотонними . Метод №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__їх використання.

Коли використовувати одинарний візерунок

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

Коли люди кажуть, що одиночні особи погані, найпоширенішою причиною є те, що це неявний загальний стан . У той час як глобальні змінні та імпорт модулів вищого рівня є явним загальним станом, інші об'єкти, які передаються навколо, як правило, інстанціюються. Це хороший момент, за двома винятками .

Перше і те, що згадується в різних місцях, це коли одинаки постійні . Використання глобальних констант, особливо переліків, є загальноприйнятим і вважається розумним, тому що, незважаючи ні на що, жоден з користувачів не може зіпсувати їх іншим користувачам . Це однаково справедливо для постійного сингтона.

Другий виняток, про який менше згадують, - навпаки - коли сингтон - це лише раковина даних , а не джерело даних (прямо чи опосередковано). Ось чому лісоруби відчувають себе як "природне" використання для одиночних. Оскільки різні користувачі не змінюють реєстратори способами, якими будуть піклуватися інші користувачі, стан справді не є спільним . Це заперечує основний аргумент проти однотонної картини і робить їх розумним вибором через їх простоту використання для виконання завдання.

Ось цитата з http://googletesting.blogspot.com/2008/08/root- why-of-singletons.html :

Тепер є один вид Сінглтона, який добре. Це синглтон, де всі доступні об'єкти незмінні. Якщо всі об'єкти незмінні, то Сінглтон не має глобального стану, оскільки все є постійним. Але так легко перетворити цей вид одинарного в перемінний, це дуже слизький схил. Тому я і проти цих синглтонів, не тому, що вони погані, а тому, що їм дуже просто погано. (Як бічна примітка, перерахування Java - це лише такі одинаки. Поки ви не введете стан у свій перелік, ви все в порядку, тому, будь ласка, не робіть це.)

Інший вид Сінглтонів, які є напівприйнятними, - це ті, які не впливають на виконання вашого коду. Вони не мають "побічних ефектів". Ідеальний приклад - ведення журналів. Він завантажений Singletons та глобальним станом. Це прийнятно (так як це не зашкодить вам), оскільки ваша програма не поводиться нічим, незалежно від того, включений чи ні той реєстратор. Інформація тут протікає одним способом: із вашої програми до реєстратора. Навіть думки реєстраторів є глобальним станом, оскільки від реєстраторів до вашої програми не надходить інформація, реєстратори є прийнятними. Ви все одно повинні вводити свій реєстратор, якщо ви хочете, щоб ваш тест стверджував, що щось зареєстровано, але в цілому Logger не є шкідливим, незважаючи на те, що він повний.


5
Ні, одинаки ніколи не бувають добрими. Ведення журналів може бути хорошим кандидатом на те, що він є глобальним (настільки ж жахливим, як вони), але, звичайно, не одиноким.
Cat Plus Plus

11
Подивіться на googletesting.blogspot.com/2008/08 / ... . Це, як правило, анти-синглтон (з поважних причин), але він добре пояснює, чому незмінні сингли і однолітки без побічних ефектів не мають однакових проблем, якщо ви обережні. Я трохи процитую це наприкінці своєї публікації.
agf

4
Моя проблема з одинаками - дурна передумова "лише одного екземпляра". Це і тонни проблем безпеки ниток. І залежність ховається. Глобали погані, а одиночні - просто глобалісти з більшими проблемами.
Cat Plus Plus

4
@Cat Є дуже хороше використання для одиночних кнопок. Ледача інсталяція апаратних модулів (особливо в однопотокових додатках) - одна з них (але існують і безпечні однопоточні сигнали).
Пол Манта

3
@Alcott __new__в метакласі - це коли клас новий - коли він визначений, а не коли екземпляр буде новим. Виклик класу ( MyClass()) - це операція, яку ви хочете змінити, а не визначення класу. Якщо ви дійсно хочете зрозуміти, як працює Python, найкраще, що ви можете зробити (крім того, щоб продовжувати його використовувати) - це прочитати docs.python.org/reference/datamodel.html . Хороша посилання на метакласи - eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example . Хороша стаття про одиночних - серія з блогу google, яку я пов’язав у цій відповіді.
agf

91
class Foo(object):
     pass

some_global_variable = Foo()

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


14
чому ти сказав "Не використовуй одиночних"? Будь-яка причина?
Алькотт

3
Це не спрацює, якщо сінгл доводиться маринувати. Скориставшись прикладом, який ви дали:s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False
dividebyzero

4
@dividebyzero: isтест оператора на рівність вказівника. Я б дуже здивувався --- тому, що називаю це помилкою --- якщо б pickle.loadsповернув посилання на попередньо існуючий об'єкт, а не посилання на новостворений. Таким чином, тестування, чи s is s1не розповість вам нічого про придатність використання модулів як одиночних.
Йонас Келькер

1
@ JonasKölker pickle.loads()робить це вже, наприклад, для примірників boolта NoneType. pickle.loads(pickle.dumps(False)) is FalseврожайністьTrue
Дан Пассаро

2
@ leo-the-manic: справедлива точка; однак, це лише побічний ефект, коли Python інтернірує об'єкти True, Falseі Noneне має нічого спільного з кодом, що знаходиться позаду pickle.loads. Крім того, це безпечно робити лише для об'єктів лише для читання. Якби pickle.loadsповернути посилання на вже існуючий об'єкт, що може змінюватися - наприклад, на модуль - це була б помилка. (І тому я стою під своїм висновком, що приклад коду дивіденсеро нічого не підтверджує.)
Йонас Кьолкер

69

Використовуйте модуль. Він імпортується лише один раз. Визначте в ньому деякі глобальні змінні - вони будуть атрибутами сингтона. Додайте деякі функції - "методи" синглтона.


11
Отже, з чим ви закінчуєтесь - це не клас. Ви не можете використовувати його як клас, ви не можете базувати на ньому інші класи, ви використовуєте синтаксис імпорту, і раптом ви втрачаєте всі переваги OOP ...
theheadofabroom

16
якщо ви можете базувати на ньому інші класи, то це може бути не сингл. Ви можете створити один із похідних класів, але також один із базового класу, але похідний клас також є членом базового, і у вас є два базових, який ви маєте використовувати?
SingleNegationElimination

Це не працює в модулях. У своєму "головному" модулі я встановив значення. Потім я посилаюсь на нього в іншому модулі та його null. Зітхнути.
Пол Кеньора

1
@PaulKenjora У вас повинен бути помилка в коді. Якщо ви визначаєте глобальну змінну в модулі, коли ви отримуєте доступ до неї з іншого модуля, вона повинна мати значення.
warvariuc

Він має значення, але коли я його змінюю, він не зберігається. Я закінчив використовувати клас із властивостями у модулі, який працює. Прості типи як глобальні для мене не спрацювали (вони втратили значення, як тільки змінилася область).
Пол Кенджора

29

Напевно, ніколи не потрібен сингл в 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 не повторно виконує код.


4
В основному це правда, але іноді цього недостатньо. Наприклад, у мене є проект, який повинен реєструвати дані багатьох класів на рівні DEBUG. Мені потрібні параметри командного рядка, розібрані при запуску для того, щоб встановити визначений користувачем рівень журналу до того, як ці класи будуть ініційовані. Інформація на рівні модулів робить це проблематичним. Можливо, я міг би ретельно структурувати додаток, щоб усі ці класи не імпортувалися, поки не буде виконана обробка CLI, але природна структура мого додатка важливіша, ніж догматичне прихильність до "синглтони погані", оскільки вони можуть бути зроблено досить чисто.
CryingCyclops

якби ви перевірили свій код під час виправлення my_singleton, чи можливо це? оскільки цей my_singleton міг бути інстанційним у якомусь іншому модулі.
Naveen

@Naveen - my_singleton - це єдиний об’єкт. Якщо ви "виправите" це зміна, це вплине на всі майбутні посилання, навіть в інших модулях.
Алан Дайк

16

Ось один-лайнер для вас:

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

Ваш об'єкт охоче отримує інстанцію. Це може бути або не бути тим, що ви хочете.


5
-1. Це дуже погано і нерозумно. Ви не визначаєте клас для використання в якості об'єкта. Ви більше не можете отримати доступ до класу, не будучи дуже некрасивим type(wat)або wat.__class__. Якщо ви дійсно хочете цього домогтися, краще визначте клас та одразу ж його придумуйте, не потрібно возитися з декоратором.
0xc0de

2
Чому потрібно знати клас одинарних? Просто використовуйте однотонний об’єкт ..
Толлі

1
Це не однотонний візерунок , тому функцію IMO слід називати інакше.
GingerPlusPlus

8
Вікіпедія: "Одинарний шаблон - це шаблон дизайну, який обмежує інстанціювання класу одним об'єктом". Я б сказав, що моє рішення робить саме це. Гаразд, я думаю, що можна було б зробити wat2 = type(wat)(), але це пітон, ми всі погоджуємося на дорослих і все таке. Ви не можете гарантувати, що буде лише один екземпляр, але ви можете гарантувати, що якщо люди зроблять другий, він буде виглядати некрасиво і - якщо вони будуть порядні, доброзичливі люди - як попереджувальний знак для них. Що я пропускаю?
Йонас Кёлкер

7

Перевірте питання про переповнення стека. Чи існує простий, елегантний спосіб визначення синглів у Python? з кількома рішеннями.

Я настійно рекомендую спостерігати за розмовами Алекса Мартеллі щодо моделей дизайну в пітоні: частина 1 та частина 2 . Зокрема, у частині 1 він розповідає про одиночні / спільні об'єкти стану.


2
Хоча це насправді не відповідь на моє запитання, ресурси, які ви вказуєте, дуже корисні. Я з
грубою надією

3

Ось моя власна реалізація синглів. Все, що вам потрібно зробити - це прикрасити клас; щоб отримати синглтон, тоді вам доведеться скористатися 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
Це не справжній синглтон. SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)())слід друкувати Trueсправжнім синглтоном; з відбитками кодуFalse
GingerPlusPlus

@GingerPlusPlus Я знав про деякі обмеження, але не про те, яке ти вказав. Дякуємо, що згадали про це. На жаль, на даний момент у мене немає часу роздумувати над цим рішенням.
Пол Манта

2

Спосіб 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», але я цього не пробував (я спробував код як показано).


напевно, це не метаклас - у python3 використовувати метаклас для побудови MyClass ви б це зробилиclass MyClass(metaclass=Singleton)
theheadofabroom

Посилання mikewatkins.ca (ефективно) порушено.
Пітер Мортенсен

1

Ну, крім згоди з загальною пітонічною пропозицією щодо глобального рівня модулів, як щодо цього:

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

У чому сенс _sealedатрибута? Наскільки я бачу, це нічого не робить? Щось знущається з цього приводу, що говорить, що воно не повинно працювати добре ... Я проведу кілька порівняльних тестів пізніше цього тижня.
theheadofabroom

_sealje забезпечує, що ваш init запускається лише один раз; Я не бачу сенсу, чому він повинен виконуватись гірше, ніж звичайний декоратор, подібний до функцій - функція виконується лише один раз у класі та повертає новий успадкований клас
Guard

До речі, ваша редакція містить вкладки, які порушують відступи. Ви також говорите: "ми створюємо 2 класи" - ви маєте на увазі, що ми створюємо "1 додатковий клас"?
Гвардія

Так, я мав на увазі один додатковий клас. Я маю намір включити речі, __init__які потрібно викликати кожен раз, коли він ініціалізується. Просто простий ініціалізація у class.method. що стосується відступу - ви використовували вкладки та пробіли - я виправив більшу частину цього, але, здається, пропустив один, якщо ви хочете його отримати (просто перевірте журнал редагування)
theheadofabroom

re init : звичайно, це залежить від вас, я просто намагався імітувати поведінку сингтона іншими мовами, де код конструктора (який не зовсім init , але дуже близький за своїм значенням) викликається лише один раз, якщо ви хочете, щоб ініт був щоразу дзвонять, просто вбивайте всі посилання на _запечатані повторні пробіли / вкладки - ну тоді мої емаки потребують виправлення. у будь-якому випадку, виправлена ​​версія
Guard

1

Як щодо цього:

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


Ви не визначаєте клас, щоб використовувати його як об'єкт.
0xc0de

1
Кожен раз, коли ви телефонуєте type(MySingleton)(), вам дзвонять MySingleton.__init__()і об'єкт ініціалізується кілька разів; ви можете виправити це написання cls.__init__ = lambda self: passу своєму singleton. Крім того, переосмислення cls.__call__здається безглуздим і навіть шкідливим - __call__визначене в цьому контексті використовується під час дзвінка MySingleton(any, list, of, arguments), а не під час дзвінка type(MySingleton)(any, list, of, arguments).
GingerPlusPlus

@GingerPlusPlus, спасибі за вказівку на те , що __init__()викликається знову при виконанні type(MySingleton)(). Запропоноване вами рішення (додавання cls.__init__ = lambda self: pass) дає синтаксичну помилку, оскільки остання частина виразу лямбда має бути виразом, а не висловом. Однак додавання cls.__init__ = lambda self: Noneробіт, тому я додав це до своєї відповіді.
Толлі

1
@GingerPlusPlus, Що стосується використання __call__. мій намір полягав у тому, щоб зробити і те, type(MySingleton)()і MySingleton()інстанцію повернути. Так це робиться те, що я хотів. Ви можете вважати MySingleton як тип синглтона або екземпляр сингтона (або обидва).
Толлі

1

Я кину міну на ринг. Це простий декоратор.

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

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


1
  • Якщо ви хочете мати декілька примірників одного класу, але тільки якщо арги або kwargs різні, можна використовувати це
  • Вих.
    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
>>>

0

Код на основі відповіді Толлі .

#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

Він трохи схожий на відповідь 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

Хоча це дещо інша інтерпретація однотонного шаблону, я майже впевнений, що він все-таки справедливий, хоча, можливо, я б спокусився використовувати __new__, а не __init__, оскільки він суто діє на атрибути класу, і це заважає коротко бути другою інстанцією. Відмінність між цим і методом 2 полягає в тому, чи намагається ініціалізувати більше одного разу повертає один екземпляр, або збільшує виняток. Я думаю, що я щасливий, що або задовольнити однотонний малюнок, один простіший у використанні, а інший більш чітко виражений, що це синглтон.
theheadofabroom

Очевидно використання назви класу у __init__запобіганні підкласифікації, але, хоча це полегшує справи, це не потрібно
theheadofabroom

Дякую ... ах, так, миттєвий другий екземпляр перед тим, як Виключення буде кинуто. Я змінив __init__так, що, сподіваюся, це має бути підкласом ...
Майк гризун

Класно, theможливо, виграв би метод класу з подібних причин
theheadofabroom

так, ви праві. Тоді ви можете мати однокласний підклас SuperElvis і (наприклад) однокласний підклас ImaginaryElvis ... і вони можуть співіснувати. Дивіться додаткові думки. Будь ласка, не соромтеся покращити мій код.
мійський гризун

0

Один лайнер (я не пишаюся, але це робить роботу):

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

Це може зробити цю роботу до тих пір, поки ваш клас не буде імпортований до будь-яких інших модулів ...
Aran-Fey

Правда. Якщо ви можете оплатити витрати на вступ до класу, ви можете запустити Myclass () відразу після визначення класу, щоб уникнути цього.
polvoazul

0

Якщо вам не потрібна ледача ініціалізація екземпляра Singleton, то наступне має бути простим і безпечним для потоків:

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

Цей спосіб Aє синхронізованим при імпорті модуля.


0

Можливо, я неправильно розумію однотонний малюнок, але моє рішення таке просте і прагматичне (пітонічне?). Цей код заповнює дві мети

  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!

0

Після деякого бою з цим я врешті-решт придумав наступне, так що об’єкт config буде завантажений лише один раз, коли викликається з окремих модулів. Метаклас дозволяє зберігати екземпляр глобального класу у вбудованому диктаті, що в даний час представляється найменшим способом зберігання належної програмної глобальної програми.

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

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

-1

Я не можу пригадати, де я знайшов це рішення, але вважаю, що воно є найелегантнішим з моєї точки зору, що не є 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 (і OOP взагалі), я сподіваюся, що хтось поставить мене прямо, чому це жахливий підхід?


2
чому це жахливий підхід? коли ви хочете створити інший однокласовий клас, ви повинні скопіювати та вставити __new__. Не повторюйте себе .
GingerPlusPlus

Крім того, чому ваш новий бере *argsі **kwargs, а потім нічого не робить з ними? Передайте їх в dict.__new__такий спосіб: dict.__new__(cls, *args, **kwargs).
GingerPlusPlus

Це буде викликати __init__метод щоразу, коли викликається клас. Якщо ваш __init__метод насправді щось зробив, ви помітили б проблему. Щоразу, коли SomeSingleton()стан вашого одиночного чоловіка скидається __init__методом.
Аран-Фей

-2

Це мій кращий спосіб реалізації синглів:

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

1
Це не є однотонним, оскільки дозволяє існувати більше одного примірника класу. Поліпшення
полягало

-2

Ця відповідь, ймовірно, не те, що ви шукаєте. Я хотів, щоб одинак ​​був у тому сенсі, що лише цей об'єкт мав свою ідентичність для порівняння. У моєму випадку він використовувався як значення Sentinel . На що відповідь дуже проста, зробіть будь-який предмет mything = object()і за природою пітона, тільки ця річ матиме свою ідентичність.

#!python
MyNone = object()  # The singleton

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

Я дізнався, що модулі насправді можна імпортувати кілька разів, в такому випадку це лише локальний синглтон, який насправді не є синглетом ні в якій якості.
ThorSummoner

Чи можете ви детальніше пояснити, як модуль можна імпортувати кілька разів? Єдиний раз, коли я бачив це, коли відбувається виняток під час завантаження модуля, користувач може все-таки завантажити модуль пізніше, але побічні ефекти вже відбудуться, тому деякі дії можуть бути виконані вдруге.
sleblanc

Після того, як модуль повністю завантажений, я не бачу способу запустити цей модуль ще раз, крім примушування інтерпретатора робити це за допомогою evalабо importlib.reload.
sleblanc

-3

Це рішення спричиняє забруднення простору імен на рівні модуля (три визначення, а не лише одне), але мені легко прослідкувати.

Я хотів би мати можливість написати щось подібне (лінива ініціалізація), але, на жаль, заняття недоступні в тілі власних визначень.

# 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.