Maison > développement back-end > Tutoriel Python > Quelle est la meilleure façon d'implémenter un singleton en Python ?

Quelle est la meilleure façon d'implémenter un singleton en Python ?

Susan Sarandon
Libérer: 2024-12-17 16:07:09
original
291 Les gens l'ont consulté

What is the best way to implement a singleton in Python?

La meilleure façon d'implémenter un singleton en Python

Bien que les avantages et les inconvénients du modèle de conception singleton ne soient pas au centre de cet article, cet article explorera comment pour implémenter le singleton en Python de la meilleure façon possible. Implémentez ce modèle de manière pythonique. Ici, « le plus pythonique » signifie suivre le « principe de la moindre surprise ».

Méthode de mise en œuvre

Méthode 1 : 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
Copier après la connexion
Copier après la connexion
Copier après la connexion

Avantages :

  • Le décorateur a du sexe supplémentaire, plus intuitif que l’héritage multiple.

Inconvénients :

  • L'objet créé à l'aide de MyClass() est un véritable objet singleton, mais MyClass lui-même est une fonction, pas une classe, les méthodes de classe ne peuvent donc pas être appelées.

Méthode 2 : 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
Copier après la connexion
Copier après la connexion

Avantages :

  • C'est une vraie classe.

Inconvénients :

  • Héritage multiple, désagréable. Lors de l'héritage d'une deuxième classe de base, __new__ peut être remplacé.

Méthode 3 : 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
Copier après la connexion

Avantages :

  • C'est une vraie classe.
  • Les successions sont automatiquement couvertes.
  • Utilisez __metaclass__ correctement (et faites-moi le comprendre).

Inconvénients :

  • Aucun inconvénient.

Méthode 4 : Renvoyez le décorateur de la 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.__name__ = class_.__name__
    return class_w

@singleton
class MyClass(BaseClass):
    pass
Copier après la connexion

Avantages :

  • C'est une vraie classe.
  • Les successions sont automatiquement couvertes.

Inconvénients :

  • Y a-t-il une surcharge liée à la création de deux classes pour chaque classe pour laquelle vous souhaitez devenir un singleton ? Bien que cela fonctionne bien dans mon cas, je crains que cela ne soit pas évolutif. Quel est le but de l'attribut
  • _sealed ?
  • Vous ne pouvez pas utiliser super() pour appeler des méthodes portant le même nom dans une classe de base car elles seraient récursives. Cela signifie que __new__ ne peut pas être personnalisé, et qu'une classe qui nécessite l'appel de __init__ ne peut pas non plus être sous-classée.

Méthode 5 : Module

Module Singleton singleton.py.

Avantages :

  • Mieux vaut simple que compliqué.

Inconvénients :

  • Pas d'instanciation différée.

Méthode recommandée

Je recommande d'utiliser la Méthode 2, mais il est préférable d'utiliser des métaclasses au lieu de classes 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
Copier après la connexion

Ou en Python3 :

class Logger(metaclass=Singleton):
    pass
Copier après la connexion

Si vous souhaitez que __init__ s'exécute à chaque fois qu'une classe est appelée, ajoutez le code suivant à Singleton.__call__ In l'instruction if :

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
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le rôle de la métaclasse

Une métaclasse est une classe de classes, c'est-à-dire qu'une classe est une instance de sa métaclasse. La métaclasse d'un objet en Python peut être trouvée via type(obj). Les nouvelles classes normales sont de type type. Le Logger ci-dessus sera de type class 'your_module.Singleton', tout comme la (seule) instance de Logger sera de type class 'your_module.Logger' . Lorsqu'un enregistreur est appelé à l'aide de Logger(), Python demande d'abord à la métaclasse Singleton du Logger ce qu'il doit faire, permettant la création d'instances préemptives. Le processus est similaire à la façon dont Python demande à une classe ce qu'elle doit faire avec ses attributs en appelant __getattr__, et vous référencez ses attributs en faisant myclass.attribute.

Les métaclasses déterminent essentiellement ce que signifie la classe appelante et comment implémenter cette signification. Voir par exemple http://code.activestate.com/recipes/498149/ qui utilise des métaclasses pour recréer essentiellement des structures de style C en Python. Sujet de discussion [Quels sont les cas d'utilisation spécifiques des métaclasses ? ](https://codereview.stackexchange.com/questions/82786/what-are-some-concrete-use-cases-for-metaclasses) fournit également quelques exemples, qui sont généralement liés à la programmation déclarative, en particulier dans l'ORM utilisé dans .

Dans ce cas, si vous utilisez votre Méthode 2 et qu'une sous-classe définit une méthode __new__, elle sera exécutée à chaque fois que SubClassOfSingleton() est appelée, car elle est responsable de l'appel des méthodes qui renvoyer les instances stockées. Avec les métaclasses, elle n'est exécutée qu'une seule fois, lors de la création de l'instance unique. Vous devez personnaliser la définition de la classe appelante, qui est déterminée par son type.

En général, il est logique d'utiliser des métaclasses pour implémenter des singletons. Un singleton est spécial car son instance n'est créée qu'une seule fois, tandis qu'une métaclasse est une implémentation personnalisée d'une classe créée qui la fait se comporter différemment d'une classe normale. L'utilisation d'une métaclasse vous donne plus de contrôle alors que vous auriez autrement besoin de personnaliser la définition de votre classe singleton.

Bien sûr

Votre singleton n'a pas besoin d'héritage multiple (car la métaclasse n'est pas une classe de base), mais pour que l'héritage crée une sous-classe d'une classe, vous devez vous assurer que le singleton la classe est la première/La métaclasse la plus à gauche redéfinit __call__. Il est peu probable que cela pose un problème. Le dictionnaire d'instance ne se trouve pas dans l'espace de noms de l'instance, il ne peut donc pas être écrasé accidentellement.

Vous entendrez également que le modèle singleton viole le « principe de responsabilité unique », ce qui signifie que chaque classe ne doit faire qu'une seule chose. De cette façon, vous n'avez pas à vous soucier de casser une chose que fait le code lorsque vous devez en modifier un autre, car ils sont indépendants et encapsulés. L'implémentation de la métaclasse réussit ce test. Les métaclasses sont chargées d'appliquer le modèle, en créant des classes et des sous-classes qui n'ont pas besoin de savoir qu'elles sont des singletons. La méthode 1 échoue à ce test, comme vous l'avez souligné avec "MyClass elle-même est une fonction, pas une classe, donc les méthodes de classe ne peuvent pas être appelées".

Versions compatibles Python 2 et 3

Écrire du code en Python 2 et 3 nécessite un schéma légèrement plus compliqué. Étant donné que les métaclasses sont généralement des sous-classes de la classe de type, vous pouvez utiliser une métaclasse pour créer dynamiquement une classe de base intermédiaire avec elle comme métaclasse au moment de l'exécution, puis utiliser cette classe de base comme classe de base pour une classe de base singleton publique. C'est plus facile à dire qu'à faire, comme suit :

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
Copier après la connexion
Copier après la connexion
Copier après la connexion

L'ironie de cette approche est qu'elle utilise des sous-classes pour implémenter des métaclasses. Un avantage possible est que, contrairement à une métaclasse pure, isinstance(inst, Singleton) renverra True.

Correction

Concernant un autre sujet, vous l'avez peut-être remarqué, mais l'implémentation de la classe de base dans votre message d'origine était erronée. Pour référencer des _instances dans une classe, vous devez utiliser super() ou une méthode statique de la méthode de classe, car la classe réelle n'a pas encore été créée au moment de l'appel. Tout cela est également vrai pour les implémentations de métaclasses.

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
Copier après la connexion
Copier après la connexion

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal