Créer un conteneur DI avec Python

Créer un conteneur DI avec Python

DI Container est un système qui transmet automatiquement une instance du type approprié à l'argument du constructeur lors de la création d'une instance d'une classe. Vous pouvez facilement implémenter des modèles DI et exprimer du code faiblement couplé.

Si vous souhaitez en savoir plus sur les modèles DI, veuillez vous référer à un autre article. En fait, c'est très facile à faire, alors je ferai de mon mieux.

Je vais lier le référentiel terminé. Dépôt

DEMO

Enregistrez dans la classe DI que c'est la classe d'implémentation, ou que cette classe sera utilisée dans une seule tonne. Après cela, le flux consiste à demander à la classe DI de créer une instance de cette classe.

Regardez la démo de calmar.

class Animal:
    def __init__(self, kind: str, name: str):
        self.kind = kind
        self.name = name

    def __str__(self):
        return "I am {kind}. My name is {name}".format(kind=self.kind, name=self.name)


class Cat(Animal):
    def __init__(self, name: str = "Cathy"):
        super().__init__('Cat', name)


di = DI()
di.register(Animal, Cat)
print(di.resolve(Animal))
# > I am Cat. My name is Cathy.


class Person:
    def __init__(self, name: str, role: str):
        self.name = name
        self.role = role

    def __str__(self):
        return "I am {name}. My role is {role}.".format(name=self.name, role=self.role)

nukumizu = Person('Nukumizu', 'Actor')
di.register_singleton(Person, nukumizu)
print(di.resolve(Person))
# > I am Nukumizu. My role is Actor.

Vue d'ensemble

Les exigences minimales pour un conteneur DI sont les trois suivantes.

--Classe de conteneur pour enregistrer la classe ou le singleton correspondant

Jetons un œil au code réel dans l'ordre.

Container Commençons par la classe Container. Cette classe fournit les fonctionnalités suivantes:

Jetons un coup d'œil au code réel.

container.py


class DIContainer:
    concrete_table = {}
    singleton_table = {}

    def register(self, base_cls: type, concrete_cls: type):
        if not issubclass(concrete_cls, base_cls):
            raise TypeError('Concrete class is required {} not {}.'.format(base_cls, concrete_cls))
        self.concrete_table[base_cls] = concrete_cls

    def register_singleton(self, t: type, instance: object):
        if not isinstance(instance, t):
            raise TypeError('Instance type is required {} not {}.'.format(t, type(instance)))
        self.singleton_table[t] = instance

    def get(self, t: type):
        if self.is_registered_concrete(t):
            return self.concrete_table[t]
        return t

    def get_singleton(self, t: type):
        if self.is_registered_singleton(t):
            return self.singleton_table[t]

        raise KeyError('{} is not registered as singleton.'.format(t))

    def is_registered_singleton(self, t: type):
        return t in self.singleton_table.keys()

    def is_registered_concrete(self, t: type):
        return t in self.concrete_table.keys()

    def is_registered(self, t: type):
        return self.is_registered_concrete(t) or self.is_registered_singleton(t)

    def clear(self):
        self.concrete_table.clear()
        self.singleton_table.clear()

Puisque je ne m'inscris que honnêtement, je pense qu'il n'y a pas d'endroit pour expliquer en particulier. Je suis désolé si le code est difficile à lire.

Resolver Ensuite, regardons la classe Resolver. Cette classe fournit les fonctionnalités suivantes:

Il n'y en a que deux.

Jetons un coup d'œil au code réel.

resolver.py


from container import DIContainer


class Resolver:
    def __init__(self, container: DIContainer):
        self.container = container

    def resolve(self, cls: type):
        if self.container.is_registered_singleton(cls):
            return self.container.get_singleton(cls)
        cls = self.container.get(cls)
        init_args = self.resolve_init_args(cls)
        return cls(**init_args)

    def resolve_init_args(self, cls: type):
        init_args_annotations = get_init_args_annotations(cls)
        defaults = get_init_default_values(cls)
        result = {}
        args_count = len(init_args_annotations)
        for key, t in init_args_annotations.items():
            if self.container.is_registered(t) or len(defaults) < args_count:
                result[key] = self.resolve(t)
            else:
                result[key] = defaults[len(defaults) - args_count]
            args_count -= 1

        return result


def get_init_args_annotations(cls: type):
    if hasattr(cls.__init__, '__annotations__'):
        return cls.__init__.__annotations__
    return {}


def get_init_default_values(cls: type):
    if hasattr(cls.__init__, '__defaults__'):
        result = cls.__init__.__defaults__
        return [] if result is None else result
    return []

Je vais expliquer get_init_args_annotations en dehors de la classe. Tout ce dont vous avez besoin pour créer un DIContainer est l'information sur l'argument du constructeur. Cela ne commencera pas sans cela. La façon d'obtenir les informations du constructeur en python est annotations. Vous pouvez maintenant obtenir un tableau associatif avec le nom d'argument Key et le type de classe Value. Un exemple est présenté ci-dessous.

class Person:
    def __init__(self, name: str, age: int):
        pass
        
