Utiliser une métaclasse
Je recommanderais la méthode n ° 2 , mais il vaut mieux utiliser une métaclasse qu'une classe de base. Voici un exemple d'implémentation:
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Logger(object):
__metaclass__ = Singleton
Ou en Python3
class Logger(metaclass=Singleton):
pass
Si vous souhaitez exécuter __init__
chaque fois que la classe est appelée, ajoutez
else:
cls._instances[cls].__init__(*args, **kwargs)
à la if
déclaration en Singleton.__call__
.
Quelques mots sur les métaclasses. Une métaclasse est la classe d'une classe ; c'est-à-dire qu'une classe est une instance de sa métaclasse . Vous trouvez la métaclasse d'un objet en Python avec type(obj)
. Les classes normales de nouveau style sont de type type
. Logger
dans le code ci-dessus sera de type class 'your_module.Singleton'
, tout comme la (seule) instance de Logger
sera de type class 'your_module.Logger'
. Lorsque vous appelez avec enregistreur Logger()
, Python demande d' abord la métaclasse Logger
, Singleton
, ce qu'il faut faire, ce qui permet la création d'instance à préempté. Ce processus est le même que Python demandant à une classe quoi faire en appelant __getattr__
lorsque vous faites référence à l'un de ses attributs en faisant myclass.attribute
.
Une métaclasse décide essentiellement ce que signifie la définition d'une classe et comment mettre en œuvre cette définition. Voir par exemple http://code.activestate.com/recipes/498149/ , qui recrée essentiellement des styles C struct
en Python à l'aide de métaclasses. Le fil Quels sont les cas d'utilisation (concrets) des métaclasses? fournit également quelques exemples, ils semblent généralement liés à la programmation déclarative, en particulier telle qu'elle est utilisée dans les ORM.
Dans cette situation, si vous utilisez votre méthode n ° 2 et qu'une sous-classe définit une __new__
méthode, elle sera exécutée à chaque appel SubClassOfSingleton()
, car elle est responsable de l'appel de la méthode qui renvoie l'instance stockée. Avec une métaclasse, elle ne sera appelée qu'une seule fois , lorsque la seule instance sera créée. Vous voulez personnaliser ce que cela signifie d'appeler la classe , qui est déterminée par son type.
En général, il est logique d'utiliser une métaclasse pour implémenter un singleton. Un singleton est spécial car il n'est créé qu'une seule fois , et une métaclasse est la façon dont vous personnalisez la création d'une classe . L'utilisation d'une métaclasse vous donne plus de contrôle au cas où vous auriez besoin de personnaliser les définitions de classe singleton d'une autre manière.
Vos singletons n'auront pas besoin d'héritage multiple (car la métaclasse n'est pas une classe de base), mais pour les sous-classes de la classe créée qui utilisent l'héritage multiple, vous devez vous assurer que la classe singleton est la première / la plus à gauche avec une métaclasse qui redéfinit __call__
Il est très peu probable que ce soit un problème. Le dict d'instance n'est pas dans l'espace de noms de l'instance, il ne le remplacera donc pas accidentellement.
Vous entendrez également que le modèle singleton viole le «principe de responsabilité unique» - chaque classe ne doit faire qu'une seule chose . De cette façon, vous n'avez pas à vous soucier de gâcher une chose que le code fait si vous devez en changer une autre, car ils sont séparés et encapsulés. L'implémentation de la métaclasse réussit ce test . La métaclasse est responsable de l' application du modèle et la classe et les sous-classes créées n'ont pas besoin de savoir qu'elles sont des singletons . La méthode n ° 1 échoue à ce test, comme vous l'avez noté avec "MyClass lui-même est une fonction, pas une classe, vous ne pouvez donc pas appeler de méthodes de classe à partir de celle-ci."
Version compatible Python 2 et 3
Écrire quelque chose qui fonctionne à la fois en Python2 et 3 nécessite l'utilisation d'un schéma légèrement plus compliqué. Étant donné que les métaclasses sont généralement sous - classes de type type
, il est possible d'utiliser un pour créer dynamiquement une classe de base intermédiaire au moment de l' exécution avec elle comme métaclasse et utiliser alors que les baseclass du public Singleton
classe de base. Il est plus difficile à expliquer qu'à faire, comme illustré ci-dessous:
# works in Python 2 & 3
class _Singleton(type):
""" A metaclass that creates a Singleton base class when called. """
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Singleton(_Singleton('SingletonMeta', (object,), {})): pass
class Logger(Singleton):
pass
Un aspect ironique de cette approche est qu'elle utilise le sous-classement pour implémenter une métaclasse. Un avantage possible est que, contrairement à une métaclasse pure, isinstance(inst, Singleton)
il reviendra True
.
Corrections
Sur un autre sujet, vous l'avez probablement déjà remarqué, mais l'implémentation de la classe de base dans votre message d'origine est incorrecte. _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 à laquelle vous devez passer la classe , pas une méthode de classe, car la classe réelle n'a pas encore été créée quand elle est appelé. Toutes ces choses seront également vraies pour une implémentation de métaclasse.
class Singleton(object):
_instances = {}
def __new__(class_, *args, **kwargs):
if class_ not in class_._instances:
class_._instances[class_] = super(Singleton, class_).__new__(class_, *args, **kwargs)
return class_._instances[class_]
class MyClass(Singleton):
pass
c = MyClass()
Décorateur retournant une classe
Au départ, j'écrivais un commentaire mais c'était trop long, je vais donc l'ajouter ici. La méthode # 4 est meilleure que l'autre version de décorateur, mais c'est plus de code que nécessaire pour un singleton, et ce n'est pas aussi clair.
Les principaux problèmes proviennent de la classe étant sa propre classe de base. Tout d'abord, n'est-il pas étrange qu'une classe soit une sous-classe d'une classe presque identique avec le même nom qui n'existe que dans son __class__
attribut? Cela signifie également que vous ne pouvez pas définir de méthodes qui appellent la méthode du même nom sur leur classe de base avec, super()
car elles recurseront. Cela signifie que votre classe ne peut pas être personnalisée __new__
et ne peut dériver d'aucune classe qui doit y faire __init__
appel.
Quand utiliser le motif singleton
Votre cas d'utilisation est l' un des meilleurs exemples de vouloir utiliser un singleton. Vous dites dans l'un des commentaires "Pour moi, l'exploitation forestière a toujours semblé un candidat naturel pour les Singletons." Tu as absolument raison .
Lorsque les gens disent que les singletons sont mauvais, la raison la plus courante est qu'ils sont un état partagé implicite . Alors qu'avec les variables globales et les importations de module de niveau supérieur , l'état partagé est explicite , les autres objets qui sont transmis sont généralement instanciés. C'est un bon point, à deux exceptions près .
Le premier, et celui qui est mentionné à divers endroits, est lorsque les singletons sont constants . L'utilisation de constantes globales, en particulier les énumérations, est largement acceptée et considérée comme raisonnable car, quoi qu'il arrive , aucun des utilisateurs ne peut les gâcher pour un autre utilisateur . Cela est également vrai pour un singleton constant.
La deuxième exception, qui est moins mentionnée, est l'inverse - lorsque le singleton n'est qu'un récepteur de données , pas une source de données (directement ou indirectement). C'est pourquoi les bûcherons se sentent comme une utilisation "naturelle" des singletons. Comme les différents utilisateurs ne modifient pas les enregistreurs de la manière dont les autres utilisateurs se soucieront, il n'y a pas vraiment d'état partagé . Cela annule l'argument principal contre le modèle singleton et en fait un choix raisonnable en raison de leur facilité d'utilisation pour la tâche.
Voici une citation de http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :
Maintenant, il y a un type de Singleton qui est OK. C'est un singleton où tous les objets accessibles sont immuables. Si tous les objets sont immuables, Singleton n'a pas d'état global, car tout est constant. Mais il est si facile de transformer ce type de singleton en mutable, c'est une pente très glissante. Par conséquent, je suis contre ces Singletons aussi, non pas parce qu'ils sont mauvais, mais parce qu'il est très facile pour eux de se dégrader. (En guise de remarque, l'énumération Java n'est que ce genre de singletons. Tant que vous ne mettez pas d'état dans votre énumération, vous êtes OK, alors ne le faites pas.)
Les autres types de singletons, qui sont semi-acceptables sont ceux qui n'affectent pas l'exécution de votre code, ils n'ont aucun "effet secondaire". La journalisation en est un parfait exemple. Il est chargé de singletons et d'état global. C'est acceptable (car cela ne vous fera pas de mal) car votre application ne se comporte pas différemment, qu'un enregistreur donné soit activé ou non. Les informations ici circulent dans un sens: de votre application vers l'enregistreur. Même si les enregistreurs sont un état global car aucune information ne circule des enregistreurs vers votre application, les enregistreurs sont acceptables. Vous devez toujours injecter votre enregistreur si vous voulez que votre test confirme que quelque chose est en cours d'enregistrement, mais en général, les enregistreurs ne sont pas nocifs malgré leur état.
foo.x
ou si vous insistez à laFoo.x
place deFoo().x
); utilisez les attributs de classe et les méthodes statiques / de classe (Foo.x
).