Partage
  • Partager sur Facebook
  • Partager sur Twitter

méthode spéciale __del__

__del__

Sujet résolu
    8 août 2018 à 8:38:06

    Bonjour à toutes et à tous,

    en ce moment sur ce site, je suis le cours de python que je trouve absolument génial et que je recommande (https://openclassrooms.com/fr/courses/235344-apprenez-a-programmer-en-python). Récemment en ayant lu le chapitre sur les méthodes spéciales, je me suis dit qu'il serait pas mal d'écrire une classe qui reprendrait une grande partie des méthodes spéciales du cours, histoire de bien se les mettre dans la tête ;) . Pour cela j'ai décidé de reprendre l'exemple des durées exposé encore une fois dans le cours et de l'étoffer un petit peu :

    import os
    
    class Duree :
        """classe permettant de modéliser l'heure sous la forme : "MM:SS" """
    
        def __init__(self, min = 0, sec = 0) :
            """constructeur de classe"""
            self.min = min
            self.sec = sec
            self.renombrer(self.denombrer())    # verifie la cohérence
            
        def __repr__(self) :
            """affichage de la classe"""
            return "{:02}:{:02}".format(self.min, self.sec)     # affichage MM:SS
        
        def __del__(self) :
            """suppression à la fin de l'execution ou quand l'utilisateur écrit del objet_duree"""
            print("Suppression de l'objet... D:")
        
        # opérations -----
        
        def __add__(self, valeur) :
            """addition : self + valeur"""
            renvoi = Duree(self.min, self.sec)  # copie
            duree_sec = renvoi.denombrer() + valeur # durée en secondes avec la valeur
            renvoi.renombrer(duree_sec) # reconversion en durée MM:SS
            return renvoi
        
        def __radd__(self, valeur) :
            """addition : valeur + self"""
            return self + valeur    # appelle __add__
        
        def __sub__(self, valeur) :
            """soustraction : self - valeur"""
            return self + (-valeur) # appelle __add__
        
        def __rsub__(self, valeur) :
            """soustraction : valeur - self"""
            renvoi = Duree(self.min, self.sec)  # même principe que __add__
            duree_sec = valeur - renvoi.denombrer()
            renvoi.renombrer(duree_sec)
            return renvoi
        
        def __mul__(self, valeur) :
            """multiplication : self * valeur"""
            renvoi = Duree(self.min, self.sec) # même principe que __add__
            duree_sec = renvoi.denombrer() * valeur
            renvoi.renombrer(duree_sec)
            return renvoi
        
        def __rmul__(self, valeur) :
            """multiplication : valeur * self"""
            return self * valeur    # appelle __mul__
        
        # comparaisons
        
        def __eq__(self, objet) :
            """test : self == objet"""
            try :
                return self.denombrer() == objet.denombrer()
            except AttributeError :
                return "Vous ne pouvez comparer que deux durées entre eux !"
    
        def __gt__(self, objet) :
            """test : self > objet"""
            try :
                return self.denombrer() > objet.denombrer()
            except AttributeError :
                return "Vous ne pouvez comparer que deux durées entre eux !"
        
        def __ge__(self, objet) :
            """test : self >= objet"""
            try :
                return self.denombrer() >= objet.denombrer()
            except AttributeError :
                return "Vous ne pouvez comparer que deux durées entre eux !"
        
        # méthodes -----
        
        def denombrer(self) :
            """renvoi la durée en secondes uniquement : renvoi un entier"""
            return self.sec + self.min * 60 # total en secondes
        
        def renombrer(self, sec) :
            """prend en paramètres une durée uniquement en secondes. self prendra la valeur
            de la durée en minutes et secondes en fonction du paramètre."""
            sec = int(sec)  # pour eviter le bazar
            if sec >= 0 :
                if sec >= 60 :
                    self.min = sec // 60
                    self.sec = sec % 60
                else :
                    self.min = 0
                    self.sec = sec
            else :
                if sec <= -60 :
                    self.min = -(sec // -60)    # if x < 0 : -(x // -60) == nombre de minutes pour x secondes
                    self.sec = sec % -60
                else :
                    self.min = 0
                    self.sec = sec
                    
                    
                    
                    
                    
                    
    # execution
    if __name__ == '__main__' :
        
        d1 = Duree(3)
        print(d1)
        print(d1 + 1)
        d1 += 1
        d1 = d1 + 1
        print(d1)
        del d1
        
        os.system("pause")

    Bon, tout va bien pou l'instant :lol: . En exécutant le programme j'obtiens cela :

    03:00
    03:01
    Suppression de l'objet... D:
    Suppression de l'objet... D:
    Suppression de l'objet... D:
    03:02
    Suppression de l'objet... D:
    Appuyez sur une touche pour continuer...

    En effet, j'ai dans la classe défini une méthode spéciale __del__ qui affiche un message de suppression quand un objet de type durée est détruit. Mon objectif était d'afficher ce message quand l'utilisateur de la classe tapait explicitement "del objet_duree" ou quand le programme se fermait et que l'objet n'a pas encore été supprimé. Il y a visiblement quelque-chose qui ne va pas car dans la partie exécution après la classe je n'ai tapé qu'une seule fois "del d1" alors que la console affiche 4 fois le message :( . En examinant un peu, je me suis rendu compte que l'erreur venait des méthodes spéciales permettant la surcharge des opérateurs mathématiques : en effet dans __add__ par exemple je renvoi un nouvel objet Duree, mais cet objet va ensuite être détruit (eh oui, l'espace de noms contenant l'objet est détruit :'( ). Vu que je fais des opérations 3 fois et que j'appelle 3 fois __add__ le message de suppression s'affiche 3 fois en trop.

    La question est :

    Comment dans ce cas faire en sorte que la méthode __del__ ne soit appelée que quand l'on écrit

    "del objet_duree" ou quand le programme touche à sa fin ?


    Pour y remédier, j'ai essayé de rajouter un attribut supplémentaire dans le constructeur qui serait True par défaut :

    def __init__(self, min = 0, sec = 0, msg_suppression = True) :
            """constructeur de classe"""
            self.min = min
            self.sec = sec
            self.renombrer(self.denombrer())    # verifie la cohérence
            self.msg_suppression = msg_suppression

    Dans la méthode __del__, j'ai rajouté une condition :

    def __del__(self) :
            """suppression à la fin de l'execution"""
            if self.msg_suppression is True :
                print("Suppression de l'objet... D:")

    Enfin j'ai rajouté dans les méthodes __add__, __rsub__ et __mul__ False dans le constructeur :

    def __add__(self, valeur) :
            """addition : self + valeur"""
            renvoi = Duree(self.min, self.sec, False)  # copie
            duree_sec = renvoi.denombrer() + valeur # durée en secondes avec la valeur
            renvoi.renombrer(duree_sec) # reconversion en durée MM:SS
            return renvoi

    En exécutant j'ai le droit à ça :

    03:00
    03:01
    Suppression de l'objet... D:
    03:02
    Appuyez sur une touche pour continuer...

    Il n'y a qu'un seul message de suppression sauf qu'il n'est pas au bon endroit : il ne s'affiche pas quand il faut. Il devrait s’afficher après "03:02" car ce n'est qu'après l'affichage de ce "03:02" que j'ai écrit "del d1". En examinant un peu j'ai vu que l'objet Duree crée dans __add__ où son attribut vaut False est transmis à l'objet d1 quand il s'agit d'une affectation : "d1 = d1 + 1" par exemple. Au moment de l'affectation, l'objet contenu dans d1 est détruit et est remplacé par celui renvoyé par __add__ qui contient donc un attribut False.

    Cette tentative de résolution n'est qu'une parmi tant d'autres toutes vaines que je juge trop long d'exposer ici (à moins que vous le vouliez). J'en conviens, ce que j'essaye d'accomplir n'a, je pense, aucune utilité, mais j'aimerais tout de même y arriver car je pense que c'est en résolvant quelques petits problèmes par ci par là qu'on devient réellement bon dans ce domaine.

    Merci donc pour toute aide éventuelle.








    -
    Edité par BronislawAbadie 8 août 2018 à 8:43:46

    • Partager sur Facebook
    • Partager sur Twitter
    Anonyme
      8 août 2018 à 9:21:24

      Quand tu fais tes opérations mathématiques entre objets, tu crées un nouvel objet, hors dans ton code principal, tu écrases ta variable d1, tu supprimes donc le précédent objet existant. Pour bien faire, il aurait fallu donner à ce nouvel objet, un nom de variable différent.
      • Partager sur Facebook
      • Partager sur Twitter
        8 août 2018 à 10:38:06

        Merci pour ta réponse.


        oldProgrammer a écrit:

        Quand tu fais tes opérations mathématiques entre objets, tu crées un nouvel objet, hors dans ton code principal, tu écrases ta variable d1, tu supprimes donc le précédent objet existant. Pour bien faire, il aurait fallu donner à ce nouvel objet, un nom de variable différent.

        Il faudrait donc écrire d2 = d1 + 1 si j'ai bien compris (en plus de rajouter __iadd__ pour que d2 += 1 marche) ? Oui mais dans ce cas si je fais "del d2" Le message de suppression ne va pas s'afficher vu que d2.msg_suppression = False non ?

        # execution
        if __name__ == '__main__' :
            
            d1 = Duree(3)
            print(d1)
            print(d1 + 1)
            d2 = d1 + 1
            print(d2)
            print(d2.msg_suppression)
            del d2
            
            os.system("pause")
        03:00
        03:01
        03:01
        False
        Appuyez sur une touche pour continuer...




        -
        Edité par BronislawAbadie 8 août 2018 à 10:49:09

        • Partager sur Facebook
        • Partager sur Twitter
        Anonyme
          8 août 2018 à 11:03:32

          BronislawAbadie a écrit:

          Merci pour ta réponse.


          oldProgrammer a écrit:

          Quand tu fais tes opérations mathématiques entre objets, tu crées un nouvel objet, hors dans ton code principal, tu écrases ta variable d1, tu supprimes donc le précédent objet existant. Pour bien faire, il aurait fallu donner à ce nouvel objet, un nom de variable différent.

          Il faudrait donc écrire d2 = d1 + 1 si j'ai bien compris (en plus de rajouter __iadd__ pour que d2 += 1 marche) ? Oui mais dans ce cas si je fais "del d2" Le message de suppression ne va pas s'afficher vu que d2.msg_suppression = False non ?

          # execution
          if __name__ == '__main__' :
              
              d1 = Duree(3)
              print(d1)
              print(d1 + 1)
              d2 = d1 + 1
              print(d2)
              print(d2.msg_suppression)
              del d2
              
              os.system("pause")
          03:00
          03:01
          03:01
          False
          Appuyez sur une touche pour continuer...

          Pourquoi tu veux mettre cet attribut ? del(objet) devrait appeler dans les règles la méthode __del__ de l'objet créé, non ?

          D'ailleurs supprimer un objet est contre productif niveau performance, mieux vaut laisser le garbage collector faire son travail en fin de programme, la gestion mémoire ne devrait pas te préoccuper. Et si c'est le cas, alors il faut bien comprendre les méandres internes liées au langage (cpython)

          -
          Edité par Anonyme 8 août 2018 à 11:13:01

          • Partager sur Facebook
          • Partager sur Twitter
            9 août 2018 à 23:03:10

            Salut,

            C'est un sujet qui en surprend plus d'un au début. Le plus important pour commencer:

            del ne sert pas à supprimer un objet.

            C'est écrit grand, mais je ne crie pas, promis! ;) C'est just pour que ça choque, et que ça fasse réfléchir. Si del ne supprime pas un objet, alors à quoi ça sert?? o_O del sert à supprimer une référence vers un objet. Et ça c'est un point hyper important à comprendre.

            En Python, lorsque tu crées un objet, tu l'assignes bien souvent à une variable. d1 = Duree(3). L'objet créé a une référence qui est d1. Tu peux ajouter une autre référence par exemple en assignant une seconde variable à ce même objet : d1 = d2. En mémoire il n'y a pas 2 copies de ton objet. Il n'y a qu'une seule durée avec deux références. On dit parfois qu'on a collé 2 étiquettes sur le même objet. Un dernier exemple pour la route, on pourrait ajouter une troisième référence sur ce même objet en l'ajoutant dans une liste. durees = [d1]. Une fois de plus, on n'a pas copié l'objet dans la liste. La liste contient juste une référence vers l'objet.

            La méthode __del__ n'est invoquée que lorsque l'objet est supprimé. Mais quand l'objet sera-t-il supprimé? Il y a deux règles. La première est simple : un objet est supprimé lorsqu'il n'y a plus aucune référence vers l'objet. Mais comment peut-on supprimer une référence vers un objet? Il existe plusieurs manières. Tout d'abord si tu assignes une autre valeur (ou un autre objet) à une variable qui référençait ton objet, alors ce dernier aura une référence de moins. Pour reprendre l'exemple plus haut, si on écrit à présent d2 = "Message", python va créer une chaîne de caractère avec une référence (étiquette). Mais cette étiquette était déjà utilisée sur notre objet Duree. Alors python décolle l'étiquette de Duree et la colle sur la chaîne de caractères. Donc sur Duree il n'y a plus que deux références : d1 et la liste durees. On peut enlever la référence de la liste de diverses manières. Par exemple on pourrait appeler la méthode pop des listes. durees.pop(). Cette méthode enlève le dernier élément de la liste et nous le retourne. Hors on n'a pas écrit d3 = durees.pop(). Cette dernière version aurait transféré notre étiquette vers d3. Mais dans la première version, la référence est retournée mais assignée à rien du tout et donc disparait. Il ne nous reste plus que d1. On pourrait aussi assigner une autre valeur à d1 et notre objet Duree n'aurait plus aucune référence et disparaîtrait donc immédiatement. Sa méthode __del__ serait appelée alors qu'on n'a utilisé del nul part! Si tu écris del d1, tout ce que tu fais c'est décoller l'étiquette d1 de l'objet Duree. Si il restait une autre référence sur l'objet Duree, il ne serait pas supprimé.

            Une dernière manière de perdre des références est lorsque tu quittes un scope, comme quitter une fonction, une méthode, etc. Toutes les variables locales sont supprimées et donc les étiquettes qu'elles représentent sont décollées. Seulement si les objets référencés n'ont plus d'étiquettes, ils seront aussi supprimés.

            J'ai dit qu'il y avait deux règles pour la suppression d'un objet. L'autre est plus compliquée et pas vraiment nécessaire de connaitre dans un premier temps. Mais si tu es curieux, sache qu'il s'agit de détecter des cycles parmi des objets qui se référencent l'un l'autre. Mais laissons-ça de côté pour le moment.

            Tu comprendras à présent que quand tu écris print(d1 + 1), python va tout d'abord évaluer ce qu'il se trouve dans les parenthèses. L'addition va retourner un nouvel objet de type Duree qui sera utilisé par print. Mais une fois que print a terminé, il n'y aura plus de référence vers cet objet qui sera immédiatement détruit.

            Afin de mieux suivre ton programme, je te conseille d'écrire ta méthode __del__ de cette manière:

            def __del__(self):
                print("Suppression de l'objet {!r}.".format(self)

            Ainsi chaque objet supprimé va t'indiquer de quelle Duree il s'agit, pour peu que tu n'aies pas 2 objets avec la même durée. :)



            -
            Edité par Dan737 9 août 2018 à 23:07:52

            • Partager sur Facebook
            • Partager sur Twitter
              10 août 2018 à 7:41:36


              Merci beaucoup Dan737, tes explications sont très intéressantes. J'ai appris pas mal de choses grâce à toi :).

              Dan737 a écrit:

              Une dernière manière de perdre des références est lorsque tu quittes un scope, comme quitter une fonction, une méthode, etc. Toutes les variables locales sont supprimées et donc les étiquettes qu'elles représentent sont décollées. Seulement si les objets référencés n'ont plus d'étiquettes, ils seront aussi supprimés.

              Il n'y donc aucun moyen de ne pas afficher le message de suppression uniquement quand on quitte une fonction ? Ou est-ce maladroit ?
              • Partager sur Facebook
              • Partager sur Twitter
                10 août 2018 à 8:41:28

                Je n'ai pas compris ta question... L'objet ne sait que s'il est référencé ou pas, rien de plus. Que sa référence provienne du scope d'un fonction, d'un attribut de classe, d'une variable globale ou autre n'a pas d'importance. Il n'est pas possible de forcer la suppression d'un objet alors qu'il reste des références, sinon que faire lorsque le script manipule une de ces références restantes alors que l'objet est censé ne plus exister.

                Voici un petit programme pour expérimenter avec la destruction des objets. Je t'encourage à ajouter d'autre tests et à voir ce qu'il se passe. Mieux encore, essaie d'abord de trouver par toi-même ce qui devrait se passer et puis tu lances le programmes et tu compares ton attente avec le résultat.

                class Dummy:
                    """Classe pour tester les références."""
                    def __del__(self):
                        print("{!r} est supprimé".format(self))
                
                
                def fonction():
                    d1 = Dummy()
                    print("Création du Dummy {!r} dans la fonction.".format(d1))
                    d2 = Dummy()
                    print("Création du Dummy {!r} dans la fonction qui sera retourné.".format(d2))
                    return d2
                
                print("Appel de la fonction")
                d3 = fonction()
                print("Rien de local à la fonction ne survivra jusqu'ici.")
                print("Il n'y a plus qu'un objet Dummy avec une seule référence.")
                print("Ajoutons une référence...")
                d4 = d3
                print("Et à présent on supprime la première référence avec del")
                del d3
                print("Mais l'objet n'est pas supprimé puisqu'il nous reste une référence.")
                print("Et encore bien, sinon que devrais faire python avec la ligne suivante.")
                print("Objet toujours en vie {!r}".format(d4))
                print("Par contre, le nom (étiquette) d3 n'existe plus")
                try:
                    print("Objet toujours en vie {!r}".format(d3))
                except NameError as e:
                    print("d3 n'est plus reconnu. Voici le message reçu : {}".format(e.args[0]))
                print("On efface nous-même la dernière référence, et l'objet est immédiatement supprimé.")
                del d4
                print("On est arrivé à la fin.")



                • Partager sur Facebook
                • Partager sur Twitter
                  10 août 2018 à 11:05:56

                  Merci pour cette aide Dan737. Ma question en effet était en effet mal formulée mais surtout n'avait pas grande utilité (d’ailleurs j'ai eu la réponse à ma question dans ton message :lol:). Merci à tous de m'avoir aidé !
                  • Partager sur Facebook
                  • Partager sur Twitter

                  méthode spéciale __del__

                  × 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