Erstellen eines Singletons in Python

Lesezeit: 11 Minuten

Benutzer-Avatar
der Kopf des Besens

Diese Frage dient nicht der Diskussion darüber, ob die Singleton-Entwurfsmuster ist wünschenswert, ist ein Anti-Muster oder für irgendwelche Religionskriege, aber zu diskutieren, wie dieses Muster am besten in Python so implementiert wird, dass es am pythonischsten ist. In diesem Fall definiere ich „am pythonischsten“ so, dass es dem „Prinzip des geringsten Erstaunens“ folgt..

Ich habe mehrere Klassen, die zu Singletons werden würden (mein Anwendungsfall ist für einen Logger, aber das ist nicht wichtig). Ich möchte nicht mehrere Klassen mit zusätzlichem Kauderwelsch überladen, wenn ich einfach erben oder dekorieren kann.

Beste Methoden:


Methode 1: Ein Dekorateur

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

Vorteile

  • Dekorateure sind auf eine Weise additiv, die oft intuitiver ist als Mehrfachvererbung.

Nachteile

  • Während Objekte erstellt mit MyClass() wären echte Singleton-Objekte, MyClass selbst ist eine Funktion, keine Klasse, daher können Sie keine Klassenmethoden von ihr aufrufen. Auch für

    x = MyClass();
    y = MyClass();
    t = type(n)();
    

dann x == y aber x != t && y != t


Methode 2: Eine Basisklasse

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

Vorteile

  • Es ist eine wahre Klasse

Nachteile

  • Mehrfachvererbung – pfui! __new__ könnte bei der Vererbung von einer zweiten Basisklasse überschrieben werden? Man muss mehr denken als nötig.

Methode 3: Eine Metaklasse

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

Vorteile

  • Es ist eine wahre Klasse
  • Deckt Vererbung automatisch magisch ab
  • Verwendet __metaclass__ für den eigentlichen Zweck (und hat mich darauf aufmerksam gemacht)

Nachteile

  • Sind da welche?

Methode 4: Decorator, der eine Klasse mit demselben Namen zurückgibt

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

Vorteile

  • Es ist eine wahre Klasse
  • Deckt Vererbung automatisch magisch ab

Nachteile

  • Gibt es keinen Overhead für die Erstellung jeder neuen Klasse? Hier erstellen wir zwei Klassen für jede Klasse, die wir zu einem Singleton machen möchten. Während dies in meinem Fall in Ordnung ist, mache ich mir Sorgen, dass dies möglicherweise nicht skaliert. Natürlich lässt sich darüber streiten, ob es nicht zu einfach sein soll, dieses Muster zu skalieren …
  • Was ist der Sinn der _sealed Attribut
  • Es können keine Methoden mit demselben Namen für Basisklassen mit aufgerufen werden super() denn sie werden wiederkehren. Dies bedeutet, dass Sie nicht anpassen können __new__ und kann keine Klasse unterteilen, die Sie aufrufen müssen __init__.

Methode 5: ein Modul

eine Moduldatei singleton.py

Vorteile

  • Einfach ist besser als komplex

