Tworzenie Singletona w Pythonie

to pytanie nie jest do dyskusji, czy wzór singleton design pattern jest pożądany, jest anty-pattern, lub dla jakichkolwiek wojen religijnych, ale do dyskusji, jak ten wzór jest najlepiej zaimplementowany w Pythonie w taki sposób, który jest najbardziej pythoniczny. W tym przypadku definiuję "najbardziej pythoniczny", aby oznaczać, że jest on zgodny z "zasadą najmniejszego zdziwienia" .

Mam wiele klas, które staną się singletonami (mój przypadek użycia jest dla loggera, ale to nie jest ważne). Nie chcę zaśmiecać kilku klas dodatkiem gumph, kiedy mogę po prostu odziedziczyć lub ozdobić.

Najlepsze metody:


Metoda 1: dekorator

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

Plusy

  • dekoratory są addytywne w sposób, który jest często bardziej intuicyjny niż wielokrotne dziedziczenie.

Cons

  • podczas gdy obiekty utworzone za pomocą MyClass() byłyby prawdziwymi obiektami Singletona, MyClass jest funkcją A, a nie klasą, więc nie można wywołaj z niego metody klasy. Również dla
x = MyClass();
y = MyClass();
t = type(n)();

Potem x == y ale x != t && y != t


Metoda 2: klasa bazowa

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

Plusy

  • to prawdziwa klasa

Cons

  • wielokrotne dziedziczenie-eugh! __new__ może być nadpisany podczas dziedziczenia z drugiej klasy bazowej? Trzeba myśleć więcej niż trzeba.

Metoda 3: A metaklass

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

Plusy

  • It ' s a true Klasa
  • Auto-magicznie zakrywa dziedziczenie
  • używa __metaclass__ dla swojego właściwego celu (i uświadomił mi o tym)

Cons

    Są jakieś?

Metoda 4: dekorator zwracający klasę o tej samej nazwie

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

Plusy

  • to prawdziwa klasa
  • Auto-magicznie zakrywa dziedziczenie

Cons

  • czy nie ma narzutu na tworzenie każdej nowej klasy? Tutaj tworzymy dwa klasy dla każdej klasy chcemy zrobić singleton. Chociaż w moim przypadku jest to w porządku, obawiam się, że może to nie być skalowane. Oczywiście jest kwestia dyskusji, czy nie jest zbyt łatwo skalować ten wzór...
  • jaki jest sens atrybutu _sealed
  • nie można wywoływać metod o tej samej nazwie na klasach bazowych używając super(), ponieważ będą one rekurencyjne. Oznacza to, że nie możesz dostosować __new__ i nie możesz podklasować klasy, do której musisz zadzwonić __init__.

Metoda 5: Moduł

Plik modułu singleton.py

Plusy

  • proste jest lepsze niż złożone

Cons

  • nie leniwie]}
Author: Elijas Dapšauskas, 2011-07-20

26 answers

Użyj Metaklasy

Polecam metodę # 2 , ale lepiej używać metaclass niż klasy bazowej. Oto przykładowa implementacja:

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

Lub w Python3

class Logger(metaclass=Singleton):
    pass

Jeśli chcesz uruchomić __init__ za każdym razem, gdy klasa jest wywoływana, dodaj

        else:
            cls._instances[cls].__init__(*args, **kwargs)

Do if oświadczenia w Singleton.__call__.

Kilka słów o metaklasach. Metaklasa jest klasą klasy ; to znaczy, klasa jest instancją jej metaklasy . Metaklasę obiektu w Pythonie można znaleźć za pomocą type(obj). Normalne klasy nowego stylu są Typu type. Logger w powyższym kodzie będzie Typu class 'your_module.Singleton', podobnie jak (jedyna) instancja Logger będzie typu class 'your_module.Logger'. Gdy wywołujesz logger za pomocą Logger(), Python najpierw pyta metaklasę Logger, Singleton, co zrobić, aby tworzenie instancji było poprzedzone. Proces ten jest taki sam jak Python pytający klasę, co ma zrobić, wywołując __getattr__, gdy odwołujesz się do jednego z jej atrybutów, wykonując myclass.attribute.

Metaklasa zasadniczo decyduje co oznacza definicja klasy i jak zaimplementować tę definicję. Zobacz na przykład http://code.activestate.com/recipes/498149 / , który zasadniczo odtwarza styl C structS w Pythonie przy użyciu metaklasy. Wątek jakie są (konkretne) przypadki użycia metaklasy? podaje również kilka przykładów, które zazwyczaj wydają się być związane z programowaniem deklaratywnym, szczególnie używanym w ORMs.

