Partage
  • Partager sur Facebook
  • Partager sur Twitter

fonctionnement des décorateurs: Python

    24 juin 2018 à 21:55:51

    Bonsoir,

    je suis débutant en python et actuellement je suis entrain de suivre la formation:Apprenez à programmer en python qui , d'ailleurs est une trés bonne formation mais j'ai un petit souci avec les décorateurs; voici le probléme:

    pour créer une classe singleton, le code comme vous devez le savoir est le suivant ( j'ai copié le code donné dans la formation) :

    def singleton(classe_definie):

        instances = {} # Dictionnaire de nos instances singletons

        def get_instance():

            if classe_definie not in instances:

                # On crée notre premier objet de classe_definie

                instances[classe_definie] = classe_definie()

                print(instances)  # j'ai rajouté cette ligne pour voir le contenu du dictionnaire

            return instances[classe_definie]

        return get_instance


    ensuite on fait appel au décorateur:

    @singleton

    class Test:

       pass

    a=Test()

    jusque la y'a pas de probléme et on arrive a obtenir une seule instance d'une classe, mnt si je définis une autre classe Test2 par exemple qui fait appel au décorateur,  quand je crée une instance de test2, notre dictionnaire ne contient plus que l'instance de Test2, ce qui est normal puisque une fois le décorateur exécuté toutes les variables sont détruites, le probléme c'est que si j'essaie de créer une nouvelle instance de Test et bien ca renvoie la premiere instance qu'on a créé cad a , ma question est comment le décorateur sait que une instance Test a deja eté créé? 

    • Partager sur Facebook
    • Partager sur Twitter
      25 juin 2018 à 0:07:47

      Bonjour,

      Mauvais forum

      Le sujet est déplacé de la section Discussions générales vers la section Langage Python

      Merci d'utiliser la mise en forme de code Image

      Les forums d'Openclassrooms disposent d'une fonctionnalité permettant de colorer et mettre en forme les codes source afin de les rendre plus lisibles et faciles à manipuler par les intervenants. Pour cela, il faut utiliser le bouton Image de l'éditeur, choisir un des langages proposés et coller votre code dans la zone prévue. Si vous utilisez l'éditeur de messages en mode Markdown, il faut utiliser les balises <pre class="brush: c;">Votre code ici</pre>.

      • Partager sur Facebook
      • Partager sur Twitter
        25 juin 2018 à 0:17:17

        Bonjour,

        En effet cet exemple est encore une fois pas terrible, le dictionnaire ne contiendra jamais plus d'un élément.

        Ce qui fait que le décorateur fonctionne, c'est le mécanisme de portée des variables en Python. Tu dis que toutes les variables sont détruites une fois le code exécuté mais ce n'est qu'en partie vrai. Les variables existent tant qu'elles sont référencées.

        Ici, une fois l'application du décorateur, une classe Test devient égale à singleton(Test), c'est à dire égale à la fonction get_instance créée dans singleton. Le mécanisme de fermeture fait que les variables définies dans singleton soient alors accessibles dans get_instance, ce qui est le cas de instances.

        Donc en fait, la portée d'instances est liée à celle de get_instance, renvoyée par le décorateur. Et comme c'est Test qui est égal à get_instance, instances existe tant que Test existe, donc aucun soucis.

        L'autre question que tu pourrais te poser, c'est pourquoi instances est différente pour chaque classe. Et là encore c'est la portée et le principe de fermeture qui l'expliquent : un dictionnaire instances est créé à chaque application du décorateur, et c'est ce nouveau dictionnaire et uniquement lui qui sera lié à la fonction get_instance.

        Mais on rentre là dans des détails avancés du langage, qui peuvent être assez difficiles à comprendre.

        • Partager sur Facebook
        • Partager sur Twitter
          25 juin 2018 à 1:36:43

          Merci pour votre réponse, ne craignez pas de rentrer dans des détails compliqués, je comprend trés vite et ça peut m'éclairer sur des notions que je ne connais pas encore, d'ailleurs je ne connaissais pas la notions des closures et j'étais faire un tour du coté de wikipédia et la ... je n'ai toujours rien compris ( comme d'habitude avec wikipedia, je ne comprend jamais leurs articles), aprés j'ai fais une recherche sur le net et j'ai trouvé un trés bon article qui explique la notion des closures ce qui m'a permis de mieux comprendre pourquoi python se rappelle de la valeur du dictionnaire.

          pour la 2éme question ( pourquoi instances est différent pour chaque classe)  je me suis posé aussi cette question mais je voulais poser une question à la fois, en effet le dictionnaire ne contient qu'une seule valeur à la fois, et c'est celle de la dernière instance,  si par exemple je crée une instance d'une classe a , instances contient la classe a , ensuite si je crée une instance d'une classe b, instances ne contient plus que la classe b, le problème c'est si après je crée encore une instance de la classe a et bien python se rappelle toujours qu'une 1 ére instance de a a eté crée et donc la nouvelle instance aura la meme référence que la 1ére. comment est ce possible.

          -
          Edité par newgateport 25 juin 2018 à 7:30:09

          • Partager sur Facebook
          • Partager sur Twitter
            25 juin 2018 à 9:01:58

            newgateport a écrit: > en effet le dictionnaire ne contient qu'une seule valeur à la fois, et c'est celle de la dernière instance, si par exemple je crée une instance d'une classe a , instances contient la classe a , ensuite si je crée une instance d'une classe b, instances ne contient plus que la classe b, le problème c'est si après je crée encore une instance de la classe a et bien python se rappelle toujours qu'une 1 ére instance de a a eté crée et donc la nouvelle instance aura la meme référence que la 1ére. comment est ce possible.

            C'est bien ce que j'essayais d'expliquer, et c'est pourquoi je dis que la compréhension de ce mécanisme n'est pas évidente.

            Non, instances n'est pas égal au dictionnaire de la dernière instance : il ya plusieurs dictionnaires instances (un par classe) et tous coexistent. Mais les décorateurs (et le mauvais exemple de ce cours) sont probablement une difficulté supllémentaire à la comprhénesion des fermetures.

            Repartons sur un cas plus simple, celui d'une fonction d'addition.

            def add_with(a):
                def addition(x):
                    return a + x
                return addition
            

            Nous avons ici une fonction add_with qui prend un paramètre numérique et retourne une fonction d'addition. Nous pouvons créer quelques fonctions ainsi : add_3 = add_with(3), add_5 = add_with(5). Chacune sera une sous-fonction addition différente, dans l'environnement de variables (le binding) sera lui aussi différent.

            Dans add_3, le contexte/environnement/fermeture fera que a = 3, alors que dans add_5 on aura a = 5. Il n'est pas question d'antériorité de l'une par rapport à l'autre, les deux coexistent, et d'ailleurs a n'existe pas en dehors de ces fonctions d'addition, les valeurs de a n'existent que par et pour ces fonctions.

            • Partager sur Facebook
            • Partager sur Twitter
              25 juin 2018 à 11:43:33

              Merci Entwanne, votre dernier exemple m'a eclaircis bien des choses, en gros si j'ai bien compris python crée un espace mémoire ou il va piéger la valeur de instances pour chaque classe qui appelle le décorateur, et ces espaces sont indépendants les uns des autres. mnt je me pose une autre question, imaginons qu'il y'ait une autre variable en plus de instances et qu'on veuille que toutes les classes qui font appel au décorateur partagent cette variable, est ce que le mot clé global résoudrait le problème ou est ce que c'est pas possible à l'interieur du déocrateur? 

              j'ai vu aussi que vous avez rédigé vous même une formation sur python que je trouve trés intéressante (surtout que vous abordez des notions avancés de python), j'irai voir une fois que je termine la formation actuelle.

              et si c'est pas trop demander quel serait votre suggestion pour un décorateur singleton?

              -
              Edité par newgateport 25 juin 2018 à 11:47:58

              • Partager sur Facebook
              • Partager sur Twitter
                25 juin 2018 à 13:03:28

                Une variable globale permettra en effet ce comportement, mais le mot-clé global n'y est pas nécessaire. Simplement déplacer la définition instances = {} en dehors de la fonction singleton et ne rien changer au reste fonctionnera correctement.

                Ma suggestion serait de ne pas faire de décorateur singleton, qui est rarement un bon pattern. Si je devais le refaire, je reprendrai une fonction du même genre mais définissant instance = None plutôt que instances = {}.

                • Partager sur Facebook
                • Partager sur Twitter
                  25 juin 2018 à 14:24:52

                  j'ai essayé de refaire l'exemple en y incluant vos recommandations comme ceci:

                  def singleton(classe_definie):
                  
                  	instance = None
                  	
                  	def get_instance():
                  		if not instance:
                  
                  			instance = classe_definie()
                  			print(instance)  
                  		return instance
                  	return get_instance
                  
                  
                  @singleton
                  class test:
                  	pass
                  
                  a=test()

                  mais ca m'affiche une erreur: UnboundLocalError: local variable 'instance' referenced before assignment

                  je ne comprends pas du tout d'ou vient l'erreur, car instance est bien assigné à None.

                  -
                  Edité par newgateport 25 juin 2018 à 14:25:05

                  • Partager sur Facebook
                  • Partager sur Twitter
                    25 juin 2018 à 15:23:05

                    entwanne a écrit:

                    Une variable globale permettra en effet ce comportement, mais le mot-clé global n'y est pas nécessaire. Simplement déplacer la définition instances = {} en dehors de la fonction singleton et ne rien changer au reste fonctionnera correctement.


                    Ce n'est pas ce que tu as fait.
                    • Partager sur Facebook
                    • Partager sur Twitter
                      25 juin 2018 à 15:49:40

                      C'est normal, et encore une fois cela découle des règles de scoping de Python.

                      Quand Python lit une définition de variable (toto = ...), il la prend automatiquement comme une déclaration, considérant alors que la variable est déclarée dans le scope courant, c'est ce qui se passe ligne 8 dans ton code : une variable instance est créée dans le scope de la fonction get_instance, prenant le pas sur celle de la fonction singleton.

                      Et une autre règle de scoping veut qu'une variable ne puisse pas être utilisée en lecture avant d'avoir été définie (ça paraît logique, vu qu'elle n'a encore aucune valeur cela poserait un problème). C'est aussi ce qui se passe ici : tu essaies d'accéder ligne 6 à une variable qui n'a pas encore été définie dans ce scope.

                      Là on peut se poser deux questions :

                      • Pourquoi cela fonctionnait avec instances dans le premier exemple ? Parce que instances n'était jamais redéfinie/redéclarée dans la fonction get_instance, donc tous les accès à la variable depuis le scope de get_instance faisaient référence à la variable de singleton.

                      • Comment faire pour modifier une variable d'un scope extérieur ? Car c'est ce que l'on veut, redéfinir une variable appartenant à un scope parent. Pour cela il faut dissocier la déclaration de variable de sa définition. Tout en haut de la fonction get_instance, on va définir la variable instance avec l'instruction nonlocal instance. Cela indique à Python qu'une variable instance est déclarée dans un scope parent, et qu'on veut la redéfinir dans le courant. Le mot-clé global fonctionne de manière semblable pour permettre de redéfinir les variables déclarées dans le scope global du module (mais souvent ce mot-clé qui fait intervenir des aspects compliqués du langage n'est jamais correctement expliqué).

                      thetui a écrit: > Ce n'est pas ce que tu as fait.

                      En effet, il a plutôt appliqué la solution que je conseillais.

                      • Partager sur Facebook
                      • Partager sur Twitter
                        25 juin 2018 à 16:49:54

                        d'abord ça fonctionne à merveille  avec le mot clé nonlocal.

                        Comme a dit entwanne , je voulais appliquer sa solution qui m'a semblé plus propre et direct, et je voulais éviter d'utiliser le mot clé global car je croyais que instance était accessible depuis get_instance étant donné qu'elle appartenait au scope parent( mnt je sais qu'elle est accessible uniquement en lecture et pas en écriture) et que si j'utilise global elle serait accessible de partout ce que je veux pas.

                        2émement cela fait pas mal de notions que j'ai appris ces derniers 24 h.

                        enfin, un grand merci Entwanne, c'est grâce aux gens comme toi que des débutants comme moi arrivent à progresser.

                        • Partager sur Facebook
                        • Partager sur Twitter
                          25 juin 2018 à 17:01:37

                          Et ça me fait penser qu'il faudrait écrire quelque chose là-dessus, car ce sont des problèmes qui reviennent fréquemment auxquels il n'y a pas de réponse évidente à apporter.

                          • Partager sur Facebook
                          • Partager sur Twitter
                            25 juin 2018 à 17:16:43

                            et beh écoute c'est que du plaisir pour moi personnellement, les notions comme closures, décorateurs et génerateurs  méritent une attention particuliére (en meme temps je ne suis qu'au début), par exemple la formation que je suis actuellement entrain de suivre éxplique bien ce que c'est que les générateurs , mais je ne me suis rendu compte de l'utilité  des générateurs qu'en allant sur des sites anglais ou la j'ai vu toute leur puissance  ( pour faire du multithreading par exemple).
                            • Partager sur Facebook
                            • Partager sur Twitter
                              28 février 2019 à 14:05:53

                              entwanne a écrit: > Et ça me fait penser qu'il faudrait écrire quelque chose là-dessus, car ce sont des problèmes qui reviennent fréquemment auxquels il n'y a pas de réponse évidente à apporter.

                              Bon, désolé pour le up, mais 8 mois plus tard voici le "quelque chose" en question : https://zestedesavoir.com/billets/2648/variables-scopes-et-closures-en-python/

                              • Partager sur Facebook
                              • Partager sur Twitter
                                19 novembre 2019 à 13:07:07

                                entwanne a écrit:

                                entwanne a écrit: > Et ça me fait penser qu'il faudrait écrire quelque chose là-dessus, car ce sont des problèmes qui reviennent fréquemment auxquels il n'y a pas de réponse évidente à apporter.

                                Bon, désolé pour le up, mais 8 mois plus tard voici le "quelque chose" en question : https://zestedesavoir.com/billets/2648/variables-scopes-et-closures-en-python/


                                Un grand merci @entwanne pour le temps passé à rediger ce cours https://zestedesavoir.com/tutoriels/3163/variables-scopes-et-closures-en-python/ super clair et qui m'apporte enfin une possibilité de comprendre les mécanismes à l'oeuvre, et entre autre où est stocké le dictionnaire "instances" (ou "cache" dans ton exemple) et comment y accéder (à des fins pédagogiques de compréhension).

                                Cela faisait des heures que je cherchais une explication claire, et tu as raison de souligner dans ton post que ce sujet n'est pas couramment abordé sur le net de manière satisfaisante. Merci de l'avoir fait!

                                -
                                Edité par P_A Heurtebize 19 novembre 2019 à 13:07:50

                                • Partager sur Facebook
                                • Partager sur Twitter

                                fonctionnement des décorateurs: Python

                                × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                • Editeur
                                • Markdown