print(Person.__init__.__annotaions__)
# {'name': <class 'str'> , 'age': <class 'int'>}

Créez une instance de la classe basée sur les informations obtenues par \ __ annotations__.

Maintenant, vérifions le flux autour du processus de génération.

  1. Premièrement, si la classe cible est enregistrée dans Singleton, renvoyez simplement l'instance telle quelle.
  2. Ensuite, vérifiez si la classe d'implémentation de la classe cible est enregistrée dans Container.
  3. S'il est enregistré, assurez-vous de créer une instance de la classe d'implémentation.
  4. Si le constructeur de la classe à générer nécessite un argument, créez d'abord une instance de la classe d'argument. (Autrement dit, faites générer la classe d'arguments par la fonction de résolution.)
  5. Après avoir créé toutes les instances de la classe d'arguments avec priorité sur la profondeur, transmettez-la à l'argument de la classe cible et générez-la.

C'est comme générer des classes de manière récursive. Maintenant que les fonctions de conteneur DI sont terminées, vous pouvez l'utiliser en créant une façade.

di.py


from container import DIContainer
from resolver import Resolver


class DI:
    def __init__(self):
        self.container = DIContainer()
        self.resolver = Resolver(self.container)

    def register_singleton(self, t: type, instance: object):
        self.container.register_singleton(t, instance)

    def register(self, base_cls: type, concrete_cls: type):
        self.container.register(base_cls, concrete_cls)

    def resolve(self, t: type):
        return self.resolver.resolve(t)

    def clear(self):
        self.container.clear()

À ce stade, vous pouvez utiliser DI Container comme DEMO.

Résumé

Une fois que vous l'aurez compris, vous aurez moins de chances d'oublier comment l'utiliser. Connaissant le modèle DI, j'ai résolu quelques questions sur la gestion des problèmes liés à l'interface.

C'était incroyablement pratique de ne pas avoir à vérifier comment générer des classes une par une en introduisant DI Container dans notre propre bibliothèque sur le lieu de travail précédent. Bien sûr, il y a des inconvénients, mais c'est toujours pratique, je vais donc l'utiliser quand il pourra être utilisé.

Je vais lier le référentiel terminé. Dépôt

Je vous serais reconnaissant si vous pouviez me faire savoir s'il y a des lacunes.

Merci de rester avec nous jusqu'à la fin.

Recommended Posts

Créer un conteneur DI avec Python
Créer une fonction en Python
Créer un dictionnaire en Python
Créer un fichier binaire en Python
Créer une chaîne aléatoire en Python
Créer une application GUI simple en Python
[GPS] Créer un fichier kml avec Python
Créer un module Python
Créer Spatia Lite en Python
Créer un environnement Python
Créez un environnement de test Vim + Python en 1 minute
Créer un fichier GIF en utilisant Pillow en Python
Je veux créer une fenêtre avec Python
Créer un graphique de distribution normale standard en Python
Comment créer un fichier JSON en Python
Créer un environnement virtuel avec conda avec Python
Créer un modèle d'investissement dynamique simple en Python
Créer une nouvelle page en confluence avec Python
Créer un objet datetime à partir d'une chaîne en Python (Python 3.3)
Créer un package contenant des commandes globales en Python
Créez un fichier MIDI en Python en utilisant pretty_midi
Créer un modèle d'antenne cadre en Python dans KiCad
[Docker] Créez un environnement jupyterLab (python) en 3 minutes!
Prendre une capture d'écran en Python
Créer un plugin Wox (Python)
Créer un bookmarklet en Python
Créer un tableau numpy python
Dessinez un cœur en Python
Créer un répertoire avec python
DI (injection de dépendances) en Python
Créer un bot de collecte de données en Python à l'aide de Selenium
[API LINE Messaging] Créez un menu riche avec Python
Créer un plugin pour exécuter Python Doctest sur Vim (2)
Créez un plug-in pour exécuter Python Doctest avec Vim (1)
En Python, créez un décorateur qui accepte dynamiquement les arguments Créer un décorateur
Créez un faux serveur Minecraft en Python avec Quarry
Probablement dans un serpent Nishiki (Titre original: Peut-être en Python)
Ecrire une dichotomie en Python
[python] Gérer les fonctions dans une liste
Appuyez sur une commande en Python (Windows)
Créer un lecteur CSV avec Flask
Créer une portée locale en Python sans polluer l'espace de noms
Créer une interface graphique python à l'aide de tkinter
Créer un environnement Python sur Mac (2017/4)
Dessinez une matrice de diagramme de dispersion avec python
Créer un compte enfant de connect with Stripe en Python
Créons un script qui s'enregistre avec Ideone.com en Python.
ABC166 en Python A ~ C problème
Créez un environnement virtuel avec Python!
Ecrire des algorithmes A * (A-star) en Python
Créez Gmail en Python sans utiliser l'API
Résoudre ABC036 A ~ C avec Python
Créer un environnement python dans centos
Créez le code qui renvoie "A et prétendant B" en python
Ecrire un graphique à secteurs en Python
Ecrire le plugin vim en Python
Créer une documentation de projet Python dans Sphinx