W tym situation, jeśli użyjesz swojej metody # 2, a podklasa definiuje metodę __new__, będzie ona wykonywana za każdym razem, gdy wywołasz -- ponieważ jest ona odpowiedzialna za wywołanie metody, która zwraca przechowywaną instancję. W przypadku metaklasy zostanie ona wywołana tylko raz , gdy zostanie utworzona jedyna instancja. Chcesz dostosować, co to znaczy wywoływać klasę , o której decyduje jej typ.

Ogólnie rzecz biorąc, to ma sens używać metaklasy aby wdrożyć singleton. Singleton jest wyjątkowy, ponieważ jest tworzony tylko raz , a metaklasa jest sposobem, w jaki dostosowujesz Tworzenie klasy . Użycie metaclass daje większą kontrolę W przypadku, gdy trzeba dostosować definicje klasy singleton w inny sposób.

Twoje singletony nie będą potrzebować dziedziczenia wielokrotnego (ponieważ metaklasa nie jest klasą bazową), ale dla podklas stworzonej klasy , które używają dziedziczenia wielokrotnego, musisz się upewnić, że Klasa singleton jest pierwsza / lewa Z metaklasą, która redefiniuje __call__ jest to bardzo mało prawdopodobne, aby był to problem. Dict instancji znajduje się Nie w przestrzeni nazw instancji , więc nie nadpisze jej przypadkowo.

Usłyszysz również, że wzór Singletona narusza "zasadę pojedynczej odpowiedzialności" - każda klasa powinna zrobić {37]} tylko jedną rzecz {38]}. W ten sposób nie musisz się martwić o zepsucie jednej rzeczy, którą robi Kod, jeśli musisz zmienić inny, ponieważ są one oddzielne i zamknięte. Implementacja metaclass przechodzi ten test . Metaklasa jest odpowiedzialna za wymuszanie wzorca , a utworzone klasy i podklasy nie muszą być świadome, że są singletonami . Metoda #1 nie powiedzie tego testu, jak zauważyłeś z "MyClass sama w sobie jest funkcją a, a nie klasą, więc nie możesz z niej wywoływać metod klasy."

Kompatybilny z Pythonem 2 i 3 Wersja

Napisanie czegoś, co działa zarówno w Python2, jak i 3, wymaga użycia nieco bardziej skomplikowanego schematu. Ponieważ metaklasy są zazwyczaj podklasami typu type, można użyć ich do dynamicznego utworzenia pośredniej klasy bazowej w czasie wykonywania, z nią jako jej metaklasą, a następnie użyć , że jako klasy bazowej publicznej Singleton klasy bazowej. Jest to trudne do wyjaśnienia niż do zrobienia, jak zilustrowano poniżej: {]}

# 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

Ironicznym aspektem tego podejścia jest to, że wykorzystuje podklasowanie do implementacji metaklasy. Jedną z możliwych zalet jest to, że w przeciwieństwie do czystej metaklasy, isinstance(inst, Singleton) zwróci True.

Korekta

W innym temacie, prawdopodobnie już to zauważyłeś, ale Implementacja klasy bazowej w Twoim oryginalnym poście jest błędna. _instances musi być odwołana do klasy, musisz użyć super() lub jesteś rekurencyjnym , a __new__ jest w rzeczywistości statyczną metodą, którą musisz przekazać klasę , a nie klasą metoda, ponieważ rzeczywista Klasa nie została jeszcze utworzona podczas jej wywoływania. Wszystkie te rzeczy będą prawdziwe również w przypadku implementacji metaklasy.

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

Dekorator Zwraca Klasę

Początkowo pisałem komentarz, ale był zbyt długi, więc dodam to tutaj. metoda # 4 jest lepsza niż inna wersja dekoratora, ale jest więcej kodu niż potrzeba dla Singletona i nie jest tak jasne, co robi.

Główne problemy wynikają z tego, że klasa jest to własna klasa podstawowa. Po pierwsze, czy to nie dziwne, że klasa jest podklasą prawie identycznej klasy o tej samej nazwie, która istnieje tylko w atrybucie __class__? Oznacza to również, że nie można zdefiniować żadnych metod, które wywołują metodę o tej samej nazwie w swojej klasie bazowej z super(), ponieważ będą one rekurencyjne. Oznacza to, że twoja klasa nie może dostosować __new__ i nie może wywodzić się z żadnych klas, które wymagają __init__ ich wywołania.

