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:
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 inconvénients
m = MyClass(); n = MyClass(); o = type(n)();
puis m == n && m != o && n != o
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
Les inconvénients
__new__
pourrait être écrasé pendant l'héritage d'une deuxième classe de base? Il faut penser plus que nécessaire.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
__metaclass__
à ses propres fins (et m'a informé de cela)Les inconvénients
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
Les inconvénients
_sealed
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__
.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 struct
s 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".
É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
.
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()
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__
.
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.
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.
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.
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.
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.
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.
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)
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é).
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).
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.
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:
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()
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
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:
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)
)
Créer une instance. Avant de remplacer __new__
, c'est si simple.
__new__
en utilisant la méthode définie précédemment. 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.
Si
__new__()
renvoie une instance decls
, 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.
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 ...)
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:
__new__
__new__
, vous devez décorer avec @classmethod ou __new__
sera une méthode d'instance indépendante.the
en une propriété de niveau classe, en la renommant éventuellement en instance
.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
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.
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:
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).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.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.
Je ne comprends peut-être pas le motif singleton, mais ma solution est simple et pragmatique (pythonique?). Ce code remplit deux objectifs
Foo
accessible partout (global).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!
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.>>> 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
>>>