Question

class Package:
    def __init__(self):
        self.files = []

    # ...

    def __del__(self):
        for file in self.files:
            os.unlink(file)

__del__(self) ci-dessus échoue avec une exception AttributeError. Je comprends Python ne garantit pas l'existence de « variables globales » (données membre dans ce contexte?) quand on invoque __del__(). Si tel est le cas, ce qui est la raison de l'exception, comment puis-je faire que l'objet destructs correctement?

Était-ce utile?

La solution

Je vous recommande d'utiliser la déclaration de Python pour with la gestion des ressources qui ont besoin d'être nettoyés. Le problème avec l'aide d'une déclaration explicite est que vous close() à vous soucier des gens oublier de l'appeler du tout ou oublier de le placer dans un bloc pour empêcher finally une fuite de ressource lorsqu'une exception se produit.

Pour utiliser l'instruction __enter__, créez une classe avec les méthodes suivantes:

  def __enter__(self)
  def __exit__(self, exc_type, exc_value, traceback)

Dans votre exemple ci-dessus, vous utiliseriez

class Package:
    def __init__(self):
        self.files = []

    def __enter__(self):
        return self

    # ...

    def __exit__(self, exc_type, exc_value, traceback):
        for file in self.files:
            os.unlink(file)

Alors, quand quelqu'un voulait utiliser votre classe, ils feraient ce qui suit:

with Package() as package_obj:
    # use package_obj

La package_obj variable est une instance de package de type (c'est la valeur retournée par la méthode __exit__). Sa méthode sera automatiquement <=> appelé, peu importe si oui ou non une exception se produit.

Vous pouvez même utiliser cette approche un peu plus loin. Dans l'exemple ci-dessus, quelqu'un pourrait encore instancier paquet en utilisant son constructeur sans utiliser la clause <=>. Vous ne voulez pas que cela se produise. Vous pouvez résoudre ce problème en créant une classe PackageResource qui définit les méthodes <=> et <=>. Ensuite, la classe Package serait strictement défini dans la méthode et retour <=>. De cette façon, l'appelant n'a jamais pu instancier la classe Package sans utiliser une déclaration <=>:

class PackageResource:
    def __enter__(self):
        class Package:
            ...
        self.package_obj = Package()
        return self.package_obj

    def __exit__(self, exc_type, exc_value, traceback):
        self.package_obj.cleanup()

Vous souhaitez l'utiliser comme suit:

with PackageResource() as package_obj:
    # use package_obj

Autres conseils

La méthode standard consiste à utiliser atexit.register:

# package.py
import atexit
import os

class Package:
    def __init__(self):
        self.files = []
        atexit.register(self.cleanup)

    def cleanup(self):
        print("Running cleanup...")
        for file in self.files:
            print("Unlinking file: {}".format(file))
            # os.unlink(file)

Mais vous devez garder à l'esprit que cela persistera toutes les instances créées de jusqu'à Python est Package terminée.

Démo en utilisant le code ci-dessus enregistré en tant que package.py :

$ python
>>> from package import *
>>> p = Package()
>>> q = Package()
>>> q.files = ['a', 'b', 'c']
>>> quit()
Running cleanup...
Unlinking file: a
Unlinking file: b
Unlinking file: c
Running cleanup...

En annexe de réponse de Clint, vous pouvez simplifier l'utilisation PackageResource contextlib.contextmanager :

@contextlib.contextmanager
def packageResource():
    class Package:
        ...
    package = Package()
    yield package
    package.cleanup()

Sinon, bien que probablement pas aussi Pythonic, vous pouvez remplacer Package.__new__:

class Package(object):
    def __new__(cls, *args, **kwargs):
        @contextlib.contextmanager
        def packageResource():
            # adapt arguments if superclass takes some!
            package = super(Package, cls).__new__(cls)
            package.__init__(*args, **kwargs)
            yield package
            package.cleanup()

    def __init__(self, *args, **kwargs):
        ...

et utilisez simplement with Package(...) as package.

Pour faire avancer les choses plus courtes, le nom de votre fonction de nettoyage et utiliser close href="https://docs.python.org/3/library/contextlib.html#contextlib.closing" rel="noreferrer"> contextlib.closing, dans ce cas, vous pouvez utiliser la classe non modifiée par Package ou remplacer son with contextlib.closing(Package(...)) au plus simple __new__

class Package(object):
    def __new__(cls, *args, **kwargs):
        package = super(Package, cls).__new__(cls)
        package.__init__(*args, **kwargs)
        return contextlib.closing(package)

Et ce constructeur est hérité, de sorte que vous pouvez simplement hériter, par exemple.

class SubPackage(Package):
    def close(self):
        pass

Je ne pense pas qu'il est possible pour les membres de l'instance à retirer avant est appelé __del__. Je dirais que la raison de votre AttributeError particulier est un autre endroit (vous supprimez par erreur peut-être self.file ailleurs).

Cependant, comme les autres ont souligné, vous devriez éviter d'utiliser <=>. La principale raison est que les cas avec ne seront pas <=> déchets collectés (ils ne seront libérés lorsque leur refcount atteint 0). Par conséquent, si vos instances sont impliqués dans des références circulaires, ils vont vivre dans la mémoire aussi longtemps que l'exécution de l'application. (Je peux me tromper sur tout cela que, je dois relire les documents gc, mais je suis plutôt sûr que cela fonctionne comme ça).

Je pense que le problème pourrait être s'il y a __init__ plus de code que montré?

__del__ sera appelée quand même n'a pas été <=> exécuté correctement ou a jeté une exception.

Il suffit de terminer votre destructor avec un essai / except et il ne sera pas jeter une exception si vos globals sont déjà éliminés.

Modifier

Essayez ceci:

from weakref import proxy

class MyList(list): pass

class Package:
    def __init__(self):
        self.__del__.im_func.files = MyList([1,2,3,4])
        self.files = proxy(self.__del__.im_func.files)

    def __del__(self):
        print self.__del__.im_func.files

Il farcir la liste des fichiers dans le répertoire del fonction qui est garanti d'exister au moment de l'appel. Le proxy weakref est d'empêcher Python, ou vous-même de supprimer la variable self.files en quelque sorte (si elle est supprimée, il n'affectera pas la liste des fichiers d'origine). Si ce n'est pas le cas que cela est en cours de suppression, même si il y a plus de références à la variable, vous pouvez supprimer l'encapsulation proxy.

Voici un squelette de travail minimal:

class SkeletonFixture:

    def __init__(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

    def method(self):
        pass


with SkeletonFixture() as fixture:
    fixture.method()

Important: auto retour


Si vous êtes comme moi, et donnent sur la partie return self (de bonne réponse Clint Miller ), vous sera regarder ce non-sens:

Traceback (most recent call last):
  File "tests/simplestpossible.py", line 17, in <module>                                                                                                                                                          
    fixture.method()                                                                                                                                                                                              
AttributeError: 'NoneType' object has no attribute 'method'

J'ai passé une demi-journée à ce sujet. Hope it helps la personne suivante.

Il semble que la façon idiomatiques à faire est de fournir une méthode de close() (ou similaire), et l'appeler explicitement.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top