web-dev-qa-db-fra.com

Créer un singleton en Python

Cette question ne concerne pas la question de savoir si le modèle de conception unique est souhaitable, est un anti-modèle, ou pour toute guerre de religion, mais pour discuter de la meilleure façon de mettre en œuvre ce modèle en Python une telle façon qui est la plupart du temps Pythonic. Dans cet exemple, je définis «plus pythonique» comme signifiant qu’il suit le «principe du moindre étonnement».

J'ai plusieurs classes qui deviendraient singletons (mon cas d'utilisation est celui d'un enregistreur, mais ce n'est pas important). Je ne souhaite pas encombrer plusieurs classes de gomme 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 souvent plus intuitive que l'héritage multiple.

Les inconvénients

  • Bien que les objets créés à l'aide de MyClass () soient de véritables objets singleton, MyClass elle-même est une fonction et non 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)(); 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: 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 un vrai cours
  • La magie couvre automatiquement l'héritage
  • Utilise __metaclass__ à ses propres fins (et m'a informé 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.__= class_.__name__
    return class_w

@singleton
class MyClass(BaseClass):
    pass

Avantages

  • C'est un vrai cours
  • La magie couvre automatiquement l'héritage

Les inconvénients

  • N'y a-t-il pas une surcharge pour créer chaque nouvelle classe? Nous créons ici deux classes pour chaque classe que nous souhaitons créer un singleton. Bien que cela me convienne, je crains que cela ne se répande pas. Bien sûr, la question de savoir s’il est trop facile de modifier ce schéma est en débat ...
  • Quel est le point de l'attribut _sealed
  • Impossible d'appeler des méthodes du même nom sur les classes de base à l'aide de super() car elles renverront. Cela signifie que vous ne pouvez pas personnaliser __new__ et ne pouvez pas sous-classer une classe qui nécessite que vous appeliez jusqu'à __init__.
684
theheadofabroom

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

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

à l'instruction if dans 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 dans 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 (seule) de Logger sera de type class 'your_module.Logger'. Lorsque vous appelez l'enregistreur avec Logger(), Python demande d'abord la métaclasse de Logger, Singleton, que faire, ce qui permet d'empêcher la création d'instances. Ce processus est identique à Python demandant à une classe quoi faire en appelant __getattr__ lorsque vous référencez l'un de ses attributs en effectuant myclass.attribute.

Une métaclasse décide essentiellement de la définition de la classe et de la manière de la mettre en œuvre. Voir, par exemple, http://code.activestate.com/recipes/498149/ , qui reconstitue essentiellement le style C structs dans Python à l'aide de 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 celle utilisée dans les ORM.

Dans cette situation, si vous utilisez votre méthode n ° 2 et qu’une sous-classe définit une méthode __new__, elle sera exécuté à chaque fois , vous appelez SubClassOfSingleton() - car il est responsable de l'appel de la méthode qui renvoie l'instance stockée. Avec une métaclasse, il sera appelé une seule fois lors de la création de la seule instance. Vous voulez personnaliser ce que signifie 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 est créé 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 utilise plusieurs héritages, 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. L'instance dict est pas dans l'espace de nom de l'instance , elle ne sera donc pas écrasée par inadvertance.

Vous entendrez également que le modèle de singleton enfreint le "Principe de responsabilité unique" - chaque classe ne doit faire qu'une seule chose . De cette façon, vous n'avez pas à craindre de perdre une chose dans le code si vous devez en changer une autre, car elles sont séparées et encapsulées. L'implémentation de la métaclasse passe ce test . La métaclasse est responsable de l'application du modèle et la classe et les sous-classes créées ne doivent pas nécessairement être conscientes qu'il s'agit de singletons . La méthode n ° 1 échoue à ce test, comme vous l'avez noté avec "MyClass est une fonction et non 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é. Comme les métaclasses sont généralement des sous-classes de type type, il est possible d’en utiliser une pour créer dynamiquement une classe de base intermédiaire au moment de l’exécution, en tant que métaclasse, puis utiliser que en tant que la classe de base du public Singleton classe de base. C'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 des sous-classes pour implémenter une métaclasse. Un avantage possible est que, contrairement à une métaclasse pure, isinstance(inst, Singleton) retournera True.

Les 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 fausse. _instances doit être référencé sur la classe , vous devez utiliser super() ou vous êtes recursing , et __new__ est en fait une méthode statique que vous devez transmettre à la classe , pas une méthode de classe, car la classe actuelle n'a pas encore été créée lors de son 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 rentrant une classe

Au départ, je rédigeais un commentaire mais celui-ci était trop long, je vais donc l'ajouter ici. La méthode n ° 4 est meilleure que l'autre version du décorateur, mais elle nécessite plus de code que nécessaire pour un singleton, et son rôle n'est pas aussi clair.

Les principaux problèmes proviennent du fait que la classe est sa propre classe de base. Premièrement, n'est-il pas étrange qu'une classe soit une sous-classe d'une classe presque identique portant le même nom et qui n'existe que dans son attribut __class__? Cela signifie également que vous ne pouvez pas définir les méthodes appelant la méthode du même nom sur leur classe de base avec super() car elles le feront. recurse. Cela signifie que votre classe ne peut pas personnaliser __new__, et ne peut dériver d'aucune classe nécessitant l'appel de __init__.

Quand utiliser le motif singleton

Votre cas d'utilisation est l'un des meilleurs exemples de l'utilisation d'un singleton. Vous dites dans l'un des commentaires: "Pour moi, l'exploitation forestière 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 commune est qu’ils sont un état partagé implicite . Alors que, avec les variables globales et les modules de niveau supérieur, les importations sont explicites à l'état partagé, les autres objets 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 d'énums, est largement acceptée et considérée comme saine, car, peu importe le cas, , aucun des utilisateurs ne peut le gâcher pour un autre utilisateur . Ceci est également vrai pour un singleton constant.

La deuxième exception, moins mentionnée, est l’inverse: lorsque le singleton est seulement un collecteur de données et non 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 d'une manière qui intéressera les autres utilisateurs, il n'y a pas d'état vraiment partagé. . Cela annule l'argument principal du motif singleton et en fait un choix raisonnable en raison de la facilité d'utilisation de 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 genre de singleton en mutable, c’est une pente très glissante. Par conséquent, je suis également contre ces Singletons, non pas parce qu’ils sont mauvais, mais parce qu’il leur est très facile de se dégrader. (En note Java, l'énumération ne sont que ce genre de singletons. Tant que vous ne mettez pas d'état dans votre énumération, vous êtes OK, donc veuillez ne 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 est un exemple parfait. Il est chargé de singletons et d'état global. C'est acceptable (car cela ne vous fera pas de mal) parce que votre application ne se comporte pas différemment, qu'un enregistreur donné soit activé ou non. Les informations présentées ici ne vont que dans un sens: de votre application à l'enregistreur. Même les penseurs sont à l’état global puisqu’aucune information ne coule dans votre application, ils sont acceptables. Vous devez quand même injecter votre enregistreur si vous souhaitez que votre test affirme que quelque chose est en train d’être enregistré, mais en général, les enregistreurs ne sont pas dangereux, bien qu’ils soient pleins d’état.

552
agf
class Foo(object):
     pass

some_global_variable = Foo()

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

75
Cat Plus Plus

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

56
warvariuc

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 obtenez un singleton de facto.

Si vous devez absolument avoir un cours singleton, alors je choisirais:

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.

20
Alan Dyke

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 impatience. Cela peut être ou ne pas être ce que vous voulez.

14
Jonas Kölker

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 recommanderais fortement de regarder les exposés d'Alex Martelli sur les modèles de conception en python: partie 1 et partie 2 . Dans la partie 1, il parle en particulier de singletons/d'objets d'état partagés.

7
Anton

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 alors utiliser la méthode Instance. 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
Paul Manta

La méthode 3 semble être très soignée, mais si vous voulez que votre programme s'exécute à la fois 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 génère 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 en 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 cet objet dans l'assignation doit être remplacé par la «BaseClass», mais je n'ai pas essayé cela (j'ai essayé le code tel qu'illustré).

2
Tim

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

@singleton
class MySingleton:
    #....

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

1
Tolli

Les réponses précédentes sont correctes, mais je ne suis pas d'accord avec l'énoncé posté dans le cadre de la question de la méthode 1 "MyClass est en soi une fonction et non une classe. Vous ne pouvez donc pas appeler de méthodes de classe". Voir mon exemple ci-dessous, cette méthode est appelée plusieurs fois, dans MyClass qui est décorée avec une balise singleton.

Notez également que ceci est très similaire à certaines des réponses postées et est basé sur document python mais il est légèrement différent car la classe et la fonction sont conçues de manière à pouvoir recevoir 1 ou 0 arguments et toujours des travaux singleton.

 enter image description here Voici la preuve que vous pouvez appeler les méthodes de singleton plusieurs fois et montre qu'une instance de la classe est toujours utilisée et qu'aucun nouvel objet n'est créé.

#/usr/bin/env python

def singleton(cls):
    instances = {}
    def getinstance(anyArgs=None):
        if cls not in instances:
            instances[cls] = cls(anyArgs)
        return instances[cls]
    return getinstance

@singleton
class MyClass:
    def __init__(self,count=None):
        print("print argument if any exists",count)

    def test(self, counter):
        # time.sleep(1000)
        print("-->",counter)
        return counter

### create two objects to see if we get a singleton behavior !
a = MyClass(10000)
a.test(1)
b = MyClass()
b.test(2)
if a != b:
    print("this is not a singleton")

#lets makesure it's still is the same object
if a!=b:
    print("error")

Étant donné que le chef de la chambre à coucher (la personne qui a posé la question) avait fait part de ses commentaires sur sa question initiale, je suis allé travailler sur une nouvelle solution en fonction de ses commentaires (j’ai gardé ma réponse précédente car je pense que cela pourrait être utile à pas exactement ce que Heheadofabroom demande)… .. Voici la réponse mise à jour:

 enter image description here

voici le code pour le copier et le coller :)

