599 Stimmen

Wie bereinige ich ein Python-Objekt richtig?

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

    # ...

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

__del__(self) schlägt mit einer AttributeError-Ausnahme fehl. Ich verstehe Python garantiert nicht das Vorhandensein von "globalen Variablen" (Mitgliedsdaten in diesem Zusammenhang?), wenn __del__() aufgerufen wird. Wenn das der Fall ist und dies der Grund für die Ausnahme ist, wie kann ich sicherstellen, dass das Objekt ordnungsgemäß zerstört wird?

3 Stimmen

Wenn ich lese, was du verlinkt hast, scheinen globale Variablen, die verschwinden, hier nicht zu gelten, es sei denn, du sprichst davon, dass dein Programm beendet wird, wobei ich vermute, dass es nach dem, was du verlinkt hast, MÖGLICH sein könnte, dass das Betriebssystemmodul selbst bereits verschwunden ist. Ansonsten glaube ich nicht, dass es auf Mitgliedsvariablen in einer __del__()-Methode zutrifft.

3 Stimmen

Die Ausnahme wird ausgelöst, lange bevor mein Programm beendet wird. Die AttributeError-Ausnahme, die ich erhalte, ist Python, das sagt, dass es self.files nicht als ein Attribut von Package erkennt. Vielleicht verstehe ich das falsch, aber wenn sie mit "globals" keine Variablen meinen, die global für Methoden sind (sondern möglicherweise lokal für die Klasse), dann weiß ich nicht, was diese Ausnahme verursacht. Google weist darauf hin, dass Python sich das Recht vorbehält, Mitgliedsdaten zu bereinigen, bevor __del__(self) aufgerufen wird.

1 Stimmen

Der Code wie gepostet scheint für mich zu funktionieren (mit Python 2.5). Können Sie den tatsächlichen Code posten, der fehlschlägt - oder eine vereinfachte (je einfacher, desto besser) Version, die immer noch den Fehler verursacht?

761voto

Clint Miller Punkte 14763

Ich empfehle die Verwendung von Pythons with Anweisung zur Verwaltung von Ressourcen, die bereinigt werden müssen. Das Problem bei der Verwendung einer expliziten close() Anweisung ist, dass man sich Sorgen machen muss, dass die Leute vergessen, sie überhaupt aufzurufen, oder dass sie vergessen, sie in eine finally Block, um ein Ressourcenleck zu verhindern, wenn eine Ausnahme auftritt.

Zur Verwendung des with Anweisung erstellen Sie eine Klasse mit den folgenden Methoden:

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

In Ihrem obigen Beispiel würden Sie Folgendes verwenden

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)

Wenn dann jemand Ihre Klasse benutzen wollte, musste er Folgendes tun:

with Package() as package_obj:
    # use package_obj

Die Variable package_obj ist eine Instanz des Typs Package (es ist der Wert, der von der Methode __enter__ Methode). Seine __exit__ Methode wird automatisch aufgerufen, unabhängig davon, ob eine Ausnahme auftritt oder nicht.

Sie könnten sogar noch einen Schritt weiter gehen. Im obigen Beispiel könnte jemand ein Paket mit seinem Konstruktor instanziieren, ohne die with Klausel. Sie wollen nicht, dass das passiert. Sie können dies beheben, indem Sie eine PackageResource-Klasse erstellen, die die __enter__ y __exit__ Methoden. Dann würde die Paketklasse streng innerhalb der __enter__ Methode und zurückgegeben. Auf diese Weise konnte der Aufrufer die Paketklasse nie instanziieren, ohne eine with Erklärung:

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()

Sie würden dies wie folgt verwenden:

with PackageResource() as package_obj:
    # use package_obj

54 Stimmen

Technisch gesehen könnte man PackageResource().__enter__() explizit aufrufen und so ein Paket erstellen, das niemals abgeschlossen wird... aber man müsste wirklich versuchen, den Code zu brechen. Wahrscheinlich nichts, worüber man sich Sorgen machen müsste.

4 Stimmen

Übrigens, wenn Sie Python 2.5 verwenden, müssen Sie von Zukunft importieren Sie with_statement, um die with-Anweisung verwenden zu können.

3 Stimmen

Ich habe einen Artikel gefunden, der zeigt, warum sich __del__() so verhält, wie es sich verhält, und der die Verwendung eines Kontextmanagers rechtfertigt: andy-pearce.com/blog/posts/2013/Apr/python-destructor-drawbacks

106voto

ostrokach Punkte 14576

Die Standardmethode ist die Verwendung von 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)

Sie sollten jedoch bedenken, dass dadurch alle erstellten Instanzen von Package bis Python beendet wird.

Demo unter Verwendung des obigen Codes gespeichert als paket.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...

3 Stimmen

Das Schöne am atexit.register-Ansatz ist, dass man sich keine Gedanken darüber machen muss, was der Benutzer der Klasse tut (hat er with ? haben sie ausdrücklich genannt __enter__ ?) Der Nachteil ist natürlich, wenn Sie die Bereinigung vor dem Beenden von Python benötigen, wird es nicht funktionieren. In meinem Fall ist es mir egal, ob es ist, wenn das Objekt aus dem Geltungsbereich geht oder wenn es nicht, bis Python beendet ist :)

0 Stimmen

Kann ich enter und exit verwenden und auch atexit.register(self.__exit__) ?

0 Stimmen

@myradio Ich weiß nicht, wie das nützlich sein soll? Kann man nicht die gesamte Bereinigungslogik innerhalb von __exit__ und einen Kontextmanager verwenden? Auch, __exit__ nimmt zusätzliche Argumente entgegen (z.B. __exit__(self, type, value, traceback) ), so dass Sie diese berücksichtigen müssen. Wie auch immer, es klingt, als sollten Sie eine separate Frage zu SO stellen, weil Ihr Anwendungsfall ungewöhnlich erscheint?

49voto

SCGH Punkte 727

Eine bessere Alternative ist die Verwendung von weakref.finalisieren . Siehe die Beispiele unter Finalizer-Objekte y Vergleich von Finalizern mit __del__()-Methoden .

1 Stimmen

Ich habe es heute benutzt und es funktioniert einwandfrei, besser als andere Lösungen. Ich habe eine Multiprozessor-basierte Communicator-Klasse, die eine serielle Schnittstelle öffnet, und dann habe ich eine stop() Methode zum Schließen der Ports und join() die Prozesse. Wenn die Programme jedoch unerwartet beendet werden stop() nicht aufgerufen wird - ich habe das mit einem Finalizer gelöst. Aber in jedem Fall rufe ich _finalizer.detach() in der Stop-Methode, um zu verhindern, dass sie zweimal aufgerufen wird (manuell und später erneut durch den Finalizer).

5 Stimmen

IMO ist dies wirklich die beste Antwort. Sie kombiniert die Möglichkeit, bei der Garbage Collection aufzuräumen mit der Möglichkeit, beim Beenden aufzuräumen. Der Nachteil ist, dass Python 2.7 nicht weakref.finalize hat.

40voto

Tobias Kienzler Punkte 23415

Als Anhang zu Clint's Antwort können Sie vereinfachen PackageResource mit contextlib.contextmanager :

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

Alternativ, wenn auch wahrscheinlich nicht als Pythonic, können Sie überschreiben 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):
        ...

und verwenden Sie einfach with Package(...) as package .

Um die Sache kürzer zu machen, benennen Sie Ihre Aufräumfunktion close und verwenden contextlib.closing in diesem Fall können Sie entweder die unveränderte Package Klasse über with contextlib.closing(Package(...)) oder überschreiben Sie seine __new__ zum einfacheren

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

Und dieser Konstruktor wird vererbt, Sie können also einfach erben, z. B.

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

1 Stimmen

Das ist großartig. Das letzte Beispiel gefällt mir besonders gut. Es ist bedauerlich, dass wir die vierzeilige Floskel des Package.__new__() Methode jedoch nicht. Oder vielleicht können wir das. Wir könnten wahrscheinlich entweder einen Klassendekorator oder eine Metaklasse definieren, die diese Boilerplate für uns generiert. Nahrung für Pythonic Gedanken.

0 Stimmen

@CecilCurry Danke, und guter Punkt. Jede Klasse, die von Package sollte dies auch tun (obwohl ich das noch nicht getestet habe), so dass keine Metaklasse erforderlich sein sollte. Obwohl ich haben Ich habe in der Vergangenheit einige ziemlich merkwürdige Wege gefunden, Metaklassen zu verwenden...

0 Stimmen

@CecilCurry Eigentlich wird der Konstruktor vererbt, Sie können also Package (oder besser eine Klasse namens Closing ) als Elternteil Ihrer Klasse anstelle von object . Aber fragen Sie mich nicht, wie die Mehrfachvererbung damit zusammenhängt...

22voto

user2394284 Punkte 4682

Hier ist ein minimales Arbeitsgerüst:

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()

Das ist wichtig: Selbst zurückgeben


Wenn Sie wie ich sind und die return self Teil (von Die richtige Antwort von Clint Miller ), werden Sie auf diesen Unsinn starren:

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

Ich hoffe, es hilft der nächsten Person.

0 Stimmen

Auf return self , eine Diskussion ist hier stackoverflow.com/questions/38281853/

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X