Kiedy używać wzoru Singletona

Twój przypadek użycia jest jednym z lepszych przykładów chęci użycia Singletona. Mówisz w jednym z komentarzy "dla mnie logowanie zawsze wydawało się naturalnym kandydatem do singletonów."Masz absolutną rację.

Kiedy ludzie mówią, że singletony są złe, najczęstszym powodem jest to, że są implicit shared state . Podczas gdy zmienne globalne i import modułów najwyższego poziomu są jawnym współdzielonym stanem, inne obiekty, które są przekazywane, są zazwyczaj tworzone jako instancje. To dobry punkt Z dwoma wyjątkami .

Pierwszy, i taki, który jest wymieniany w różnych miejscach, jest wtedy, gdy singletony sąstałą . Użycie stałych globalnych, zwłaszcza enum, jest powszechnie akceptowane i uważane za rozsądne, ponieważ bez względu na wszystko, żaden z użytkowników nie może ich zepsuć dla żadnego innego użytkownika[38]. Podobnie jest w przypadku stałego Singletona.

Drugi wyjątek, o którym mówi się mniej, jest odwrotnie - gdy singleton jest tylko pochłaniacz danych , a nie Źródło danych (bezpośrednio lub pośrednio). To dlatego loggery czują się jak "naturalne" zastosowanie dla singletonów. Ponieważ różni użytkownicy Nie zmieniają loggerów w sposób, na jaki inni użytkownicy będą dbać, nie ma tak naprawdę nie jest współdzielony stan . To neguje główny argument przeciwko wzorcowi Singletona i czyni je rozsądnym wyborem ze względu na łatwość użycia[37]}dla zadania.

Oto cytat z http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :

Istnieje jeden rodzaj Singletonu, który jest OK. To jest singleton, w którym wszystkie osiągalne obiekty są niezmienne. Jeśli wszystkie obiekty są niezmienne, Singleton nie ma stanu globalnego, ponieważ wszystko jest stałe. Ale tak łatwo jest zamienić ten rodzaj Singletona w zmienny, jest bardzo śliski. Dlatego też jestem przeciwko tym Singletonom, nie dlatego, że są złe, ale dlatego, że bardzo łatwo im się zepsuć. (Na marginesie Java enumeration to właśnie takie singletony. Tak długo, jak nie umieszczasz stanu w wyliczeniu, jesteś w porządku, Więc proszę, nie rób tego.]}

Inny rodzaj singletonów, które są pół-akceptowalne, to te, które nie wpływają na wykonanie kodu, nie mają "skutków ubocznych". Logowanie jest doskonałym przykładem. Jest załadowany Singletonami i stanem globalnym. Jest to dopuszczalne (bo w nim nie zaszkodzi), ponieważ aplikacja nie zachowuj się inaczej, niezależnie od tego, czy dany rejestrator jest włączony, czy nie. Informacje TUTAJ przepływają w jedną stronę: z Twojej aplikacji do REJESTRATORA. Nawet rejestratory myśli są stanem globalnym, ponieważ żadne informacje nie przepływają z rejestratorów do Twojej aplikacji, Rejestratory są akceptowalne. Nadal należy wstrzykiwać logger, jeśli chcesz, aby twój test potwierdził, że coś jest rejestrowane, ale ogólnie Rejestratory nie są szkodliwe, mimo że są pełne stanu.

 757
Author: agf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55
class Foo(object):
     pass

some_global_variable = Foo()

Moduły są importowane tylko raz, Wszystko inne jest zbyt rozmyślne. Nie używaj singletonów i staraj się nie używać globali.

 103
Author: Cat Plus Plus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-20 10:52:04

Użyj modułu. Jest importowany tylko raz. Zdefiniuj w nim zmienne globalne - będą to "atrybuty" Singletona. Dodać kilka funkcji - 'metody' Singletona.

 71
Author: warvariuc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-25 18:13:20

Prawdopodobnie nigdy nie potrzebujesz Singletona w Pythonie. Po prostu zdefiniuj wszystkie swoje dane i funkcje w module i masz de facto singleton:

import datetime
file_name=None

def set_file_name(new_file_name: str):
    global file_name
    file_name=new_file_name

def write(message: str):
    global file_name
    if file_name:
        with open(file_name, 'a+') as f:
            f.write("{} {}\n".format(datetime.datetime.now(), message))
    else:
        print("LOG: {}", message)

Do użycia:

    import log
    log.set_file_name("debug.log")
    log.write("System starting")
    ...

Jeśli naprawdę musisz mieć klasę singleton to ja bym poszedł z:

    class My_Singleton(object):
        def foo(self):
            pass

    my_singleton = My_Singleton()

Do użycia:

    from mysingleton import my_singleton
    my_singleton.foo()

Gdzie mysingleton.py jest nazwą pliku zdefiniowaną przez My_Singleton. Działa to, ponieważ po pierwszym zaimportowaniu pliku Python nie wykonuje ponownie kodu.

 38
Author: Alan Dyke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-05 18:27:28

Oto jedna linijka dla Ciebie:

singleton = lambda c: c()

Oto jak go używasz:

@singleton
class wat(object):
    def __init__(self): self.x = 1
    def get_x(self): return self.x

assert wat.get_x() == 1

Twój obiekt otrzymuje instancję z niecierpliwością. To może być to, czego chcesz.

 13
Author: Jonas Kölker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-19 14:43:33

Sprawdź pytanie o przepełnienie stosu Czy istnieje prosty, elegancki sposób definiowania singletonów w Pythonie? z kilkoma rozwiązaniami.

Zdecydowanie polecam obejrzenie wykładów Alexa Martelliego na temat wzorców projektowych w Pythonie: Część 1 i część 2. W szczególności w części 1 mówi o singletonach/wspólnych obiektach stanu.

 7
Author: Anton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:01

Oto moja własna implementacja singletonów. Wszystko, co musisz zrobić, to udekorować klasę; aby uzyskać singleton, musisz użyć metody Instance. Oto przykład:

   @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

A oto kod:

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)
 4
Author: Paul Manta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-14 13:57:18
  • Jeśli ktoś chce mieć wiele instancji tej samej klasy, ale tylko wtedy, gdy args lub kwargs są różne, można użyć zewnętrznego pakietu Pythona poręczne dekoratorki (Pakiet decorators).
  • Ex.
    1. Jeśli masz klasę obsługującą komunikację serial i aby utworzyć instancję, którą chcesz wysłać jako argument port szeregowy, to przy tradycyjnym podejściu nie będzie działać
    2. używając wyżej wymienionych dekoratorów, można utworzyć wiele instancje klasy, jeśli args są różne.
    3. dla tych samych args, dekorator zwróci tę samą instancję, która została już utworzona.
>>> 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
>>>
 3
Author: Siddhesh Suhas Sathe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-15 12:47:22

Metoda 3 wydaje się być bardzo zgrabna, ale jeśli chcesz, aby twój program działał zarówno w Python 2 i Python 3 , to nie działa. Nawet zabezpieczenie oddzielnych wariantów testami dla wersji Pythona nie powiodło się, ponieważ wersja Pythona 3 daje błąd składni w Pythonie 2.

Dzięki Mike ' owi Watkinsowi: http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/. Jeśli chcesz, aby program działał zarówno w Pythonie 2 jak i Pythonie 3, musisz coś like:

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

Zakładam, że 'obiekt' w przypisaniu musi zostać zastąpiony 'BaseClass' , ale nie próbowałem tego (próbowałem kodu, jak pokazano).

 2
Author: Tim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-14 21:54:07

Wrzucę swoje do ringu. To prosty dekorator.

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.

Korzyści, które myślę, że ma nad innymi rozwiązaniami:

  • jest jasne i zwięzłe (dla mojego oka; D).
  • jego działanie jest całkowicie zamknięte. Nie musisz zmieniać ani jednej rzeczy w implementacji YourClass. Obejmuje to brak konieczności używania metaklasy dla swojej klasy(zauważ, że powyższa metaklasa znajduje się w fabryce, a nie w" prawdziwej " klasie).
  • nie polega na łatanie wszystkiego.
  • jest przezroczysty dla dzwoniących:
    • wywołujący wciąż po prostu importują YourClass, wygląda to jak Klasa (ponieważ jest) i używają jej normalnie. Nie trzeba dostosowywać rozmówców do funkcji fabrycznej.
    • to, co YourClass() instancje jest nadal prawdziwą instancją YourClass zaimplementowaną, a nie jakiegokolwiek proxy, więc nie ma szans na skutki uboczne wynikające z tego.
    • isinstance(instance, YourClass) i podobne operacje nadal działają zgodnie z oczekiwaniami (choć ten bit wymaga abc więc wyklucza Pythona

Przychodzi mi na myśl jeden minus: classmethods i staticmethods prawdziwej klasy nie są transparentnie wywoływane przez ukrywającą ją klasę factory. Używałem tego na tyle rzadko, że nigdy nie spotkałem się z taką potrzebą, ale łatwo byłoby to naprawić, używając niestandardowej metaklasy w fabryce, która implementuje {7]} do delegowania dostępu do atrybutów do prawdziwej klasy.

Pokrewny wzorzec, który uważam za bardziej przydatny (nie jest to "unikalny" wzorzec, w którym instancjowanie klasy z tymi samymi argumentami powoduje powrót tej samej instancji. Tj. "singleton na argumenty". Powyższe dostosowuje się do tej studni i staje się jeszcze bardziej zwięzłe:

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

To wszystko powiedziane, zgadzam się z ogólną radą, że jeśli uważasz, że potrzebujesz jednej z tych rzeczy, naprawdę powinieneś przestać na chwilę i zadać sobie pytanie, czy naprawdę to robisz. 99% czas, YAGNI.

 2
Author: mikenerone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-30 04:58:41

Cóż, poza zgadzaniem się z ogólną sugestią Pythoniczną dotyczącą posiadania globalnego poziomu modułów, co powiesz na to:

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)

Wyjście To:

111     # the __init__ is called only on the 1st time
MyClass # the __name__ is preserved
True    # this is actually the same instance
 1
Author: Guard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-25 19:27:57

A Może Tak:

def singleton(cls):
    instance=cls()
    cls.__new__ = cls.__call__= lambda cls: instance
    cls.__init__ = lambda self: None
    return instance

Użyj go jako dekoratora na klasie, która powinna być singletonem. Tak:

@singleton
class MySingleton:
    #....

Jest to podobne do dekoratora singleton = lambda c: c() w innej odpowiedzi. Podobnie jak inne rozwiązanie, jedyna instancja ma nazwę klasy (MySingleton). Jednak dzięki temu rozwiązaniu możesz nadal "tworzyć" instancje (w rzeczywistości uzyskać jedyną instancję) z klasy, wykonując MySingleton(). Zapobiega to również tworzeniu dodatkowych instancji przez wykonanie type(MySingleton)() (które również zwraca to samo instancja).

 1
Author: Tolli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-02 05:25:11

Używanie atrybutu funkcji jest również bardzo proste

def f():
    if not hasattr(f, 'value'):
        setattr(f, 'value', singletonvalue)
    return f.value
 1
Author: Ruben Decrop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-07-02 09:35:48

Wolę to rozwiązanie, które okazało się bardzo jasne i proste. Używa double check na przykład, jeśli jakiś inny wątek już go stworzył. Dodatkową rzeczą do rozważenia jest upewnienie się, że deserializacja nie tworzy żadnych innych instancji. https://gist.github.com/werediver/4396488

import threading


# Based on tornado.ioloop.IOLoop.instance() approach.
# See https://github.com/facebook/tornado
class SingletonMixin(object):
    __singleton_lock = threading.Lock()
    __singleton_instance = None

    @classmethod
    def instance(cls):
        if not cls.__singleton_instance:
            with cls.__singleton_lock:
                if not cls.__singleton_instance:
                    cls.__singleton_instance = cls()
        return cls.__singleton_instance


if __name__ == '__main__':
    class A(SingletonMixin):
        pass

    class B(SingletonMixin):
        pass

    a, a2 = A.instance(), A.instance()
    b, b2 = B.instance(), B.instance()

    assert a is a2
    assert b is b2
    assert a is not b

    print('a:  %s\na2: %s' % (a, a2))
    print('b:  %s\nb2: %s' % (b, b2))
 1
Author: Andrei R.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-26 21:53:13

Kod oparty na odpowiedzi Tolli.

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

Wyjaśnienie:

  1. Utwórz nową klasę, dziedzicząc z podanego cls
    (nie modyfikuje cls w przypadku, gdy ktoś chce np. singleton(list))

  2. Utwórz instancję. Przed nadpisaniem __new__ to takie proste.

  3. teraz, gdy łatwo utworzymy instancję, nadpisujemy {[4] } używając metody zdefiniowanej przed chwilą.
  4. Funkcja zwraca instance tylko wtedy, gdy jest to co wywołujący oczekuje, inaczej podnosi TypeError.
    Warunek nie jest spełniony, gdy ktoś próbuje odziedziczyć po odznaczonej klasie.

  5. Jeśli __new__() zwróci instancję cls, to metoda __init__() nowej instancji zostanie wywołana Jak __init__(self[, ...]), gdzie self jest nową instancją, a pozostałe argumenty są takie same, jak przekazane do __new__().

    instance jest już zainicjalizowana, więc funkcja zastępuje __init__ funkcją wykonującą nic.

Zobacz jak działa on-line

 0
Author: GingerPlusPlus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:56

Jest nieco podobna do odpowiedzi fab, ale nie dokładnie taka sama.

umowa Singletona Nie wymaga, abyśmy mogli wywoływać konstruktor wiele razy. Jako że singleton powinien być stworzony tylko raz, czy nie powinien być widziany jako stworzony tylko raz? "Spoofing" konstruktora prawdopodobnie pogarsza czytelność.

Więc moja sugestia jest taka:

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

To nie wyklucza użycia konstruktora lub pola instance przez użytkownika kod:

if Elvis() is King.instance:

... jeśli wiesz na pewno, że Elvis nie została jeszcze stworzona, a King została.

Ale to zachęca użytkowników do korzystania z metody the:

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

Aby to zakończyć, możesz także nadpisać __delattr__(), aby wywołać wyjątek, jeśli zostanie podjęta próba usunięcia instance, i nadpisać __del__(), aby to spowodowało wyjątek (chyba że wiemy, że program się kończy...)

Dalsze ulepszenia


[[16]}moje podziękowania dla tych, którzy pomogły w komentarzach i edycjach, z których więcej jest mile widzianych. Podczas gdy używam Jython, powinno to działać ogólniej i być bezpieczne dla wątków.
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()

Punkty uwagi:

  1. jeśli nie podklasujesz obiektu w python2.x otrzymasz klasę w starym stylu, która nie używa __new__
  2. podczas dekorowania __new__ musisz udekorować @classmethod lub __new__ będzie niezwiązaną metodą instancji
  3. to może być poprawione poprzez użycie metaklasy, ponieważ to pozwala na stworzenie the właściwości na poziomie klasy, ewentualnie zmianę jej nazwy na instance
 0
Author: mike rodent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-22 20:39:18

Jeden liner (nie jestem dumny, ale robi swoje):

class Myclass:
  def __init__(self):
      # do your stuff
      globals()[type(self).__name__] = lambda: self # singletonify
 0
Author: polvoazul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-19 00:09:25

Jeśli nie potrzebujesz leniwej inicjalizacji instancji Singletona, to następujące czynności powinny być łatwe i bezpieczne dla wątku:

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

W ten sposób A jest singletonem inicjowanym przy imporcie modułu.

 0
Author: Serge Rogatch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-01-01 19:45:30

Może brakuje mi zrozumienia wzoru Singletona, ale moje rozwiązanie jest takie proste i pragmatyczne (pythonic?). Ten kod wypełnia dwa cele

  1. spraw, aby instancja Foo była dostępna wszędzie (globalnie).
  2. może istnieć tylko jedna instancja Foo.

To jest kod.

#!/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()

Wyjście

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
Author: buhtz says get vaccinated,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-18 20:44:52

Po pewnym czasie zmagania się z tym, w końcu wymyśliłem, co następuje, tak, że obiekt config będzie ładowany tylko raz, gdy zostanie wywołany z oddzielnych modułów. Metaclass pozwala na przechowywanie globalnej instancji klasy w builtins dict, co obecnie wydaje się być najdelikatniejszym sposobem przechowywania odpowiedniego programu global.

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>...
 0
Author: Den-Jason,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-10-18 14:24:05

Polecam eleganckie rozwiązanie przy użyciu metaklasy

class Singleton(type): 
    # Inherit from "type" in order to gain access to method __call__
    def __init__(self, *args, **kwargs):
        self.__instance = None # Create a variable to store the object reference
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            # if the object has not already been created
            self.__instance = super().__call__(*args, **kwargs) # Call the __init__ method of the subclass (Spam) and save the reference
            return self.__instance
        else:
            # if object (Spam) reference already exists; return it
            return self.__instance

class Spam(metaclass=Singleton):
    def __init__(self, x):
        print('Creating Spam')
        self.x = x


if __name__ == '__main__':
    spam = Spam(100)
    spam2 = Spam(200)

Wyjście:

Creating Spam

Jak widać na wyjściu, tylko jeden obiekt jest tworzony jako instancja

 0
Author: Dorcioman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-19 08:42:18

Wolę również składnię dekoratora niż pochodną metaklasu. Moje dwa grosze:

from typing import Callable, Dict, Set


def singleton(cls_: Callable) -> type:
    """ Implements a simple singleton decorator
    """
    class Singleton(cls_):  # type: ignore
        __instances: Dict[type, object] = {}
        __initialized: Set[type] = set()

        def __new__(cls, *args, **kwargs):
            if Singleton.__instances.get(cls) is None:
                Singleton.__instances[cls] = super().__new__(cls, *args, **kwargs)
            return Singleton.__instances[cls]

        def __init__(self, *args, **kwargs):
            if self.__class__ not in Singleton.__initialized:
                Singleton.__initialized.add(self.__class__)
                super().__init__(*args, **kwargs)

    return Singleton


@singleton
class MyClass(...):
    ...

Ma to pewne zalety nad innymi dekoratorami podanymi:

  • isinstance(MyClass(), MyClass) nadal będzie działać (zwrócenie funkcji z clausure zamiast klasy spowoduje, że isinstance się nie powiedzie)
  • property, classmethod i staticmethod nadal będzie działać zgodnie z oczekiwaniami
  • __init__() konstruktor jest wykonywany tylko raz
  • możesz dziedziczyć po swojej klasie (bezużytecznej?@ singleton again

Wady:

  • print(MyClass().__class__.__name__) zwróci Singleton zamiast MyClass. Jeśli nadal tego potrzebujesz, zalecam użycie metaklasy zgodnie z sugestią powyżej.

Jeśli potrzebujesz innej instancji opartej na parametrach konstruktora, to rozwiązanie musi zostać ulepszone(rozwiązanie dostarczone przez siddhesh-suhas-sathe zapewnia to).

Na koniec, zgodnie z innymi sugestiami, rozważ użycie modułu w Pythonie. Moduły są obiektami. Można nawet przekazać je w zmiennych i wstrzyknąć je w innych klasach.

 0
Author: Boriel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-07 14:49:32

Nie pamiętam, gdzie znalazłem to rozwiązanie, ale uważam, że jest najbardziej "eleganckie"z mojego Nie-Pythonowego punktu widzenia:

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
Dlaczego mi się to podoba? Żadnych dekoratorów, meta klas, wielokrotnego dziedziczenia...a jeśli zdecydujesz, że nie chcesz już być Singletonem, po prostu usuń metodę __new__. Ponieważ jestem nowy w Pythonie (i OOP w ogóle) oczekuję, że ktoś wyjaśni mi, dlaczego jest to straszne podejście?
 -1
Author: 2cynykyl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-11 05:38:12

Ta odpowiedź prawdopodobnie nie jest tym, czego szukasz. Chciałem Singletona w tym sensie, że tylko ten przedmiot ma swoją tożsamość, dla porównania. W moim przypadku była używana jako wartość Sentinel . Na co odpowiedź jest bardzo prosta, stwórz dowolny obiekt mything = object() i ze względu na naturę Pythona tylko ta rzecz będzie miała swoją tożsamość.

#!python
MyNone = object()  # The singleton

for item in my_list:
    if item is MyNone:  # An Example identity comparison
        raise StopIteration
 -1
Author: ThorSummoner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-29 18:40:06

To jest mój preferowany sposób implementacji singletonów:

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
 -2
Author: fab,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-30 21:04:44

To rozwiązanie powoduje pewne zanieczyszczenie przestrzeni nazw na poziomie modułu( trzy definicje, a nie tylko jedna), ale uważam, że jest to łatwe do naśladowania.

Chciałbym móc napisać coś takiego (leniwa inicjalizacja), ale niestety klasy nie są dostępne w ciele własnych definicji.

# 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

Ponieważ nie jest to możliwe, możemy wyodrębnić inicjalizację i statyczną instancję w

Inicjalizacja:

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

Leniwy inicjalizacja:

Inicjalizacja:

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
 -3
Author: Gregory Nisbet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-31 22:07:59