Existe-t-il une manière simple et élégante de définir des singletons ?[dupliquer]
-
09-06-2019 - |
Question
Cette question a déjà une réponse ici :
- Créer un singleton en Python 20 réponses
Il semble y avoir de nombreuses façons de définir célibataires en Python.Existe-t-il un consensus sur Stack Overflow ?
La solution
Je n'en vois pas vraiment la nécessité, car un module avec des fonctions (et non une classe) servirait bien de singleton.Toutes ses variables seraient liées au module, qui ne pourrait de toute façon pas être instancié à plusieurs reprises.
Si vous souhaitez utiliser une classe, il n'existe aucun moyen de créer des classes privées ou des constructeurs privés en Python, vous ne pouvez donc pas vous protéger contre plusieurs instanciations, autrement que via la simple convention d'utilisation de votre API.Je mettrais toujours simplement des méthodes dans un module et considérerais le module comme le singleton.
Autres conseils
Voici ma propre implémentation de singletons.Tout ce que vous avez à faire est de décorer la classe ;pour obtenir le singleton, il faut alors 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. Also, the decorated class cannot be
inherited from. 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.
"""
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)
Vous pouvez remplacer le __new__
méthode comme celle-ci :
class Singleton(object):
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(
cls, *args, **kwargs)
return cls._instance
if __name__ == '__main__':
s1 = Singleton()
s2 = Singleton()
if (id(s1) == id(s2)):
print "Same"
else:
print "Different"
Une approche légèrement différente pour implémenter le singleton en Python est la motif borgne par Alex Martelli (employé de Google et génie Python).
class Borg:
__shared_state = {}
def __init__(self):
self.__dict__ = self.__shared_state
Ainsi, au lieu de forcer toutes les instances à avoir la même identité, elles partagent un état.
L’approche modulaire fonctionne bien.Si j'ai absolument besoin d'un singleton, je préfère l'approche Metaclass.
class Singleton(type):
def __init__(cls, name, bases, dict):
super(Singleton, cls).__init__(name, bases, dict)
cls.instance = None
def __call__(cls,*args,**kw):
if cls.instance is None:
cls.instance = super(Singleton, cls).__call__(*args, **kw)
return cls.instance
class MyClass(object):
__metaclass__ = Singleton
Voir cette implémentation de PEP318, implémentant le modèle singleton avec un décorateur :
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
@singleton
class MyClass:
...
Comme le réponse acceptée dit, la manière la plus idiomatique est simplement de utiliser un module.
Dans cet esprit, voici une preuve de concept :
def singleton(cls):
obj = cls()
# Always return the same object
cls.__new__ = staticmethod(lambda cls: obj)
# Disable __init__
try:
del cls.__init__
except AttributeError:
pass
return cls
Voir le Modèle de données Python pour plus de détails sur __new__
.
Exemple:
@singleton
class Duck(object):
pass
if Duck() is Duck():
print "It works!"
else:
print "It doesn't work!"
Remarques:
Vous devez utiliser des classes de nouveau style (dérivées de
object
) pour ça.Le singleton est initialisé lorsqu'il est défini, plutôt que lors de sa première utilisation.
Ceci est juste un exemple de jouet.Je ne l'ai jamais utilisé dans le code de production et je n'ai pas l'intention de le faire.
Je n'en suis pas sûr, mais mon projet utilise des « singletons conventionnels » (et non des singletons forcés), c'est-à-dire si j'ai une classe appelée DataController
, je définis cela dans le même module :
_data_controller = None
def GetDataController():
global _data_controller
if _data_controller is None:
_data_controller = DataController()
return _data_controller
Ce n’est pas élégant, puisqu’il s’agit de six lignes complètes.Mais tous mes singletons utilisent ce modèle, et il est au moins très explicite (qui est pythonique).
Le Documentation Python couvre cela :
class Singleton(object):
def __new__(cls, *args, **kwds):
it = cls.__dict__.get("__it__")
if it is not None:
return it
cls.__it__ = it = object.__new__(cls)
it.init(*args, **kwds)
return it
def init(self, *args, **kwds):
pass
Je le réécrirais probablement pour ressembler davantage à ceci :
class Singleton(object):
"""Use to create a singleton"""
def __new__(cls, *args, **kwds):
"""
>>> s = Singleton()
>>> p = Singleton()
>>> id(s) == id(p)
True
"""
self = "__self__"
if not hasattr(cls, self):
instance = object.__new__(cls)
instance.init(*args, **kwds)
setattr(cls, self, instance)
return getattr(cls, self)
def init(self, *args, **kwds):
pass
Il devrait être relativement propre d'étendre ceci :
class Bus(Singleton):
def init(self, label=None, *args, **kwds):
self.label = label
self.channels = [Channel("system"), Channel("app")]
...
La seule fois où j'ai écrit un singleton en Python, j'ai utilisé une classe où toutes les fonctions membres avaient le décorateur de méthode de classe.
class foo:
x = 1
@classmethod
def increment(cls, y = 1):
cls.x += y
Il existe également des articles intéressants sur le blog Google Testing, expliquant pourquoi les singletons sont/peuvent être mauvais et constituent un anti-modèle :
Créer un décorateur singleton (c'est-à-dire une annotation) est une manière élégante si vous souhaitez décorer (annoter) des classes à l'avenir.Ensuite, vous mettez simplement @singleton avant la définition de votre classe.
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
@singleton
class MyClass:
...
Voici un exemple de Python IAQ de Peter Norvig Comment créer le modèle Singleton en Python ? (Vous devez utiliser la fonction de recherche de votre navigateur pour trouver cette question, il n'y a pas de lien direct, désolé)
Bruce Eckel a également un autre exemple dans son livre Penser en Python (encore une fois, il n'y a pas de lien direct vers le code)
je pense que forcer une classe ou une instance pour être un singleton est exagérée.Personnellement, j'aime définir une classe instanciable normale, une référence semi-privée et une simple fonction d'usine.
class NothingSpecial:
pass
_the_one_and_only = None
def TheOneAndOnly():
global _the_one_and_only
if not _the_one_and_only:
_the_one_and_only = NothingSpecial()
return _the_one_and_only
Ou s'il n'y a aucun problème d'instanciation lors de la première importation du module :
class NothingSpecial:
pass
THE_ONE_AND_ONLY = NothingSpecial()
De cette façon, vous pouvez écrire des tests sur de nouvelles instances sans effets secondaires, et il n'est pas nécessaire de saupoudrer le module d'instructions globales, et si nécessaire, vous pouvez dériver des variantes à l'avenir.
Le modèle Singleton implémenté avec Python gracieuseté d'ActiveState.
Il semble que l’astuce consiste à placer la classe censée n’avoir qu’une seule instance dans une autre classe.
Étant relativement nouveau sur Python, je ne suis pas sûr de savoir quel est l'idiome le plus courant, mais la chose la plus simple à laquelle je puisse penser est simplement d'utiliser un module au lieu d'une classe.Ce qui aurait été des méthodes d'instance sur votre classe deviennent simplement des fonctions dans le module et toutes les données deviennent simplement des variables dans le module au lieu de membres de la classe.Je soupçonne que c'est l'approche pythonique pour résoudre le type de problème pour lequel les gens utilisent des singletons.
Si vous voulez vraiment une classe singleton, il existe une implémentation raisonnable décrite sur le premier coup sur Google pour "Python singleton", plus précisément :
class Singleton:
__single = None
def __init__( self ):
if Singleton.__single:
raise Singleton.__single
Singleton.__single = self
Cela semble faire l'affaire.
OK, un singleton peut être bon ou mauvais, je sais.Il s'agit de mon implémentation, et j'étends simplement une approche classique pour introduire un cache à l'intérieur et produire de nombreuses instances d'un type différent ou plusieurs instances du même type, mais avec des arguments différents.
Je l'ai appelé Singleton_group, car il regroupe des instances similaires et empêche qu'un objet de la même classe, avec les mêmes arguments, puisse être créé :
# Peppelinux's cached singleton
class Singleton_group(object):
__instances_args_dict = {}
def __new__(cls, *args, **kwargs):
if not cls.__instances_args_dict.get((cls.__name__, args, str(kwargs))):
cls.__instances_args_dict[(cls.__name__, args, str(kwargs))] = super(Singleton_group, cls).__new__(cls, *args, **kwargs)
return cls.__instances_args_dict.get((cls.__name__, args, str(kwargs)))
# It's a dummy real world use example:
class test(Singleton_group):
def __init__(self, salute):
self.salute = salute
a = test('bye')
b = test('hi')
c = test('bye')
d = test('hi')
e = test('goodbye')
f = test('goodbye')
id(a)
3070148780L
id(b)
3070148908L
id(c)
3070148780L
b == d
True
b._Singleton_group__instances_args_dict
{('test', ('bye',), '{}'): <__main__.test object at 0xb6fec0ac>,
('test', ('goodbye',), '{}'): <__main__.test object at 0xb6fec32c>,
('test', ('hi',), '{}'): <__main__.test object at 0xb6fec12c>}
Chaque objet porte le cache singleton...Cela pourrait être mauvais, mais cela fonctionne très bien pour certains :)
class Singleton(object[,...]):
staticVar1 = None
staticVar2 = None
def __init__(self):
if self.__class__.staticVar1==None :
# create class instance variable for instantiation of class
# assign class instance variable values to class static variables
else:
# assign class static variable values to class instance variables
Ma solution simple qui est basée sur la valeur par défaut des paramètres de fonction.
def getSystemContext(contextObjList=[]):
if len( contextObjList ) == 0:
contextObjList.append( Context() )
pass
return contextObjList[0]
class Context(object):
# Anything you want here
Le demi-frère de Singleton
Je suis entièrement d'accord avec Staale et je laisse ici un exemple de création d'un demi-frère singleton :
class void:pass
a = void();
a.__class__ = Singleton
a
signalera maintenant comme étant de la même classe que singleton même si cela ne lui ressemble pas.Ainsi, les singletons utilisant des classes compliquées finissent par dépendre du fait que nous ne les manipulons pas beaucoup.
Ainsi, nous pouvons avoir le même effet et utiliser des choses plus simples comme une variable ou un module.Pourtant, si nous voulons utiliser des classes pour plus de clarté et parce que en Python, une classe est un objet, donc nous avons déjà l'objet (pas une instance, mais il fera l'affaire).
class Singleton:
def __new__(cls): raise AssertionError # Singletons can't have instances
Là, nous avons une belle erreur d'assertion si nous essayons de créer une instance, et nous pouvons stocker sur des dérivations des membres statiques et y apporter des modifications au moment de l'exécution (j'adore Python).Cet objet est aussi bon que d'autres demi-frères (vous pouvez toujours les créer si vous le souhaitez), cependant il aura tendance à fonctionner plus vite en raison de sa simplicité.
class Singeltone(type):
instances = dict()
def __call__(cls, *args, **kwargs):
if cls.__name__ not in Singeltone.instances:
Singeltone.instances[cls.__name__] = type.__call__(cls, *args, **kwargs)
return Singeltone.instances[cls.__name__]
class Test(object):
__metaclass__ = Singeltone
inst0 = Test()
inst1 = Test()
print(id(inst1) == id(inst0))
Dans les cas où vous ne souhaitez pas la solution basée sur les métaclasses ci-dessus et que vous n'aimez pas l'approche simple basée sur un décorateur de fonctions (par ex.car dans ce cas les méthodes statiques sur la classe singleton ne fonctionneront pas), ce compromis fonctionne :
class singleton(object):
"""Singleton decorator."""
def __init__(self, cls):
self.__dict__['cls'] = cls
instances = {}
def __call__(self):
if self.cls not in self.instances:
self.instances[self.cls] = self.cls()
return self.instances[self.cls]
def __getattr__(self, attr):
return getattr(self.__dict__['cls'], attr)
def __setattr__(self, attr, value):
return setattr(self.__dict__['cls'], attr, value)