Création d'un singleton en Python

946

Cette question n'est pas pour la discussion de savoir si le modèle de conception singleton est souhaitable ou non , ou pour toute guerre religieuse, mais pour discuter de la meilleure façon de mettre en œuvre ce modèle en Python de la manière la plus pythonique. Dans ce cas, je définis «le plus pythonique» pour signifier qu'il suit le «principe du moindre étonnement» .

J'ai plusieurs classes qui deviendraient singletons (mon cas d'utilisation est pour un enregistreur, mais ce n'est pas important). Je ne souhaite pas encombrer plusieurs classes avec une gomme ajoutée quand je peux simplement hériter ou décorer.

Meilleures méthodes:


Méthode 1: Un décorateur

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

Avantages

  • Les décorateurs sont additifs d'une manière qui est souvent plus intuitive que l'héritage multiple.

Les inconvénients

  • Alors que les objets créés à l'aide de MyClass () seraient de vrais objets singleton, MyClass lui-même est une fonction, pas une classe, vous ne pouvez donc pas appeler de méthodes de classe à partir de celle-ci. Aussi pour m = MyClass(); n = MyClass(); o = type(n)();alorsm == n && m != o && n != o

Méthode 2: une classe de base

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

Avantages

  • C'est une vraie classe

Les inconvénients

  • Héritage multiple - eugh! __new__pourrait être écrasé lors de l'héritage d'une deuxième classe de base? Il faut penser plus que nécessaire.

Méthode 3: une métaclasse

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

Avantages

  • C'est une vraie classe
  • Couvre automatiquement l'héritage
  • Utilise __metaclass__pour son bon usage (et m'a fait prendre conscience de cela)

Les inconvénients

  • Y a-t-il?

Méthode 4: le décorateur renvoie une classe du même nom

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

Avantages

  • C'est une vraie classe
  • Couvre automatiquement l'héritage

Les inconvénients

  • N'y a-t-il pas une surcharge pour créer chaque nouvelle classe? Ici, nous créons deux classes pour chaque classe que nous souhaitons faire un singleton. Bien que cela soit bien dans mon cas, je crains que cela ne soit pas à l'échelle. Bien sûr, il y a un débat pour savoir s'il serait trop facile de mettre à l'échelle ce modèle ...
  • Quel est l'intérêt de l' _sealedattribut
  • Impossible d'appeler des méthodes du même nom sur les classes de base à l'aide super()car elles vont récurrentes. Cela signifie que vous ne pouvez pas personnaliser __new__et ne pouvez pas sous-classer une classe qui a besoin de vous pour appeler __init__.

Méthode 5: un module

un fichier module singleton.py

Avantages

  • Simple, c'est mieux que complexe

Les inconvénients

theheadofabroom
la source
12
Trois autres techniques: utilisez un module à la place (souvent - généralement, je pense - c'est un modèle plus approprié pour Python mais cela dépend un peu de ce que vous en faites); créer une seule instance et la traiter à la place ( foo.xou si vous insistez à la Foo.xplace de Foo().x); utilisez les attributs de classe et les méthodes statiques / de classe ( Foo.x).
Chris Morgan
10
@ChrisMorgan: Si vous n'utilisez que des méthodes de classe / statique, alors ne vous embêtez pas vraiment à créer une classe.
Cat Plus Plus
2
@Cat: L'effet est similaire, mais les raisons derrière la création d'une variable globale peuvent être à peu près n'importe quoi, y compris ne pas savoir mieux. Pourquoi crée-t-on un singleton? Si vous devez demander, vous ne devriez pas être ici. Cette explication est non seulement plus pythonique, mais rend la maintenance beaucoup plus simple. Oui, les singletons sont du sucre syntaxique pour les globaux, mais les classes sont du sucre syntaxique pour tout un tas de choses disgracieuses et je ne pense pas que quiconque vous dira que vous êtes toujours mieux sans eux.
theheadofabroom
14
@BiggAl: Les singletons ne sont pas Pythonic, peu importe comment vous les implémentez. Ils sont au mieux le signe d'un design défectueux.
Cat Plus Plus
8
Le sentiment anti-signletons est la programmation culte du fret à son pire. Même chose avec les gens qui entendent (peu se donnent la peine de lire) "La déclaration de Goto est considérée comme nuisible" et pense que les gotos sont un signe de mauvais code quel que soit le contexte.
Hejazzman

Réponses:

658

Utiliser une métaclasse

Je recommanderais la méthode n ° 2 , mais il vaut mieux utiliser une métaclasse qu'une classe de base. Voici un exemple d'implémentation:

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

Ou en Python3

class Logger(metaclass=Singleton):
    pass

Si vous souhaitez exécuter __init__chaque fois que la classe est appelée, ajoutez

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

à la ifdéclaration en Singleton.__call__.

Quelques mots sur les métaclasses. Une métaclasse est la classe d'une classe ; c'est-à-dire qu'une classe est une instance de sa métaclasse . Vous trouvez la métaclasse d'un objet en Python avec type(obj). Les classes normales de nouveau style sont de type type. Loggerdans le code ci-dessus sera de type class 'your_module.Singleton', tout comme la (seule) instance de Loggersera de type class 'your_module.Logger'. Lorsque vous appelez avec enregistreur Logger(), Python demande d' abord la métaclasse Logger, Singleton, ce qu'il faut faire, ce qui permet la création d'instance à préempté. Ce processus est le même que Python demandant à une classe quoi faire en appelant __getattr__lorsque vous faites référence à l'un de ses attributs en faisant myclass.attribute.

Une métaclasse décide essentiellement ce que signifie la définition d'une classe et comment mettre en œuvre cette définition. Voir par exemple http://code.activestate.com/recipes/498149/ , qui recrée essentiellement des styles C structen Python à l'aide de métaclasses. Le fil Quels sont les cas d'utilisation (concrets) des métaclasses? fournit également quelques exemples, ils semblent généralement liés à la programmation déclarative, en particulier telle qu'elle est utilisée dans les ORM.

Dans cette situation, si vous utilisez votre méthode n ° 2 et qu'une sous-classe définit une __new__méthode, elle sera exécutée à chaque appel SubClassOfSingleton(), car elle est responsable de l'appel de la méthode qui renvoie l'instance stockée. Avec une métaclasse, elle ne sera appelée qu'une seule fois , lorsque la seule instance sera créée. Vous voulez personnaliser ce que cela signifie d'appeler la classe , qui est déterminée par son type.

En général, il est logique d'utiliser une métaclasse pour implémenter un singleton. Un singleton est spécial car il n'est créé qu'une seule fois , et une métaclasse est la façon dont vous personnalisez la création d'une classe . L'utilisation d'une métaclasse vous donne plus de contrôle au cas où vous auriez besoin de personnaliser les définitions de classe singleton d'une autre manière.

Vos singletons n'auront pas besoin d'héritage multiple (car la métaclasse n'est pas une classe de base), mais pour les sous-classes de la classe créée qui utilisent l'héritage multiple, vous devez vous assurer que la classe singleton est la première / la plus à gauche avec une métaclasse qui redéfinit __call__Il est très peu probable que ce soit un problème. Le dict d'instance n'est pas dans l'espace de noms de l'instance, il ne le remplacera donc pas accidentellement.

Vous entendrez également que le modèle singleton viole le «principe de responsabilité unique» - chaque classe ne doit faire qu'une seule chose . De cette façon, vous n'avez pas à vous soucier de gâcher une chose que le code fait si vous devez en changer une autre, car ils sont séparés et encapsulés. L'implémentation de la métaclasse réussit ce test . La métaclasse est responsable de l' application du modèle et la classe et les sous-classes créées n'ont pas besoin de savoir qu'elles sont des singletons . La méthode n ° 1 échoue à ce test, comme vous l'avez noté avec "MyClass lui-même est une fonction, pas une classe, vous ne pouvez donc pas appeler de méthodes de classe à partir de celle-ci."

Version compatible Python 2 et 3

Écrire quelque chose qui fonctionne à la fois en Python2 et 3 nécessite l'utilisation d'un schéma légèrement plus compliqué. Étant donné que les métaclasses sont généralement sous - classes de type type, il est possible d'utiliser un pour créer dynamiquement une classe de base intermédiaire au moment de l' exécution avec elle comme métaclasse et utiliser alors que les baseclass du public Singletonclasse de base. Il est plus difficile à expliquer qu'à faire, comme illustré ci-dessous:

# 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

Un aspect ironique de cette approche est qu'elle utilise le sous-classement pour implémenter une métaclasse. Un avantage possible est que, contrairement à une métaclasse pure, isinstance(inst, Singleton)il reviendra True.

Corrections

Sur un autre sujet, vous l'avez probablement déjà remarqué, mais l'implémentation de la classe de base dans votre message d'origine est incorrecte. _instancesdoit être référencé sur la classe , vous devez utiliser super()ou vous êtes récursif , et __new__est en fait une méthode statique à laquelle vous devez passer la classe , pas une méthode de classe, car la classe réelle n'a pas encore été créée quand elle est appelé. Toutes ces choses seront également vraies pour une implémentation de métaclasse.

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

Décorateur retournant une classe

Au départ, j'écrivais un commentaire mais c'était trop long, je vais donc l'ajouter ici. La méthode # 4 est meilleure que l'autre version de décorateur, mais c'est plus de code que nécessaire pour un singleton, et ce n'est pas aussi clair.

Les principaux problèmes proviennent de la classe étant sa propre classe de base. Tout d'abord, n'est-il pas étrange qu'une classe soit une sous-classe d'une classe presque identique avec le même nom qui n'existe que dans son __class__attribut? Cela signifie également que vous ne pouvez pas définir de méthodes qui appellent la méthode du même nom sur leur classe de base avec, super()car elles recurseront. Cela signifie que votre classe ne peut pas être personnalisée __new__et ne peut dériver d'aucune classe qui doit y faire __init__appel.

Quand utiliser le motif singleton

Votre cas d'utilisation est l' un des meilleurs exemples de vouloir utiliser un singleton. Vous dites dans l'un des commentaires "Pour moi, l'exploitation forestière a toujours semblé un candidat naturel pour les Singletons." Tu as absolument raison .

Lorsque les gens disent que les singletons sont mauvais, la raison la plus courante est qu'ils sont un état partagé implicite . Alors qu'avec les variables globales et les importations de module de niveau supérieur , l'état partagé est explicite , les autres objets qui sont transmis sont généralement instanciés. C'est un bon point, à deux exceptions près .

Le premier, et celui qui est mentionné à divers endroits, est lorsque les singletons sont constants . L'utilisation de constantes globales, en particulier les énumérations, est largement acceptée et considérée comme raisonnable car, quoi qu'il arrive , aucun des utilisateurs ne peut les gâcher pour un autre utilisateur . Cela est également vrai pour un singleton constant.

La deuxième exception, qui est moins mentionnée, est l'inverse - lorsque le singleton n'est qu'un récepteur de données , pas une source de données (directement ou indirectement). C'est pourquoi les bûcherons se sentent comme une utilisation "naturelle" des singletons. Comme les différents utilisateurs ne modifient pas les enregistreurs de la manière dont les autres utilisateurs se soucieront, il n'y a pas vraiment d'état partagé . Cela annule l'argument principal contre le modèle singleton et en fait un choix raisonnable en raison de leur facilité d'utilisation pour la tâche.

Voici une citation de http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :

Maintenant, il y a un type de Singleton qui est OK. C'est un singleton où tous les objets accessibles sont immuables. Si tous les objets sont immuables, Singleton n'a pas d'état global, car tout est constant. Mais il est si facile de transformer ce type de singleton en mutable, c'est une pente très glissante. Par conséquent, je suis contre ces Singletons aussi, non pas parce qu'ils sont mauvais, mais parce qu'il est très facile pour eux de se dégrader. (En guise de remarque, l'énumération Java n'est que ce genre de singletons. Tant que vous ne mettez pas d'état dans votre énumération, vous êtes OK, alors ne le faites pas.)

Les autres types de singletons, qui sont semi-acceptables sont ceux qui n'affectent pas l'exécution de votre code, ils n'ont aucun "effet secondaire". La journalisation en est un parfait exemple. Il est chargé de singletons et d'état global. C'est acceptable (car cela ne vous fera pas de mal) car votre application ne se comporte pas différemment, qu'un enregistreur donné soit activé ou non. Les informations ici circulent dans un sens: de votre application vers l'enregistreur. Même si les enregistreurs sont un état global car aucune information ne circule des enregistreurs vers votre application, les enregistreurs sont acceptables. Vous devez toujours injecter votre enregistreur si vous voulez que votre test confirme que quelque chose est en cours d'enregistrement, mais en général, les enregistreurs ne sont pas nocifs malgré leur état.

agf
la source
5
Non, les singletons ne sont jamais bons. La journalisation peut être un bon candidat pour être un mondial (aussi terrible soit-elle), mais certainement pas un singleton.
Cat Plus Plus
11
Regardez googletesting.blogspot.com/2008/08/… . Il est généralement anti-singleton (pour une bonne raison) mais il explique bien pourquoi les singletons immuables et les singletons sans effets secondaires n'ont pas les mêmes problèmes, si vous faites attention. Je vais le citer un peu à la fin de mon post.
agf
4
Mon problème avec les singletons est la prémisse stupide de "une seule instance". Cela et des tonnes de problèmes de sécurité des fils. Et la dépendance se cache. Les globaux sont mauvais et les singletons ne sont que des globaux avec plus de problèmes.
Cat Plus Plus
4
@Cat Il existe de très bonnes utilisations pour les singletons. L'instanciation paresseuse des modules matériels (en particulier dans les applications à thread unique) en fait partie (mais des singletons thread-safe existent également).
Paul Manta
3
@Alcott __new__dans une métaclasse, c'est quand la classe est nouvelle - quand elle est définie, pas quand l' instance serait nouvelle. L'appel de la classe ( MyClass()) est l'opération que vous souhaitez remplacer, pas la définition de la classe. Si vous voulez vraiment comprendre comment Python fonctionne, la meilleure chose que vous puissiez faire (à part continuer à l'utiliser) est de lire docs.python.org/reference/datamodel.html . Une bonne référence sur les métaclasses est eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example . Un bon article sur les singletons est la série du blog Google que j'ai liée dans cette réponse.
agf
91
class Foo(object):
     pass

some_global_variable = Foo()

Les modules ne sont importés qu'une seule fois, tout le reste est sur-pensé. N'utilisez pas de singletons et essayez de ne pas utiliser de globaux.

Cat Plus Plus
la source
14
pourquoi avez-vous dit "N'utilisez pas de singletons"? N'importe quelle raison?
Alcott
3
Cela ne fonctionnera pas si le singleton doit être décapé. En utilisant l'exemple que vous avez donné:s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False
dividebyzero
4
@dividebyzero: l' isopérateur teste l'égalité du pointeur. Je serais plutôt surpris --- au point de l'appeler un bug --- si pickle.loadsrenvoyé une référence à un objet préexistant plutôt qu'une référence à un objet nouvellement créé. Ainsi, tester si s is s1ne vous dit rien sur la pertinence d'utiliser des modules comme singletons.
Jonas Kölker
1
@ JonasKölker le pickle.loads()fait déjà, par exemple pour les instances de boolet NoneType. pickle.loads(pickle.dumps(False)) is FalserendementsTrue
Dan Passaro
2
@ leo-the-manic: juste point; cependant, ce n'est qu'un effet secondaire de Python internant les objets True, Falseet None, et n'a rien à voir avec le code derrière pickle.loads. En outre, il est sûr de le faire uniquement pour les objets en lecture seule. Si je devais pickle.loadsrenvoyer une référence à un objet modifiable déjà existant, tel qu'un module, ce serait un bogue. (Et donc je maintiens mon implication que l'exemple de code de dividebyzero ne prouve rien.)
Jonas Kölker
69

Utilisez un module. Il n'est importé qu'une seule fois. Définissez-y des variables globales - ce seront les «attributs» de singleton. Ajoutez quelques fonctions - les «méthodes» du singleton.

warvariuc
la source
11
Donc vous vous retrouvez avec ... Pas une classe. Vous ne pouvez pas l'utiliser en tant que classe, vous ne pouvez pas baser d'autres classes dessus, vous utilisez la syntaxe d'importation, et tout à coup, vous perdez tous les avantages de la POO ...
theheadofabroom
16
si vous pouvez baser d'autres classes dessus, ce n'est peut-être pas un singleton. vous pouvez créer une de la classe dérivée, mais aussi une de la classe de base, mais la classe dérivée est également membre de la base, et vous en avez deux de la base, laquelle êtes-vous censé utiliser?
SingleNegationElimination
Cela ne fonctionne pas entre les modules. Dans mon module "principal", j'ai défini une valeur. Je le référence ensuite dans un autre module et son null. Soupir.
Paul Kenjora
1
@PaulKenjora Vous devez avoir une erreur dans votre code. Si vous définissez une variable globale dans un module, lorsque vous y accédez à partir d'un autre module, elle doit avoir la valeur.
warvariuc
Il a de la valeur mais quand je le change il n'est pas conservé. J'ai fini par utiliser une classe avec des propriétés dans un module qui fonctionne. Les types simples comme les globaux ne fonctionnaient pas pour moi (ils ont perdu des valeurs dès que la portée a changé).
Paul Kenjora
29

Vous n'avez probablement jamais besoin d'un singleton en Python. Définissez simplement toutes vos données et fonctions dans un module et vous avez un singleton de facto.

Si vous devez vraiment absolument avoir une classe singleton, alors j'irais avec:

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

my_singleton = My_Singleton()

Utiliser:

from mysingleton import my_singleton
my_singleton.foo()

où mysingleton.py est votre nom de fichier dans lequel My_Singleton est défini. Cela fonctionne car après la première importation d'un fichier, Python ne réexécute pas le code.

Alan Dyke
la source
4
Généralement vrai, mais parfois ce n'est pas suffisant. Par exemple, j'ai un projet avec un besoin de consigner les instanciations de nombreuses classes au niveau DEBUG. J'ai besoin d'analyser les options de ligne de commande au démarrage afin de définir le niveau de journalisation spécifié par l'utilisateur avant que ces classes ne soient instanciées. Les instanciations au niveau du module rendent cela problématique. Il est possible que je puisse soigneusement structurer l'application afin que toutes ces classes ne soient pas importées tant que le traitement CLI n'est pas terminé, mais la structure naturelle de mon application est plus importante que l'adhésion dogmatique aux «singletons sont mauvais», car ils peuvent être fait assez proprement.
CryingCyclops
si vous deviez tester votre code tout en corrigeant my_singleton, cela serait-il possible? puisque ce my_singleton pourrait être instancié dans un autre module.
Naveen
@Naveen - my_singleton est un objet unique. Si vous le "corrigez", ce changement affectera toutes les références futures, même dans d'autres modules.
Alan Dyke du
16

Voici une doublure pour vous:

singleton = lambda c: c()

Voici comment vous l'utilisez:

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

assert wat.get_x() == 1

Votre objet est instancié avec impatience. Cela peut ou non être ce que vous voulez.

Jonas Kölker
la source
5
-1. C'est très mauvais et idiot. Vous ne définissez pas une classe à utiliser comme objet. Vous ne pouvez plus accéder à la classe sans être très laid comme type(wat)ou wat.__class__. Si vous voulez vraiment y parvenir, mieux définir la classe et l'instancier tout de suite, pas besoin de gâcher le décorateur.
0xc0de
2
Pourquoi avez-vous besoin de savoir utiliser la classe d'un singleton? Il suffit d'utiliser l'objet singleton ..
Tolli
1
Ce n'est pas un motif singleton , donc la fonction IMO doit être nommée différemment.
GingerPlusPlus
8
Wikipedia: "le motif singleton est un motif de conception qui limite l'instanciation d'une classe à un seul objet". Je dirais que ma solution fait exactement cela. D'accord, je suppose qu'on pourrait le faire wat2 = type(wat)(), mais c'est du python, nous sommes tous des adultes consentants et tout ça. Vous ne pouvez pas garantir qu'il n'y aura qu'une seule instance, mais vous pouvez garantir que si les gens en font une deuxième, cela aura l'air laid et - s'ils sont honnêtes et honnêtes - comme un signe d'avertissement pour eux. Qu'est-ce que je rate?
Jonas Kölker
7

Consultez la question Stack Overflow Existe-t-il un moyen simple et élégant de définir des singletons en Python? avec plusieurs solutions.

Je recommande fortement de regarder les exposés d'Alex Martelli sur les modèles de conception en python: partie 1 et partie 2 . En particulier, dans la partie 1, il parle de singletons / objets d'état partagés.

Anton
la source
2
Bien que ce ne soit pas vraiment une réponse à ma question, les ressources que vous pointez sont très utiles. Je vous donne à contrecœur un +1
theheadofabroom
3

Voici ma propre implémentation de singletons. Tout ce que vous avez à faire est de décorer la classe; pour obtenir le singleton, vous devez ensuite utiliser la Instanceméthode. Voici un exemple:

   @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

Et voici le code:

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)
Paul Manta
la source
2
Ce n'est pas vrai singleton. SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)())devrait imprimer Trueen vrai singleton; avec vos impressions de codeFalse
GingerPlusPlus
@GingerPlusPlus J'étais au courant de quelques limitations, mais pas de celle que vous avez signalée. Merci de l'avoir mentionné. Malheureusement, je n'ai pas le temps pour le moment de réfléchir à une solution à ce problème.
Paul Manta
2

La méthode 3 semble être très soignée, mais si vous voulez que votre programme s'exécute à la fois en Python 2 et Python 3 , cela ne fonctionne pas. Même la protection des variantes séparées avec des tests pour la version Python échoue, car la version Python 3 donne une erreur de syntaxe dans Python 2.

Merci à Mike Watkins: http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/ . Si vous voulez que le programme fonctionne à la fois en Python 2 et Python 3, vous devez faire quelque chose comme:

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

Je suppose que «objet» dans l'affectation doit être remplacé par «BaseClass», mais je n'ai pas essayé cela (j'ai essayé le code comme illustré).

Tim
la source
ce n'est sûrement pas une métaclasse - en python3 pour utiliser une métaclasse pour construire MyClass vous feriezclass MyClass(metaclass=Singleton)
theheadofabroom
Le lien mikewatkins.ca est (effectivement) rompu.
Peter Mortensen
1

Eh bien, à part être d'accord avec la suggestion générale de Pythonic sur le fait d'avoir un niveau global au niveau du module, qu'en est-il:

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)

La sortie est:

111     # the __init__ is called only on the 1st time
MyClass # the __name__ is preserved
True    # this is actually the same instance
Garde
la source
Quel est l'intérêt de l' _sealedattribut? Pour autant que je vois cela ne fait rien? Quelque chose me dérange à ce sujet qui dit que cela ne devrait pas bien fonctionner ... Je ferai des tests comparatifs plus tard cette semaine.
theheadofabroom
_sealed garantit que votre init n'est exécuté qu'une seule fois; Je ne vois pas pourquoi il devrait fonctionner moins bien que le décorateur ordinaire de type fonction - la fonction n'est exécutée qu'une seule fois par classe, et renvoie une nouvelle classe héritée
Garde
BTW, votre édition contient des onglets qui cassent les tirets Vous dites également «nous créons 2 classes» - voulez-vous dire que nous créons «1 classe supplémentaire»?
Garde
Oui, un cours supplémentaire est ce que je voulais dire. J'ai l'intention d'inclure des éléments __init__à appeler à chaque initialisation. Juste un simple 'Initialisation dans class.method'. quant à l'indentation - vous avez utilisé des tabulations et des espaces - j'en ai réparé la plupart, mais semblez en avoir manqué un si vous voulez l'obtenir (il suffit de consulter le journal d'édition)
theheadofabroom
re init : bien sûr, cela dépend de vous, j'ai juste essayé d'imiter le comportement singleton dans d'autres langages, où le code constructeur (qui n'est pas exactement init , mais très proche dans son sens) n'est appelé qu'une seule fois si vous voulez que l' init soit appelé à chaque fois, tuez toutes les références à _sealed re espaces / tabs - eh bien, mon emacs doit être corrigé. de toute façon, ci-dessus est la version corrigée
Guard
1

Que dis-tu de ça:

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

Utilisez-le comme décorateur dans une classe qui devrait être un singleton. Comme ça:

@singleton
class MySingleton:
    #....

Ceci est similaire au singleton = lambda c: c()décorateur dans une autre réponse. Comme l'autre solution, la seule instance a le nom de la classe ( MySingleton). Cependant, avec cette solution, vous pouvez toujours "créer" des instances (en fait obtenir la seule instance) à partir de la classe, en faisant MySingleton(). Il vous empêche également de créer des instances supplémentaires en faisant type(MySingleton)()(cela renvoie également la même instance).

Tolli
la source
Vous ne définissez pas une classe pour l'utiliser comme objet.
0xc0de
1
Chaque fois que vous appelez type(MySingleton)(), MySingleton.__init__()est appelé et l'objet est initialisé plusieurs fois; vous pouvez le corriger en écrivant cls.__init__ = lambda self: passdans votre singleton. En outre, le remplacement cls.__call__semble inutile et même dangereux - __call__défini dans ce contexte est utilisé lorsque vous appelez MySingleton(any, list, of, arguments), pas lorsque vous appelez type(MySingleton)(any, list, of, arguments).
GingerPlusPlus
@GingerPlusPlus, Merci d'avoir souligné que l' __init__()on appelle à nouveau en faisant type(MySingleton)(). La solution que vous avez proposée (en ajoutant cls.__init__ = lambda self: pass) donne une erreur de syntaxe, car la dernière partie de l'expression lambda doit être une expression, pas une instruction. Cependant, en ajoutant des cls.__init__ = lambda self: Noneœuvres, j'ai donc ajouté cela à ma réponse.
Tolli
1
@GingerPlusPlus, Concernant l'utilisation de __call__. mon intention était de faire les deux type(MySingleton)()et de MySingleton()renvoyer l'instance. Il fait donc ce que je voulais. Vous pouvez considérer MySingleton comme le type du singleton ou l'instance du singleton (ou les deux).
Tolli
1

Je vais jeter le mien dans le ring. C'est un simple décorateur.

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.

Avantages, je pense que cela a sur certaines des autres solutions:

  • C'est clair et concis (à mes yeux; D).
  • Son action est complètement encapsulée. Vous n'avez pas besoin de changer une seule chose au sujet de l'implémentation de YourClass. Cela inclut le fait de ne pas avoir besoin d'utiliser une métaclasse pour votre classe (notez que la métaclasse ci-dessus est en usine, pas la "vraie" classe).
  • Il ne repose sur rien de patch de singe.
  • C'est transparent pour les appelants:
    • Les appelants importent toujours simplement YourClass, cela ressemble à une classe (car c'est le cas), et ils l'utilisent normalement. Pas besoin d'adapter les appelants à une fonction d'usine.
    • Ce qui YourClass()instancie est toujours un véritable exemple de ce que YourClassvous avez implémenté, pas un proxy d'aucune sorte, donc aucune chance d'effets secondaires en résultant.
    • isinstance(instance, YourClass) et des opérations similaires fonctionnent toujours comme prévu (bien que ce bit nécessite abc, il empêche donc Python <2.6).

Un inconvénient me vient à l'esprit: les méthodes de classe et les méthodes statiques de la classe réelle ne peuvent pas être appelées de manière transparente via la classe d'usine qui la cache. Je l'ai utilisé assez rarement pour ne jamais rencontrer ce besoin, mais il serait facilement rectifié en utilisant une métaclasse personnalisée en usine qui implémente __getattr__()pour déléguer l'accès aux attributs tout-en-un à la classe réelle.

Un modèle connexe que j'ai trouvé plus utile (pas que je dis que ce genre de choses sont nécessaires très souvent) est un modèle "Unique" où l'instanciation de la classe avec les mêmes arguments aboutit à récupérer la même instance. C'est-à-dire un "singleton par arguments". Ce qui précède s'adapte bien à cela et devient encore plus concis:

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

Cela dit, je suis d'accord avec l'avis général que si vous pensez avoir besoin de l'une de ces choses, vous devriez probablement vous arrêter un instant et vous demander si vous en avez vraiment besoin. 99% du temps, YAGNI.

PleurerCyclopes
la source
1
  • Si l'on veut avoir plusieurs instances de la même classe, mais seulement si les args ou kwargs sont différents, on peut utiliser ceci
  • Ex.
    1. Si vous avez une classe gérant la serialcommunication, et pour créer une instance que vous souhaitez envoyer le port série comme argument, alors avec l'approche traditionnelle ne fonctionnera pas
    2. En utilisant les décorateurs mentionnés ci-dessus, on peut créer plusieurs instances de la classe si les arguments sont différents.
    3. Pour les mêmes arguments, le décorateur retournera la même instance qui a déjà été créée.
>>> 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
>>>
Siddhesh Suhas Sathe
la source
0

Code basé sur la réponse de 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)

Explication:

  1. Créer une nouvelle classe, héritant de given cls
    (elle ne change pas clsau cas où quelqu'un le voudrait par exemple singleton(list))

  2. Créez une instance. Avant de __new__passer outre, c'est si facile.

  3. Maintenant, lorsque nous avons facilement créé une instance, remplace la __new__méthode définie il y a un instant.
  4. La fonction instancene retourne que lorsque c'est ce que l'appelant attend, sinon il lève TypeError.
    La condition n'est pas remplie lorsque quelqu'un tente d'hériter d'une classe décorée.

  5. Si __new__()renvoie une instance de cls, alors la __init__()méthode de la nouvelle instance sera invoquée comme __init__(self[, ...]), où self est la nouvelle instance et les arguments restants sont les mêmes que ceux passés à __new__().

    instanceest déjà initialisé, donc la fonction remplace __init__par la fonction qui ne fait rien.

Voyez-le travailler en ligne

GingerPlusPlus
la source
0

Elle est légèrement similaire à la réponse de fab mais pas exactement la même.

Le contrat singleton n'exige pas que nous puissions appeler le constructeur plusieurs fois. Comme un singleton doit être créé une seule fois, ne devrait-il pas être vu comme créé une seule fois? «Usurper» le constructeur altère sans doute la lisibilité.

Donc, ma suggestion est juste ceci:

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

Cela n'exclut pas l'utilisation du constructeur ou du champ instancepar code utilisateur:

if Elvis() is King.instance:

... si vous savez avec certitude que cela Elvisn'a pas encore été créé, et c'est le cas King.

Mais il encourage les utilisateurs à utiliser la theméthode de manière universelle:

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

Pour faire cela, vous pouvez également remplacer __delattr__()pour lever une exception si une tentative de suppression est effectuée instance, et remplacer __del__()pour qu'il déclenche une exception (sauf si nous savons que le programme se termine ...)

Améliorations supplémentaires


Mes remerciements à ceux qui ont contribué aux commentaires et aux modifications, dont d'autres sont les bienvenus. Bien que j'utilise Jython, cela devrait fonctionner plus généralement et être thread-safe.

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

Points à noter:

  1. Si vous ne sous-classez pas d'objet dans python2.x, vous obtiendrez une classe à l'ancienne, qui n'utilisera pas __new__
  2. Lors de la décoration, __new__vous devez décorer avec @classmethod ou __new__sera une méthode d'instance non liée
  3. Cela pourrait éventuellement être amélioré en utilisant une métaclasse, car cela vous permettrait de créer theune propriété de niveau classe, éventuellement en la renommantinstance
Mike rongeur
la source
Bien qu'il s'agisse d'une interprétation légèrement différente du modèle singleton, je suis presque sûr qu'il est toujours valide, bien que je puisse être tenté d'utiliser __new__ plutôt que __init__, car il agit uniquement sur les attributs de classe et cela empêche qu'il n'y ait brièvement une seconde instance. La différence entre cette méthode et la méthode 2 est de savoir si le fait d'initialiser plusieurs fois renvoie l'instance unique ou déclenche une exception. Je pense que je suis heureux que l'un ou l'autre satisfasse le modèle singleton, l'un est plus facile à utiliser, tandis que l'autre est plus explicite qu'il s'agit d'un singleton.
theheadofabroom
Évidemment, l'utilisation du nom de classe dans __init__empêche le sous- classement , mais bien que cela facilite les choses, ce n'est pas obligatoire
theheadofabroom
Merci ... ah oui, une seconde instance momentanée avant que l'exception ne soit levée. J'ai modifié le __init__afin que j'espère que cela devrait être sous
classable
Cool, thepourrait probablement bénéficier d'être une méthode de classe pour des raisons similaires
theheadofabroom
Oui tu as raison. Ensuite, vous pouvez avoir un singleton de sous-classe SuperElvis et (par exemple) un singleton de sous-classe ImaginaryElvis ... et ils peuvent coexister. Voir des pensées supplémentaires. N'hésitez pas à améliorer mon code.
mike rodent
0

Un paquebot (je ne suis pas fier, mais ça fait l'affaire):

class Myclass:
  def __init__(self):
      # do your stuff
      globals()[type(self).__name__] = lambda: self # singletonify
polvoazul
la source
Cela peut faire l'affaire tant que votre classe n'a pas été importée dans d'autres modules ...
Aran-Fey
Vrai. Si vous pouvez payer le coût de lancement de la classe, vous pouvez exécuter une Myclass () immédiatement après la définition de la classe pour éviter cela.
polvoazul
0

Si vous n'avez pas besoin d'une initialisation paresseuse de l'instance de Singleton, alors ce qui suit devrait être facile et thread-safe:

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

De cette façon, Aun singleton est initialisé lors de l'importation du module.

Serge Rogatch
la source
0

Peut-être que j'ai mal compris le motif singleton mais ma solution est aussi simple et pragmatique (pythonique?). Ce code remplit deux objectifs

  1. Faire l'instance de Foo accessible partout (global).
  2. Une seule instance de Foopeut exister.

Voici le code.

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

Production

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!
buhtz
la source
0

Après avoir lutté avec cela pendant un certain temps, j'ai finalement trouvé ce qui suit, de sorte que l'objet config ne soit chargé qu'une seule fois, lorsqu'il est appelé à partir de modules distincts. La métaclasse permet de stocker une instance de classe globale dans le dict intégré, qui semble actuellement être le moyen le plus pratique de stocker un programme global approprié.

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>...
Den-Jason
la source
-1

Je ne me souviens pas où j'ai trouvé cette solution, mais je la trouve la plus «élégante» de mon point de vue non expert en 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

Pourquoi j'aime ça? Pas de décorateurs, pas de méta-classes, pas d'héritage multiple ... et si vous décidez que vous ne voulez plus que ce soit un Singleton, supprimez simplement la __new__méthode. Comme je suis nouveau sur Python (et OOP en général), je m'attends à ce que quelqu'un me dise pourquoi cette approche est terrible?

2cynykyl
la source
2
pourquoi c'est une approche terrible? lorsque vous voulez créer une autre classe singleton, vous devez copier et coller le __new__. Ne te répète pas .
GingerPlusPlus
Aussi, pourquoi vos nouvelles prises *argset **kwargs, et ensuite ne font rien avec elles? Les passer dans dict.__new__la façon suivante : dict.__new__(cls, *args, **kwargs).
GingerPlusPlus
Cela appellera la __init__méthode à chaque appel de la classe. Si votre __init__méthode a réellement fait quelque chose, vous remarquerez le problème. Chaque fois que vous le faites SomeSingleton(), l'état de votre singleton est réinitialisé par la __init__méthode.
Aran-Fey du
-2

C'est ma façon préférée d'implémenter des singletons:

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
fab
la source
1
Ce n'est pas strictement un singleton car il permet à plus d'une instance de la classe d'exister. Une amélioration consisterait à rendre la classe impossible à initialiser et à faire en sorte que toutes les méthodes de classe agissent sur les attributs de classe
theheadofabroom
-2

Cette réponse n'est probablement pas celle que vous recherchez. Je voulais un singleton dans le sens où seul cet objet avait son identité, par rapport à. Dans mon cas, il était utilisé comme valeur sentinelle . Pour lequel la réponse est très simple, faites n'importe quel objet mything = object()et par nature de python, seule cette chose aura son identité.

#!python
MyNone = object()  # The singleton

for item in my_list:
    if item is MyNone:  # An Example identity comparison
        raise StopIteration
ThorSummoner
la source
J'ai appris que les modules peuvent en fait être importés plusieurs fois, dans ce cas, ce n'est qu'un singleton local, ce qui n'est pas vraiment un singleton à quelque titre que ce soit.
ThorSummoner
Pouvez-vous expliquer comment un module peut être importé plusieurs fois? La seule fois où j'ai vu que c'est quand une exception se produit pendant que le module est chargé, l'utilisateur peut toujours charger le module plus tard, mais les effets secondaires se sont déjà produits, donc certaines actions peuvent être exécutées une deuxième fois.
sleblanc
Une fois qu'un module a été entièrement chargé, je ne vois pas de moyen de faire fonctionner à nouveau ce module, autrement qu'en contraignant l'interpréteur à le faire à l'aide de evalou importlib.reload.
sleblanc
-3

Cette solution provoque une certaine pollution de l'espace de noms au niveau du module (trois définitions plutôt qu'une seule), mais je la trouve facile à suivre.

J'aimerais pouvoir écrire quelque chose comme ça (initialisation paresseuse), mais malheureusement les classes ne sont pas disponibles dans le corps de leurs propres définitions.

# 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

Comme cela n'est pas possible, nous pouvons décomposer l'initialisation et l'instance statique dans

Initialisation désirée:

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

Initialisation paresseuse:

Initialisation désirée:

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
Gregory Nisbet
la source