Je ne comprends pas ce comportement de python del

est-ce que quelqu'un peut expliquer pourquoi le code suivant se comporte comme il le fait:

import types

class Dummy():
    def __init__(self, name):
        self.name = name
    def __del__(self):
        print "delete",self.name

d1 = Dummy("d1")
del d1
d1 = None
print "after d1"

d2 = Dummy("d2")
def func(self):
    print "func called"
d2.func = types.MethodType(func, d2)
d2.func()
del d2
d2 = None
print "after d2"

d3 = Dummy("d3")
def func(self):
    print "func called"
d3.func = types.MethodType(func, d3)
d3.func()
d3.func = None
del d3
d3 = None
print "after d3"

la sortie (notez que le destructeur pour d2 n'est jamais appelé) est ceci (python 2.7)

delete d1
after d1
func called
after d2
func called
delete d3
after d3

y a-t-il un moyen de" corriger " le code pour que le destructeur soit appelé sans supprimer la méthode ajoutée? Je veux dire, le meilleur endroit pour mettre la d2.func = aucun ne serait dans le destructeur!

Merci

[modifier] en Fonction de la en ce qui concerne les premières réponses, j'aimerais préciser que je ne pose pas de questions sur le bien-fondé (ou l'absence de bien-fondé) de l'utilisation de __del__ . J'ai essayé de créer la fonction la plus courte qui démontrerait ce que je considère comme un comportement non intuitif. Je suppose qu'une référence circulaire a été créée, mais je ne sais pas pourquoi. Si possible, j'aimerais savoir comment éviter la référence circulaire....

28
demandé sur ppperry 2011-05-24 04:24:44

7 réponses

vous ne pouvez pas supposer que __del__ sera jamais appelé - ce n'est pas un endroit pour espérer que les ressources sont automatiquement désallouées. Si vous voulez vous assurer qu'une ressource (Non-mémoire) est libérée, vous devez faire une release() ou méthode similaire et ensuite appeler explicitement (ou l'utiliser dans un context manager comme souligné par Thanatos dans les commentaires ci-dessous).

au moins, vous devriez lire le __del__ documentation très proche, et alors vous ne devriez probablement pas essayer d'utiliser __del__ . (Se référer également à la gc.garbage documentation pour d'autres mauvaises choses au sujet de __del__ )

33
répondu Nick Bastin 2011-05-24 00:37:46

je donne ma propre réponse parce que, bien que j'apprécie le Conseil d'éviter __del__ , ma question était de savoir comment le faire fonctionner correctement pour l'échantillon de code fourni.

version abrégée: le code suivant utilise weakref pour éviter la référence circulaire. Je pensais avoir essayé avant de poster la question, mais je suppose que j'ai fait quelque chose de mal.

import types, weakref

class Dummy():
    def __init__(self, name):
        self.name = name
    def __del__(self):
        print "delete",self.name

d2 = Dummy("d2")
def func(self):
    print "func called"
d2.func = types.MethodType(func, weakref.ref(d2)) #This works
#d2.func = func.__get__(weakref.ref(d2), Dummy) #This works too
d2.func()
del d2
d2 = None
print "after d2"

plus long version : Lorsque j'ai affiché la question, j'ai cherché des questions semblables. Je sais que vous pouvez utiliser with à la place, et que le sentiment dominant est que __del__ est mauvais .

utiliser with a du sens, mais seulement dans certaines situations. Ouvrir un fichier, le lire et le fermer est un bon exemple où with est une bonne solution. Vous avez passé un bloc de code où l'objet est nécessaire, et vous voulez nettoyer l'objet et la fin du bloc.

une connexion de base de données semble être utilisée souvent comme un exemple qui ne fonctionne pas bien en utilisant with , puisque vous avez généralement besoin de quitter la section de code qui crée la connexion et avoir la connexion fermée dans un délai plus event-driven (plutôt que séquentiel).

si with n'est pas la bonne solution, je vois deux alternatives:

  1. vous vous assurez que __del__ fonctionne (voir ce blog pour un meilleur description de weakref utilisation)
  2. vous utilisez le module atexit pour lancer un rappel lorsque votre programme ferme. Voir ce sujet par exemple.

alors que j'ai essayé de fournir du code simplifié, mon vrai problème est plus lié à l'événement, donc with n'est pas une solution appropriée ( with est très bien pour le simplifié le code). Je voulais aussi éviter de atexit , que mon programme peut être longue, et je veux être en mesure d'effectuer le nettoyage dès que possible.

donc, dans ce cas précis, je trouve que c'est la meilleure solution pour utiliser weakref et empêcher les références circulaires qui empêcheraient __del__ de fonctionner.

cela peut être une exception à la règle, mais il ya des cas d'utilisation où l'utilisation de weakref et __del__ est le droit la mise en œuvre, à mon humble avis.

16
répondu Brett Stottlemyer 2017-12-13 10:33:38

au lieu de del , vous pouvez utiliser l'opérateur with .

http://effbot.org/zone/python-with-statement.htm

tout comme avec les objets filetype, vous pouvez quelque chose comme

with Dummy('d1') as d:
    #stuff
#d is guaranteed to be out of scope
10
répondu Falmarri 2011-05-24 00:37:48

del ne pas appeler __del__

del dans la façon dont vous utilisez supprime une variable locale. __del__ est appelée lorsque l'objet est détruit. Python en tant que langage ne donne aucune garantie quant au moment où il détruira un objet.

CPython comme l'implémentation la plus courante de Python, utilise le comptage de référence. En conséquence, del fonctionnera souvent comme vous l'attendez. Cependant, il ne fonctionnera pas dans le cas que vous avez une référence cycle.

d3 -> d3.func -> d3

Python ne détecte pas cela et ne nettoie pas tout de suite. Et pas seulement des cycles de référence. Si une exception est jeter vous voulez probablement encore appeler votre destructeur. Cependant, Python conservera généralement les variables locales dans le cadre de son traceback.

la solution ne doit pas dépendre de la méthode __del__ . Utilisez plutôt un gestionnaire de contexte.

class Dummy:
   def __enter__(self):
       return self

   def __exit__(self, type, value, traceback):
       print "Destroying", self

with Dummy() as dummy:
    # Do whatever you want with dummy in here
# __exit__ will be called before you get here

cela est garanti à travailler, et vous pouvez même vérifier les paramètres pour voir si vous manipulez une exception et faire quelque chose de différent dans ce cas.

6
répondu Winston Ewert 2011-05-24 00:43:26

un exemple complet de gestionnaire de contexte.

class Dummy(object):
    def __init__(self, name):
        self.name = name
    def __enter__(self):
        return self
    def __exit__(self, exct_type, exce_value, traceback):
        print 'cleanup:', d
    def __repr__(self):
        return 'Dummy(%r)' % (self.name,)

with Dummy("foo") as d:
    print 'using:', d

print 'later:', d
2
répondu SingleNegationElimination 2011-05-29 16:52:19

Il me semble que le véritable cœur de la question est ici:

ajouter les fonctions est dynamique (à l'exécution) et non connu à l'avance

je sens que ce que vous recherchez vraiment est une manière flexible de lier différentes fonctionnalités à un objet représentant un État de programme, également connu sous le nom de polymorphisme. Python le fait très bien, pas en attachant/détachant des méthodes, mais en instanciant différentes classes. Je suggère regardez à nouveau votre organisation de classe. Peut-être avez-vous besoin de séparer un objet de données principal, persistant des objets d'état transitoire. Utilisez le a-a paradigme plutôt que est-a: chaque fois que l'état change, vous envelopper les données du noyau dans un objet d'état, ou vous assignez le nouvel objet d'État à un attribut du noyau.

si vous êtes sûr que vous ne pouvez pas utiliser ce genre de Pythonic OOP, vous pouvez toujours travailler autour de votre problème un autre en définissant toutes vos fonctions dans la classe pour commencer et en les liant ensuite à des attributs d'instance supplémentaires (à moins que vous compiliez ces fonctions à la volée à partir de l'entrée de l'utilisateur):

class LongRunning(object):
    def bark_loudly(self):
        print("WOOF WOOF")

    def bark_softly(self):
        print("woof woof")


while True:
    d = LongRunning()
    d.bark = d.bark_loudly
    d.bark()

    d.bark = d.bark_softly
    d.bark()
2
répondu Aryeh Leib Taurog 2018-08-14 03:31:34

une solution alternative à l'utilisation de weakref est de lier dynamiquement la fonction à l'instance seulement quand elle est appelée en remplaçant __getattr__ ou __getattribute__ sur la classe pour retourner func.__get__(self, type(self)) au lieu de juste func pour les fonctions liées à l'instance. C'est ainsi que se comportent les fonctions définies sur la classe. Malheureusement (pour certains cas d'utilisation) python n'exécute pas la même logique pour les fonctions attachées à l'instance elle-même, mais vous pouvez la modifier pour le faire. J'ai avait des problèmes similaires avec des descripteurs liés à des instances. La Performance ici n'est probablement pas aussi bonne que d'utiliser weakref , mais c'est une option qui fonctionnera de manière transparente pour toute fonction assignée dynamiquement avec l'utilisation de seulement des builtins python.

si vous vous trouvez à faire cela souvent, vous pourriez vouloir un metaclass personnalisé qui fait la liaison dynamique des fonctions de niveau d'instance.

une Autre alternative est d'ajouter la fonction directement à l' classe, qui exécutera alors correctement la reliure quand il est appelé. Pour beaucoup de cas d'utilisation, cela aurait quelques maux de tête impliqués: à savoir, correctement namespace les fonctions afin qu'elles ne se heurtent pas. L'id d'instance pourrait être utilisé pour cela, mais, depuis que l'id disponible n'est pas garantie unique sur la durée de vie du programme, vous aurez besoin de réfléchir un peu pour s'assurer qu'il fonctionne pour votre cas d'utilisation... en particulier, vous devez probablement vous assurer que vous supprimez la fonction de classe quand un objet va out of scope, et donc son adresse id/memory est à nouveau disponible. __del__ est parfait pour ça :). Alternativement, vous pouvez effacer toutes les méthodes namespaced à l'instance sur la création d'objet (dans __init__ ou __new__ ).

une autre alternative (plutôt que de jouer avec les méthodes magiques de python) est d'ajouter explicitement une méthode pour appeler vos fonctions liées dynamiquement. Ceci a l'inconvénient que vos utilisateurs ne peuvent pas appeler votre fonction en utilisant python normal syntaxe:

class MyClass(object):
    def dynamic_func(self, func_name):
        return getattr(self, func_name).__get__(self, type(self))

    def call_dynamic_func(self, func_name, *args, **kwargs):
        return getattr(self, func_name).__get__(self, type(self))(*args, **kwargs)

    """
    Alternate without using descriptor functionality:
    def call_dynamic_func(self, func_name, *args, **kwargs):
        return getattr(self, func_name)(self, *args, **kwargs)
    """

Juste pour faire de ce post, je vais vous montrer votre weakref option:

import weakref
inst = MyClass()
def func(self):
    print 'My func'
#  You could also use the types modules, but the descriptor method is cleaner IMO
inst.func = func.__get__(weakref.ref(inst), type(inst))
0
répondu DylanYoung 2017-09-23 16:39:08