Nachteile

  • Nicht faul instanziiert

  • Drei weitere Techniken: Verwenden Sie stattdessen ein Modul (häufig – im Allgemeinen denke ich – ist dies ein geeigneteres Muster für Python, aber es hängt ein wenig davon ab, was Sie damit machen); Erstellen Sie eine einzelne Instanz und behandeln Sie sie stattdessen (foo.x oder wenn Sie darauf bestehen Foo.x Anstatt von Foo().x); Verwenden Sie Klassenattribute und statische/Klassenmethoden (Foo.x).

    – Chris Morgan

    20. Juli 2011 um 10:55 Uhr

  • @ChrisMorgan: Wenn Sie nur Klassen-/statische Methoden verwenden, machen Sie sich nicht wirklich die Mühe, eine Klasse zu erstellen.

    – Katze Plus Plus

    20. Juli 2011 um 10:57 Uhr

  • @Cat: Der Effekt ist ähnlich, aber die Gründe für das Erstellen einer globalen Variablen können so ziemlich alles sein, einschließlich Nichtwissen. Warum erstellt man ein Singleton? Wenn Sie fragen müssen, sollten Sie nicht hier sein. Diese Explizitheit ist nicht nur pythonischer, sondern macht die Wartung viel einfacher. Ja, Singletons sind syntaktischer Zucker für Globals, aber Klassen sind syntaktischer Zucker für eine ganze Reihe unansehnlicher Dinge, und ich glaube nicht, dass Ihnen irgendjemand sagen wird, dass Sie ohne sie immer besser dran sind.

    – der Kopf des Besens

    20. Juli 2011 um 14:22 Uhr

  • Die Anti-Signletons-Stimmung ist Cargo-Cult-Programmierung in ihrer schlimmsten Form. Das Gleiche gilt für Leute, die hören (wenige haben sich die Mühe gemacht, es tatsächlich zu lesen) “Goto-Anweisung als schädlich angesehen” und denken, dass Gotos unabhängig vom Kontext ein Zeichen für schlechten Code sind.

    – Hejazzman

    30. November 2015 um 13:31 Uhr

  • Hallo, danke für deinen ausführlichen Beitrag. Ich bin ziemlich neu in der Musterprogrammierung und in Python, und ich bin überrascht, dass, obwohl Methode 2 den meisten bekannt scheint (sie ist überall), kaum jemand erwähnt, dass, obwohl nur ein Objekt erstellt wird, init__() wird jedes Mal aufgerufen, wenn Singleton() oder MyClass() irgendwo verwendet werden. Ich habe es nicht versucht, aber AFAIK gilt dies auch für alle anderen Methoden. Dies scheint bei der Implementierung eines Singletons kaum wünschenswert zu sein, oder übersehe ich etwas? Die Lösung besteht natürlich darin, ein Attribut zu setzen, um die Ausführung von __init zu vermeiden zweimal. Nur neugierig

    – chrisvp

    4. September 2016 um 11:52 Uhr

class Foo(object):
     pass

some_global_variable = Foo()

Module werden nur einmal importiert, alles andere ist Überdenken. Verwenden Sie keine Singletons und versuchen Sie, keine Globals zu verwenden.

  • Warum hast du gesagt “keine Singletons verwenden”? Irgendein Grund?

    – Alcott

    20. September 2011 um 1:30 Uhr

  • Dies funktioniert nicht, wenn der Singleton gebeizt werden muss. Anhand des von Ihnen gegebenen Beispiels: s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False

    – geteilt durch Null

    20. Januar 2012 um 11:47 Uhr


  • @dividebyzero: die is Operator testet auf Zeigergleichheit. Ich wäre ziemlich überrascht – bis zu dem Punkt, an dem ich es einen Fehler nennen würde – wenn pickle.loads einen Verweis auf ein bereits vorhandenes Objekt zurückgegeben, anstatt einen Verweis auf ein neu erstelltes. Testen Sie also, ob s is s1 sagt Ihnen nichts über die Eignung der Verwendung von Modulen als Singletons.

    – Jonas Kölker

    17. Februar 2014 um 10:52 Uhr


  • @leo-the-manic: fairer Punkt; Dies ist jedoch nur ein Nebeneffekt von Python, der die Objekte interniert True, False und Noneund hat nichts mit dem Code dahinter zu tun pickle.loads. Außerdem ist es sicher, dies nur für schreibgeschützte Objekte zu tun. Wenn pickle.loads sollten einen Verweis auf einen bereits vorhandenen zurückgeben modifizierbar Objekt – wie etwa ein Modul – wäre ein Fehler. (Und deshalb bleibe ich bei meiner Implikation, dass das Codebeispiel von dividebyzero nichts beweist.)

    – Jonas Kölker

    3. Mai 2014 um 0:18 Uhr


  • Ich importierte das Modul in mehrere verschiedene Skriptdateien (die alle das “Singleton” verwenden würden), mit import ABC as Xund es wurde tatsächlich einmal für jedes Skript importiert, das die hatte import Anweisung für das “Singleton”-Modul. Der von mir definierten Variablen wurde also jedes Mal, wenn das Modul importiert wurde, ein neuer Wert zugewiesen. Ich habe auch versucht, den Alias ​​zu entfernen und ihn sogar in jede Datei des Projekts zu importieren, die gleichen Ergebnisse. Ich bin auf Python 3.9.6. Singleton mit Metaklasse (aus der Antwort von agf) hat den Trick für meinen Anwendungsfall fantastisch gemacht (Protokollierung, mit einer Variablen, die zur Kompilierzeit einmal initialisiert werden muss)

    – Vinicius Queiroz

    29. Oktober 2021 um 20:05 Uhr

  • Was Sie also am Ende haben, ist… Keine Klasse. Sie können es nicht als Klasse verwenden, Sie können andere Klassen nicht darauf aufbauen, Sie verwenden die Importsyntax, und plötzlich verlieren Sie alle Vorteile von OOP …

    – der Kopf des Besens

    25. Juli 2011 um 13:04 Uhr

  • Wenn Sie andere Klassen darauf aufbauen können, ist es möglicherweise kein Singleton. Sie könnten eine der abgeleiteten Klasse erstellen, aber auch eine der Basisklasse, aber die abgeleitete Klasse ist auch ein Mitglied der Basis, und Sie haben zwei der Basis, welche sollen Sie verwenden?

    – SingleNegationElimination

    25. Juli 2011 um 19:32 Uhr

  • @PaulKenjora Sie müssen einen Fehler in Ihrem Code haben. Wenn Sie eine globale Variable in einem Modul definieren, sollte sie den Wert haben, wenn Sie von einem anderen Modul aus darauf zugreifen.

    – warvariuc

    30. März 2017 um 19:40 Uhr

  • @theheadofabroom du könntest import * from base_module… überdenke OOP mein Freund! hahaha

    – polvoazul

    7. März 2018 um 4:46 Uhr

  • Wie könnten Sie ein Singleton-Objekt mit Argumenten in einem Modul initialisieren?

    – nn0p

    10. Juni 2019 um 4:51 Uhr

Benutzer-Avatar
Alan Dyke

In Python brauchen Sie wahrscheinlich nie einen Singleton. Definieren Sie einfach alle Ihre Daten und Funktionen in einem Modul und Sie haben de facto ein 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)

Benutzen:

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

Wenn Sie wirklich unbedingt eine Singleton-Klasse haben müssen, dann würde ich gehen mit:

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

my_singleton = MySingleton()

Benutzen:

from mysingleton import my_singleton
my_singleton.foo()

wo mysingleton.py ist Ihr Dateiname that MySingleton ist in definiert. Dies funktioniert, weil Python den Code nach dem ersten Import einer Datei nicht erneut ausführt.

  • Meistens wahr, aber manchmal ist das nicht genug. ZB habe ich ein Projekt mit der Notwendigkeit, Instanziierungen vieler Klassen auf DEBUG-Ebene zu protokollieren. Ich benötige Befehlszeilenoptionen, die beim Start analysiert werden, um die benutzerdefinierte Protokollierungsebene festzulegen Vor diese Klassen werden instanziiert. Instanziierungen auf Modulebene machen dies problematisch. Es ist möglich, dass ich die App sorgfältig so strukturiere, dass all diese Klassen nicht importiert werden, bis die CLI-Verarbeitung abgeschlossen ist, aber die natürliche Struktur meiner App ist wichtiger als die dogmatische Einhaltung von „Singletons are bad“, da sie es sein können ganz sauber gemacht.

    – Mikeneron

    27. August 2016 um 21:29 Uhr

  • Wenn Sie Ihren Code beim Patchen von my_singleton testen würden, wäre das möglich? da dieses my_singleton in einem anderen Modul instanziiert werden könnte.

    – Naveen

    24. April 2018 um 3:04 Uhr

  • @Naveen – my_singleton ist ein einzelnes Objekt. Wenn Sie es “patchen”, wirkt sich diese Änderung auf alle zukünftigen Referenzen aus, sogar in anderen Modulen.

    – Alan Dyke

    24. April 2018 um 19:30 Uhr

  • Dies mag in einigen Fällen funktionieren, aber manchmal ist eine verzögerte Initialisierung wichtig. In meinem Anwendungsfall kostet die Initialisierung 400 ms, also möchte ich das nicht verursachen, nur weil ich das Modul importiert habe. Es muss nur dann anfallen, wenn der Singleton wirklich benötigt wird.

    – Johannis

    7. April 2021 um 21:48 Uhr


  • @joanis. Einverstanden, dass es keine perfekte Lösung für jeden möglichen Anwendungsfall gibt. Vielleicht können Sie für den zeitaufwändigen Teil Ihres Codes immer noch eine verzögerte Initialisierung verwenden, indem Sie sie nicht in Ihren Konstruktor einfügen. Oder vielleicht brauchen Sie einen der anderen komplizierteren Vorschläge auf dieser Seite.

    – Alan Dyke

    8. April 2021 um 16:00 Uhr

Benutzer-Avatar
Jonas Kölker

Hier ist ein Einzeiler für Sie:

singleton = lambda c: c()

So verwenden Sie es:

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

assert wat.get_x() == 1

Ihr Objekt wird eifrig instanziiert. Dies kann oder kann nicht das sein, was Sie wollen.

  • Meistens wahr, aber manchmal ist das nicht genug. ZB habe ich ein Projekt mit der Notwendigkeit, Instanziierungen vieler Klassen auf DEBUG-Ebene zu protokollieren. Ich benötige Befehlszeilenoptionen, die beim Start analysiert werden, um die benutzerdefinierte Protokollierungsebene festzulegen Vor diese Klassen werden instanziiert. Instanziierungen auf Modulebene machen dies problematisch. Es ist möglich, dass ich die App sorgfältig so strukturiere, dass all diese Klassen nicht importiert werden, bis die CLI-Verarbeitung abgeschlossen ist, aber die natürliche Struktur meiner App ist wichtiger als die dogmatische Einhaltung von „Singletons are bad“, da sie es sein können ganz sauber gemacht.

    – Mikeneron

    27. August 2016 um 21:29 Uhr

  • Wenn Sie Ihren Code beim Patchen von my_singleton testen würden, wäre das möglich? da dieses my_singleton in einem anderen Modul instanziiert werden könnte.

    – Naveen

    24. April 2018 um 3:04 Uhr

  • @Naveen – my_singleton ist ein einzelnes Objekt. Wenn Sie es “patchen”, wirkt sich diese Änderung auf alle zukünftigen Referenzen aus, sogar in anderen Modulen.

    – Alan Dyke

    24. April 2018 um 19:30 Uhr

  • Dies mag in einigen Fällen funktionieren, aber manchmal ist eine verzögerte Initialisierung wichtig. In meinem Anwendungsfall kostet die Initialisierung 400 ms, also möchte ich das nicht verursachen, nur weil ich das Modul importiert habe. Es muss nur dann anfallen, wenn der Singleton wirklich benötigt wird.

    – Johannis

    7. April 2021 um 21:48 Uhr


  • @joanis. Einverstanden, dass es keine perfekte Lösung für jeden möglichen Anwendungsfall gibt. Vielleicht können Sie für den zeitaufwändigen Teil Ihres Codes immer noch eine verzögerte Initialisierung verwenden, indem Sie sie nicht in Ihren Konstruktor einfügen. Oder vielleicht brauchen Sie einen der anderen komplizierteren Vorschläge auf dieser Seite.

    – Alan Dyke

    8. April 2021 um 16:00 Uhr

Benutzer-Avatar
Gemeinschaft

Sehen Sie sich die Frage zum Stapelüberlauf an Gibt es eine einfache, elegante Möglichkeit, Singletons in Python zu definieren? mit mehreren Lösungen.

Ich würde dringend empfehlen, sich Alex Martellis Vorträge über Designmuster in Python anzusehen: Teil 1 und Teil 2. Insbesondere in Teil 1 spricht er über Singletons/Shared State Objects.

  • Dies ist zwar keine wirkliche Antwort auf meine Frage, aber die Ressourcen, auf die Sie verweisen, sind sehr nützlich. Ich gebe Ihnen widerwillig +1

    – der Kopf des Besens

    25. Juli 2011 um 13:08 Uhr

1156090cookie-checkErstellen eines Singletons in Python

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy