logo le blog invivoo blanc

Singleton – Design Patterns – Partie 1

16 décembre 2021 | Python | 0 comments

1. Contexte

Tandis que la connexion vers une base de donnée est établie pour chercher les ordres du marché financier aux différentes périodes, nous espérons que cette connexion vers la même base serait réutilisée dans une autre recherche bientôt sans se refaire. Du coup, on n’instancie la classe de la connexion qu’une fois durant la vie du programme des recherches en économisant les ressources de mémoire et le temps de son rétablissement. L’accès de telle connexion passe par un point global.

Les patrons de conception (design patterns) sont classifiés en trois familles créatrices, structurales, comportementales. Pour gérer les mécanismes de création d’objets, on essaie de créer des objets d’une manière adaptée à la situation. La forme de base de la création d’objets peut entraîner des problèmes de conception ou une complexité accrue de la conception. La famille créatrice résout cette problématique en contrôlant d’une manière ou d’une autre la création de cet objet. Dans cet article pour répondre à notre contexte en question, on va aborder le 1e patron de conception (design pattern) pour la famille créatrice : singleton.

On réalise le singleton en écrivant une classe contenant une méthode qui crée une instance unique seulement s’il n’en existe pas encore. Sinon, elle renvoie une référence vers l’objet qui existe déjà. Singleton restreint l’instanciation d’une classe et garantit qu’une seule instance de la classe existe dans le programme.

Pour simplifier les conceptions, on n’aborde pas en ce moment les aspects sur les concurrences en multithreading ni multiprocessus qui seront expliquées et implémentées en Python 3 dans cet article Singleton – Partie 2.

2. Conception

Le diagramme ULM illustre l’idée du développement de la classe Singleton en Python 3. On statiquement créé une instance globale dans la classe, puis lors de l’appel du client sans concurrence, la même instance créée à la 1e fois sera fournie directement.

3. Développement

En Python, on a plusieurs manières à développer Singleton, alors qu’en général on pourrait profiter de la surcharge de la méthode statique __new__ définie dans la classe object. A la suite on va mettre en valeur les avantages et les inconvénients des trois implémentations en Python 3 : la classe de base, le décorateur et la métaclasse.

3.1. La classe de base

Cette façon repose sur l’héritage d’une classe de base Singleton. On détecte si un objet de la classe à créer est déjà instanciée dans une variable à la classe Singleton _instance. On retourne directement cet objet lors de son existence non nulle. Si non, on le créé avant qu’il ne retourne, tandis qu’il s’est mis dans la variable _instance durant la vie du programme.  

# baseclass.py
class Singleton(object):
    _instance = None
    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = super().__new__(class_, *args, **kwargs)
        return class_._instance


class Connexion(Singleton):
    pass

class ConnexionNonSingleton (object):
    pass


if __name__ == '__main__': 
    c1 = ConnexionNonSingleton ()
    c2 = ConnexionNonSingleton ()
    assert c1 != c2

    c1 = Connexion()
    c2 = Connexion()
    assert c1 == c2

Les tests unitaires se valident par des objets connexions qui représentent le singleton.

Inconvénients :

La méthode __new__ est possible d’être surchargée par les classes dérivées, ce qui rend moins flexibles les héritages multiples.

Avantages :

Une classe Singleton est évidemment instanciée sous le contrôle.

3.2. Le décorateur

Les décorateurs sont additifs d’une manière qui est souvent plus intuitive que l’héritage multiple. Néanmoins c’est une réalisation plus compliquée en Python 3, en définissant une classe interne class_w dans la fonction singleton à retourner.

Comme ce qu’on a conçu pour la classe de base précédemment, on utilise une variable _instance dans la classe class_w pour stocker l’objet singleton qui est créé dans la méthode __new__. En tant qu’un décorateur, la fonction singleton doit prendre en compte la classe Connexion comme l’argument d’entrée, et elle en dérivera par la classe interne class_w, qui retourne comme une classe singleton.

C’est la méthode de class_w __init__ justement après l’appel de __new__ où on décide si on a besoin ou non d’instancier l’objet singleton. On s’appuie sur l’indicateur sealed (scellé) à la décision, alors que cet indicateur initialement égal à False est mis en valeur True dès la 1e création de l’instance singleton. Dès que l’on arrive à englober la classe Connexion dans la classe class_w, on refait son nom de la classe __name__ avant de faire retourner la classe singleton.

# decorator.py
def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class_, *args, **kwargs):
            if not isinstance(class_w._instance, class_):
                class_w._instance = super().__new__(class_,
                                                    *args,
                                                    **kwargs)
                class_w._instance._sealed = False
            return class_w._instance

        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super().__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = class_.__name__
    return class_w

@singleton
class Connexion(object):
    pass

class ConnexionNonSingleton(object):
    pass


if __name__ == '__main__': 
    c1 = ConnexionNonSingleton ()
    c2 = ConnexionNonSingleton ()
    assert c1 != c2

    c1 = Connexion()
    c2 = Connexion()
    assert c1 == c2

Les tests unitaires se valident par des objets connexions qui représentent le singleton.

Inconvénients :

On implémente deux classes pour réaliser un singleton, ce qui augmente la complexité.

On ne peut plus personnaliser la méthode __new__ dans les classes dérivées dont la méthode __init__ doit être appelée pour valider son application.

Avantages :

On évidemment contrôle la création et l’accès de l’instance. L’utilisation du décorateur est concise et claire, surtout pour les héritages multiples.

3.3. La métaclasse

En particulier, Python 3 fournit la métaclasse « type » pour définir comment créer une classe singleton en base, ce qui résout le problème des héritages multiples marqué en implémentation par la classe de base. Les métaclasses sont parfois appelées usines de classes [1]. On surcharge la méthode __call__ dans cette métaclasse où on détermine l’instanciation de l’objet singleton.

# metaclass.py
class Singleton(type):
    _instance = None
    def __call__(cls, *args, **kwargs):
        if not isinstance(cls._instance, cls):
            cls._instance = super().__call__(*args, **kwargs)
        return cls._instance
 

class Connexion(metaclass=Singleton):
    pass

class ConnexionNonSingleton (object):
    pass


if __name__ == '__main__': 
    c1 = ConnexionNonSingleton ()
    c2 = ConnexionNonSingleton ()
    assert c1 != c2

    c1 = Connexion()
    c2 = Connexion()
    assert c1 == c2

Les tests unitaires se valident par des objets connexions qui représentent le singleton.

Inconvénients :

Non

Avantages :

Une classe Singleton est évidemment instanciée sous le contrôle.

A la base des trois façons de réaliser singleton en Python 3, on pourrait enrichir la définition de _instance pour adapter aux contextes plus compliqués expliqués dans “La mise en cache et ses utilisations en Python”.

4. Conclusion

Dans cet article, on commence par démontrer la nécessité du patron de conception singleton pour notre contexte en question, en évoquant les mécanismes de sa famille créatrice des patrons de conception (design patterns). Selon son diagramme ULM sans tenir compte les issues des concurrences, on propose trois manières basiques à implémenter et tester ce patron de conception créateur en Python 3, alors que les comparaisons de leurs avantages et leurs inconvénients sont mises en évidence enfin.

[1] https://docs.python.org/3.2/reference/datamodel.html#customizing-class-creation