IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

levée d'exception et compteur de références


Sujet :

Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre Expert Avatar de plxpy
    Homme Profil pro
    Ingénieur géographe
    Inscrit en
    Janvier 2009
    Messages
    792
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur géographe
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Janvier 2009
    Messages : 792
    Par défaut levée d'exception et compteur de références
    Bonjour

    j'obtiens un comportement que je trouve bizarre lors de la levée d'une exception dans une méthode de classe : l'appelant se retrouve avec une instance qui a un compteur de référence différent de 1 (un peu comme si, lors de la remontée des fonctions/méthodes jusqu'à trouver un traitement pour l'exception, le compteur de référence restait inchangé).

    Du coup, au niveau de l'appelant, un "del" de l'instance ne fait que décrémenter ce compteur sans que la méthode __del__ que j'ai définie soit exécutée.

    La classe (simplifiée !) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from sys import getrefcount as grc
     
    class Classe(object):
     
        def ooops(self):
            print "debut ooops :", grc(self)
            raise RuntimeError('exception provoquee')
     
        def methode(self,exception=False):
            print "debut methode :", grc(self)
            if exception: self.ooops()
            print "fin methode :", grc(self)
     
        def __del__(self):
            print "destructeur appelee :", grc(self)
            print "ici des choses a faire"
    A l'usage, sans levée d'exception :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    plx@sony:~/Bureau$ py
    Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56) 
    [GCC 4.4.3] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from grc_v1 import Classe
    >>> from sys import getrefcount as grc
    >>> i=Classe()
    >>> grc(i)
    2
    >>> i.methode()
    debut methode : 4
    fin methode : 4
    >>> grc(i)
    2
    >>> del i
    destructeur appelee : 5
    ici des choses a faire
    quand une exception est levée :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    plx@sony:~/Bureau$ py
    Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56) 
    [GCC 4.4.3] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from grc_v1 import Classe
    >>> from sys import getrefcount as grc
    >>> i=Classe()
    >>> grc(i)
    2
    >>> i.methode(True)
    debut methode : 4
    debut ooops : 6
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "grc_v1.py", line 11, in methode
        if exception: self.ooops()
      File "grc_v1.py", line 7, in ooops
        raise RuntimeError('exception provoquee')
    RuntimeError: exception provoquee
    >>> grc(i)
    4
    >>> del i
    >>> del i
    destructeur appelee : 5
    ici des choses a faire
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'i' is not defined
    >>>
    (le deuxième "del i" n'est évidemment pas licite mais les messages retournés sont intéressants)


    Par contre, dans le méthode "methode", si je traite l'exception, au final, au niveau de l'appelant, l'instance a un compteur qui vaut 1 et un del provoque bien l'exécution de la méthode __del__ :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
        def methode(self,exception=False):
            print "debut methode :", grc(self)
            if exception:
                try:
                    self.ooops()
                except:
                    print "ooops a leve une exception"
            print "fin methode :", grc(self
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56) 
    [GCC 4.4.3] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from grc_v2 import Classe
    >>> from sys import getrefcount as grc
    >>> i=Classe()
    >>> grc(i)
    2
    >>> i.methode(True)
    debut methode : 4
    debut ooops : 6
    ooops a leve une exception
    fin methode : 5
    >>> grc(i)
    2
    >>> del i
    destructeur appelee : 5
    ici des choses a faire
    >>>
    Je trouve ça choquant. Qu'en pensez-vous ? Ai-je loupé quelque chose ?

    ps : j'ai strictement la même chose en version 2.7

  2. #2
    Membre Expert
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    941
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2007
    Messages : 941
    Par défaut
    C'est parce que l'interpréteur garde en mémoire le traceback de la dernière exception qui s'est produite (sys.last_traceback), et que celui-ci contient une référence à l'objet. Lors du second appel à del i, une autre exception est levée (NameError), et la précédente est donc libérée et __del__ est appelé.
    C'est expliqué dans la documentation de __del__ (http://docs.python.org/reference/dat...-customization). [ Les liens ancrés dans la doc Python n'ont pas l'air de bien fonctionner... C'est à la section 3.4.1 ].

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    [...]
    >>> i.methode(True)
    debut methode : 4
    debut ooops : 6
    Traceback (most recent call last):
      File "<interactive input>", line 1, in <module>
      File "C:\Prog\python\Script7.py", line 11, in methode
        if exception: self.ooops()
      File "C:\Prog\python\Script7.py", line 7, in ooops
        raise RuntimeError('exception provoquee')
    RuntimeError: exception provoquee
    >>> grc(i)
    4
    >>> sys.last_traceback = None
    >>> grc(i)
    2

  3. #3
    Membre Expert Avatar de plxpy
    Homme Profil pro
    Ingénieur géographe
    Inscrit en
    Janvier 2009
    Messages
    792
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur géographe
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Janvier 2009
    Messages : 792
    Par défaut
    Merci Dividee pour ta réponse.

    Effectivement, une référence supplémentaire est créée sur l'instance, empêchant sa "libération" tant qu'une autre exception n'est pas levée.

    Je me suis penché sur la doc que tu as indiquée. Dans un script, en non pas en interactif, mettre sys.exc_traceback à None ne fonctionne pas. En creusant encore un peu plus, la doc (2.6.5) indique que exc_traceback (ainsi que exc_type et exc_value) sont "deprecated" depuis la 1.5 et qu'il est préférable d'utiliser exc_info qui renvoie un tuple avec ces trois valeurs.

    De même, un exc_clear() est à priori préférable pour "remettre les compteurs à zéro", question exception (à mettre dans un except ou un finally par exemple).

    J'avoue que, jusqu'à aujourd'hui, j'étais passé complètement à côté du problème. C'est en gérant un verrou dans la méthode __del__ (en l'enlevant) que le problème s'est posé.

    Encore merci ! (ça commençait à me prendre méchamment la tête !)

    ps : je trouve le problème pas anodin du tout en POO et je suis surpris que cela ne soit pas abordé dans les bouquins que j'ai lus.

  4. #4
    Membre Expert
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    941
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2007
    Messages : 941
    Par défaut
    Pour ce genre de choses (gestion d'un verrou), il vaut mieux passer par un context manager (voir aussi contextlib).

    Si les bouquins que tu as lu ne te mettent pas en garde sur l'utilisation de __del__, c'est qu'ils ne sont pas très complets...

  5. #5
    Membre Expert Avatar de plxpy
    Homme Profil pro
    Ingénieur géographe
    Inscrit en
    Janvier 2009
    Messages
    792
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur géographe
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Janvier 2009
    Messages : 792
    Par défaut
    La remarque dans le post-scriptum ne concernait pas le fait que __del__ est effectivement appelée quand le compteur de référence passe à 0. Ca, je suis d'accord, c'est abordé et ce n'est pas ça qui m'a surpris.

    C'est plutôt le fait que le compteur de référence soit incrémenté lors de la levée d'une exception dans une méthode, "à l'insu de mon plein gré". Je ne suis jamais tombé sur une quelconque mise en garde là dessus.

    Résultat : le del que je faisais, en croyant dur comme fer que le compteur passait à 0 ne provoquait pas l'exécution effective de __del__ et j'étais à la merci de la survenue de la levée d'une autre exception (qui, selon le contexte, peut ne jamais survenir)

  6. #6
    Membre Expert
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    941
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2007
    Messages : 941
    Par défaut
    Je ne parlais pas du fait que __del__ est appelé quand le compteur de référence passe à 0.

    L'auteur pourrait faire le choix de ne pas détailler l'explication, qui est liée au comptage de références, mais au moins signaler que la méthode __del__ est "dangereuse", et que son exécution peut se produire plus tard que prévu (voire pas du tout).

    La gestion des exceptions en est une cause, mais il y a aussi le problème des références circulaires entre objets.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 6
    Dernier message: 06/10/2008, 16h31
  2. Installation de VBE 6.0 : levée d'exception Java
    Par Maria Ross dans le forum CORBA
    Réponses: 1
    Dernier message: 16/07/2007, 12h30
  3. levée d'exception sur ouverture fichier excel
    Par LeXo dans le forum VB 6 et antérieur
    Réponses: 3
    Dernier message: 11/10/2006, 14h30
  4. Réponses: 36
    Dernier message: 26/04/2006, 14h44

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo