Question Créer un singleton en Python


Cette question ne concerne pas la question de savoir si le modèle de conception singleton est souhaitable, est un anti-modèle, ou pour toute guerre religieuse, mais pour discuter de la façon dont ce modèle est le mieux mis en œuvre en Python d'une manière qui est le plus pythonique. Dans ce cas, je définis «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 veux pas encombrer plusieurs classes avec gogo supplémentaire 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

  • Bien que les objets créés avec MyClass () soient de vrais objets singleton, MyClass lui-même est une fonction et non une classe, donc vous ne pouvez pas appeler les méthodes de classe. Aussi pour m = MyClass(); n = MyClass(); o = type(n)(); puis m == 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 un vrai cours

Les inconvénients

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

Méthode 3: A 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 un vrai cours
  • Auto-magiquement couvre l'héritage
  • Les usages __metaclass__ pour son but propre (et m'a fait prendre conscience de cela)

Les inconvénients

  • Y a-t-il?

Méthode 4: décorateur retournant une classe avec le 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 un vrai cours
  • Auto-magiquement couvre l'héritage

Les inconvénients

  • N'y a-t-il pas un surcoût pour créer chaque nouvelle classe? Ici, nous créons deux classes pour chaque classe que nous souhaitons faire un singleton. Bien que ce soit bien dans mon cas, je crains que cela ne puisse pas évoluer. Bien sûr, il y a un débat sur la question de savoir s'il est trop facile d'adapter ce modèle ...
  • Quel est le point de la _sealed attribut
  • Impossible d'appeler des méthodes du même nom sur des classes de base à l'aide de super() parce qu'ils vont recurse. Cela signifie que vous ne pouvez pas personnaliser __new__ et ne peut pas sous-classer une classe qui a besoin de vous appeler à __init__.

560
2017-07-20 10:47


origine


Réponses:


Utilisez une métaclasse

je recommanderais Méthode n ° 2mais il vaut mieux utiliser un métaclasse qu'une classe de base. Voici un exemple de mise en œuvre:

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 voulez courir __init__ chaque fois que la classe est appelée, ajoutez

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

au if déclaration en Singleton.__call__.

Quelques mots sur les métaclasses. Une métaclasse est le classe d'une classe; c'est-à-dire, une classe est un 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. Logger dans le code ci-dessus sera de type class 'your_module.Singleton', tout comme l'instance (seulement) de Logger sera de type class 'your_module.Logger'. Lorsque vous appelez le consignateur avec Logger(), Python demande d'abord la métaclasse de Logger, Singleton, que faire, permettant de préempter la création d'instance. 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 la définition d'une classe signifie et comment mettre en œuvre cette définition. Voir par exemple http://code.activestate.com/recipes/498149/, qui recrée essentiellement le style C structs en Python en utilisant des métaclasses. Le fil Quels sont vos cas d'utilisation (concrets) pour les métaclasses en Python? fournit également quelques exemples, ils semblent généralement être liés à la programmation déclarative, en particulier dans les ORM.

Dans cette situation, si vous utilisez votre Méthode n ° 2, et une sous-classe définit un __new__ méthode, il sera exécuté à chaque fois tu appelles SubClassOfSingleton() - parce qu'il est responsable de l'appel de la méthode qui renvoie l'instance stockée. Avec une métaclasse, il faudra seulement être appelé une fois, lorsque la seule instance est créée. Tu veux personnaliser ce que cela signifie d'appeler la classe, qui est décidé par son type.

En général, il logique utiliser une métaclasse pour implémenter un singleton. Un singleton est spécial parce que créé une seule fois, et une métaclasse est la façon dont vous personnalisez le création d'une classe. En utilisant une métaclasse vous donne plus de contrôle dans le cas où vous avez besoin de personnaliser les définitions de classe singleton d'autres façons.

Vos singletons n'aura pas besoin d'héritage multiple (parce que la métaclasse n'est pas une classe de base), mais pour sous-classes de la classe créée qui utilisent l'héritage multiple, vous devez vous assurer que la classe singleton est le premier / plus à gauche un avec une métaclasse qui redéfinit __call__ Ceci est très peu susceptible d'être un problème. L'instance dict est pas dans l'espace de noms de l'instance donc il ne va pas l'écraser accidentellement.

Vous entendrez également que le modèle Singleton viole le "principe de responsabilité unique" - chaque classe devrait faire seulement une chose. De cette façon, vous n'avez pas à vous soucier de casser une chose que le code fait si vous avez besoin de changer un autre, parce qu'ils sont séparés et encapsulés. L'implémentation de la métaclasse passe ce test. La métaclasse est responsable de appliquer le motif et la classe créée et les sous-classes ne doivent pas être conscient qu'ils sont singletons. Méthode n ° 1 échoue ce test, comme vous l'avez noté avec "MyClass lui-même est une fonction, pas une classe, donc vous ne pouvez pas appeler les méthodes de classe à partir de lui."

Version compatible Python 2 et 3

Ecrire quelque chose qui fonctionne à la fois dans Python2 et 3 nécessite d'utiliser un schéma un peu plus compliqué. Puisque les métaclasses sont généralement des sous-classes de type type, il est possible d'en utiliser un pour créer dynamiquement une classe de base intermédiaire à l'exécution avec sa métaclasse, puis utiliser cette comme le baseclass du public Singleton classe de base. Il est plus difficile à expliquer qu'à faire, comme illustré ci-après:

# 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 la sous-classe pour implémenter une métaclasse. Un avantage possible est que, contrairement à une métaclasse pure, isinstance(inst, Singleton)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 erronée. _instances doit ê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 que vous devez passer la classe à, pas une méthode de classe, comme la classe réelle n'a pas été créé pourtant quand il est appelé. Toutes ces choses seront aussi 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ébut, j'écrivais un commentaire mais c'était trop long, donc je vais ajouter ceci ici. Méthode n ° 4 est meilleur 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 d'avoir une classe qui 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 toutes les méthodes qui appellent la méthode du même nom sur leur classe de base avec super() parce qu'ils vont recurse. Cela signifie que votre classe ne peut pas personnaliser __new__, et ne peut dériver de toutes les classes qui ont besoin __init__ appelé sur eux.

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'enregistrement a toujours semblé un candidat naturel pour Singletons." Vous êtes absolument raison.

Quand les gens disent que les singletons sont mauvais, la raison la plus courante est qu'ils sont état partagé implicite. Alors qu'avec les variables globales et les importations de modules de premier niveau, explicite état partagé, les autres objets transmis sont généralement instanciés. C'est un bon point, avec deux exceptions.

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

La deuxième exception, qui est mentionnée moins, est le contraire - lorsque le singleton est seulement un puits de données, pas une source de données (directement ou indirectement). C'est pourquoi les bûcherons se sentent comme un usage «naturel» pour les singletons. Comme les différents utilisateurs sont ne pas changer les enregistreurs d'autres utilisateurs se soucieront, il y a état pas vraiment 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 atteignables 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 genre de singleton en mutable, c'est une pente très glissante. Par conséquent, je suis contre ces Singletons aussi, pas parce qu'ils sont mauvais, mais parce qu'il est très facile pour eux d'aller mal. (En guise de remarque, l'énumération Java est juste ce genre de singletons: tant que vous ne mettez pas l'état dans votre énumération, vous êtes OK, alors s'il vous plaît ne le faites pas.)

Les autres types de singletons, qui sont semi-acceptables, sont ceux qui n'influent pas sur l'exécution de votre code. Ils n'ont pas d'effets secondaires. La journalisation est un exemple parfait. Il est chargé de singletons et d'état global. C'est acceptable (comme cela ne vous nuira pas) parce que votre application ne se comporte pas différemment si un enregistreur donné est activé ou non. L'information ici coule d'une manière: De votre application dans l'enregistreur. Même les enregistreurs de pensée sont des états globaux car aucune information ne circule des enregistreurs dans votre application, les enregistreurs sont acceptables. Vous devriez toujours injecter votre enregistreur si vous voulez que votre test affirme que quelque chose est enregistré, mais en général les enregistreurs ne sont pas nuisibles bien qu'ils soient pleins d'état.


386
2017-07-23 03:28



class Foo(object):
     pass

some_global_variable = Foo()

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


60
2017-07-20 10:52



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


49
2017-07-20 10:58



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 avez absolument besoin d'un cours de singleton, 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.


12
2017-12-10 22:24



Voici un one-liner 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 empressement. Cela peut ou peut ne pas être ce que vous voulez.


10
2017-10-19 14:43



Consultez la question sur le dépassement de pile Existe-t-il une manière simple et élégante de définir des singletons en Python? avec plusieurs solutions.

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


5
2017-07-20 11:00



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 le Instance mé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)

3
2017-09-20 18:04



Eh bien, à part d'ê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 de ceci:

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

2
2017-07-25 00:09



La méthode 3 semble très bien, mais si vous voulez que votre programme fonctionne à la fois Python 2 et Python 3ça ne marche 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 dans 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 présume que 'objet' dans la tâche doit être remplacé par 'BaseClass', mais je n'ai pas essayé cela (j'ai essayé le code comme illustré).


2
2017-07-24 17:06



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 un décorateur sur une classe qui devrait être un singleton. Comme ça:

@singleton
class MySingleton:
    #....

Ceci est similaire à la 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 réalité obtenir la seule instance) de la classe, en faisant MySingleton(). Cela vous empêche également de créer des instances supplémentaires en faisant type(MySingleton)() (Cela renvoie également la même instance).


1
2018-05-17 23:48