#/usr/bin/env python
from functools import wraps 

def singleton(cls):
    instances = {}
    def getinstance(anyArgs=None):
        if cls not in instances:
            instances[cls] = cls(anyArgs)
            return instances[cls]
    return getinstance

def add_function(cls):
    def outer_decorate_it(somefunction):
        @wraps(somefunction) 
        def wrapper( *args, **kwargs): 
            return somefunction(*args, **kwargs)
        setattr(cls, somefunction.__name__, wrapper)
        return somefunction
    return outer_decorate_it

@singleton
class MyClass():
    def __init__(self,count=None):
        print("print argument if any exists",count)

@add_function(MyClass)
def testit():
    print("It's me the function from the class")


MyClass.testit()
1
grepit

Bien, à part d’accepter la suggestion générale de Pythonic d’avoir un niveau de module global, 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.__= 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 __is preserved
True    # this is actually the same instance
1
Guard

Code basé sur 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 en héritant de cls donné
    (cela ne modifie pas cls au cas où quelqu'un voudrait par exemple singleton(list))

  2. Créer une instance. Avant de remplacer __new__, c'est si simple.

  3. Maintenant, lorsque nous avons facilement créé une instance, remplace __new__ en utilisant la méthode définie précédemment. 
  4. La fonction renvoie instance uniquement lorsque c'est ce que l'appelant attend, sinon elle soulè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, la méthode __init__() de la nouvelle instance sera invoquée comme __init__(self[, ...]), où self est la nouvelle instance et les arguments restants sont identiques à ceux passés à __new__().

    instance est déjà initialisé, donc la fonction remplace __init__ par la fonction ne faisant rien.

Voir le travail en ligne

0
GingerPlusPlus

C'est un peu similaire à la réponse par fab mais pas exactement la même chose.

Le contrat singleton ne nécessite pas que nous puissions appeler le constructeur plusieurs fois. En tant que singleton devrait être créé une fois et une fois seulement, ne devrait-il pas être vu comme créé une seule fois? "Usurper" le constructeur altère la lisibilité.

Donc, ma suggestion est la suivante:

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 instance par le code utilisateur:

if Elvis() is King.instance:

... si vous savez avec certitude que Elvis n'a pas encore été créé, et que King l'a fait.

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

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

Pour rendre cela complet, vous pouvez également remplacer __delattr__() pour déclencher une exception si vous tentez de supprimer instance et remplacer __del__() pour qu'il déclenche une exception (sauf si nous savons que le programme se termine ...)

Autres améliorations


Merci à ceux qui ont aidé avec des commentaires et des modifications, dont plusieurs 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 de style ancien, qui n'utilise pas __new__
  2. Lorsque vous décorez __new__, vous devez décorer avec @classmethod ou __new__ sera une méthode d'instance indépendante.
  3. Cela pourrait éventuellement être amélioré en utilisant une métaclasse, car cela vous permettrait de transformer the en une propriété de niveau classe, en la renommant éventuellement en instance.
0
mike rodent

Un paquebot (je ne suis pas fier, mais ça fait le travail):

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

Si vous n'avez pas besoin d'initialisation paresseuse de l'instance de Singleton, procédez comme suit:

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

De cette façon, A est un singleton initialisé à l’importation du module.

0
Serge Rogatch

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 à propos 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 celle de l'usine, pas la "vraie" classe).
  • Il ne repose sur rien pour patcher le singe.
  • C'est transparent pour les appelants:
    • Les appelants continuent simplement d'importer YourClass, cela ressemble à une classe (parce que c'est le cas) et ils l'utilisent normalement. Pas besoin d'adapter les appelants à une fonction d'usine.
    • Ce que YourClass() instancie est toujours une instance vraie de la YourClass que vous avez implémentée, pas un proxy de quelque nature que ce soit, donc aucune chance d’effets secondaires résultant de cela.
    • isinstance(instance, YourClass) et des opérations similaires fonctionnent toujours comme prévu (bien que ce bit nécessite abc, il exclut 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. J'en ai assez rarement utilisé pour ne jamais rencontrer ce besoin, mais cela serait facilement corrigé en utilisant une métaclasse personnalisée sur la fabrique qui implémente __getattr__() pour déléguer l'accès d'attributs complets à la vraie classe.

Un motif connexe que j'ai en fait trouvé plus utile (ce n'est pas que je dise que ce genre de choses est nécessaire très souvent) est un motif "Unique" où instancier la classe avec les mêmes arguments a pour résultat de revenir la même instance. C'est à dire. un "singleton par arguments". Ce qui précède s’adapte à ce puits 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 étant dit, je suis d'accord avec le conseil général qui veut 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.

0
CryingCyclops

Je ne comprends peut-être pas le motif singleton, mais ma solution est simple et pragmatique (pythonique?). Ce code remplit deux objectifs

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

Ceci est 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 __== '__main__':
    Foo()
    Foo()

Sortie

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
buhtz
  • Si on veut avoir plusieurs nombres d'instances de la même classe, mais seulement si les arguments ou les kwargs sont différents, on peut utiliser this
  • Ex.
    1. Si vous avez une classe qui gère la communication serial et que vous voulez créer une instance, vous souhaitez envoyer le port série sous forme d'argument, 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 renverra 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
>>>
0