7

Ich habe gesucht und ich bin nicht in der Lage mit jedem guten Grund zu kommen Python __enter__/__exit__ statt __init__ (oder __new__?) Des/__del__ zu verwenden.Python __enter__/__exit__ vs __init__ (oder __new__)/__del__

Ich verstehe, dass __enter__/__exit__ zur Verwendung bestimmt sind, mit der with Aussage als Kontext-Manager und die with Aussage ist groß. Aber das Gegenstück dazu ist, dass jeder Code in diesen Blöcken nur in diesem Kontext ausgeführt wird. Durch die Verwendung dieser anstelle von __init__/__del__ ich scheinen erstellen einen impliziten Vertrag mit Anrufern, dass sie with verwenden müssen, aber es gibt keine Möglichkeit, einen solchen Vertrag durchzusetzen, und der Vertrag wird nur über die Dokumentation mitgeteilt (oder den Code lesen). Das scheint eine schlechte Idee zu sein.

Ich bekomme den gleichen Effekt mit __init__/__del__ in einem with Block. Aber indem ich sie anstelle der Kontextverwaltungsmethoden verwende, ist mein Objekt auch in anderen Szenarien nützlich.

Also kann jemand mit einem überzeugenden Grund, warum würde ich jemals wollen die Kontext-Management-Methoden anstelle der Konstruktor/Destruktor-Methoden?

Wenn es eine bessere Stelle gibt, um eine Frage wie diese zu stellen, lassen Sie es mich bitte wissen, aber es scheint, als gäbe es nicht viele gute Informationen darüber. Up

Folgen:

Diese Frage auf einer schlechte beruhte (aber wahrscheinlich häufig) Annahme, weil ich immer with verwendet, um ein neues Objekt zu instanziieren, wobei in diesem Fall __init__/__del__ sehr nahe kommt das gleiche Verhalten wie __enter__/__exit__ (außer dass Sie nicht kontrollieren können, ob oder wenn __del__ ausgeführt wird, liegt es an der Garbage Collection und wenn der Prozess zuerst beendet wird, kann er nie aufgerufen werden). Aber wenn Sie bereits vorhandene Objekte in with Anweisungen verwenden, sind sie natürlich ganz anders.

+2

Wann (und sogar ob) '__del__' aufgerufen wird, ist nicht deterministisch. Sie können Daten verlieren, indem Sie für die Bereinigung von '__del__' abhängig sind. – user2357112

+0

möglich dupe: http://stackoverflow.com/a/6772907/674039 – wim

Antwort

7

Es gibt mehrere Unterschiede, die Sie verpasst zu haben scheinen:

  • Context Manager eine Chance bekommen, ein neues Objekt nur für den Block, den Sie ausführen, sind zu liefern. Einige Kontextmanager geben einfach self dorthin zurück (wie Dateiobjekte), aber Datenbankverbindungsobjekte könnten ein Cursor-Objekt zurückgeben, das mit der aktuellen Transaktion verknüpft ist.

  • Kontext-Manager werden nur über die Kontext-Endung benachrichtigt, aber auch, wenn der Exit durch eine Ausnahme verursacht wurde. Er kann dann entscheiden, ob er das Ereignis behandelt oder anders beim Beenden anders reagiert. Am Beispiel einer Datenbankverbindung können Sie aufgrund einer Ausnahme die Transaktion entweder festschreiben oder abbrechen.

  • __del__ wird nur aufgerufen, wenn alle Verweise auf ein Objekt entfernt werden. Dies bedeutet, dass Sie sich nicht darauf verlassen können, dass es aufgerufen wird, wenn Sie mehrfache Verweise darauf haben müssen, mit denen Sie die Lebensdauer von oder nicht steuern können. Ein Kontextmanager-Exit ist jedoch genau definiert.

  • Kontextmanager können wiederverwendet werden, und sie können den Status beibehalten. Die Datenbankverbindung erneut; Sie erstellen es einmal und verwenden es dann immer wieder als Kontextmanager. Dadurch bleibt diese Verbindung geöffnet. Es muss nicht jedes Mal ein neues Objekt angelegt werden.

    Dies ist zum Beispiel für Thread-Locks wichtig; Sie haben, um den Status zu halten, so dass nur ein Thread die Sperre auf einmal halten kann. Sie tun dies, indem Sie ein Lock-Objekt erstellen, dann verwenden Sie with lock:, so dass unterschiedliche Threads, die diesen Abschnitt ausführen, jeweils dazu gebracht werden können, zu warten, bevor sie in diesen Kontext eintreten.

Die __enter__ und __exit__ Methoden bilden die Kontext-Manager Protokoll, und Sie sollten diese nur verwenden, wenn Sie tatsächlich einen Kontext verwalten möchten. Das Ziel von Kontextmanagern ist es, die üblichen Muster try...finally und try...except zu vereinfachen und nicht die Lebensdauer einer einzelnen Instanz zu verwalten. Siehe PEP 343 – The "with" Statement:

Dieser PEP fügt eine neue Aussage „mit“ der Sprache Python es möglich zu machen Aussagen ausklammern Standardanwendungen von try/finally.

+0

Ihre ersten beiden Punkte sind wirklich über die 'mit' Aussage, nicht betreten und verlassen. Ich stimme zu, dass "mit" grandios ist, aber in dieser Frage mache ich mir mehr Gedanken über die beste Möglichkeit, ein flexibles Objekt zu schreiben, das innerhalb von 'mit' verwendet werden kann oder nicht. Ich bekomme den "__del__" Punkt. – BobDoolittle

+2

@BobDoolittle: Es ist wenig sinnvoll, '__enter__' und' __exit__' ** ohne ** die 'with'-Anweisung zu implementieren. Die "mit" -Anweisung ist der Grund, warum wir diese Methoden überhaupt haben. –

+0

Natürlich gibt es. Es ist der Unterschied zwischen einem Server und einem Client. Zwischen einem Anrufer und einem Dienst. Ich schreibe ein Objekt. Jemand anders kann es benutzen. Sie können es in einem 'mit'-Block verwenden oder nicht, und ich habe keine Möglichkeit, die Verwendung durchzusetzen. Ich sollte mein Objekt so schreiben, dass es in beiden Fällen korrekt funktioniert. Wie 'File' Objekte - kann ich dann in einer 'with'-Anweisung verwenden oder nicht, und sie arbeiten in beide Richtungen. Außerhalb eines 'with'-Blocks sollte ich close() jedoch aufrufen (aber ich vermute, dass' __del__' das auch tut). – BobDoolittle

2

del x stellt nicht direkt x.__del__()

Sie haben keine Kontrolle darüber, wann .__del__ genannt wird, oder in der Tat whether it gets called at all.

Daher ist __init__/__del__ für Kontextverwaltung nicht zuverlässig.

+0

Ich verstehe Ihren Punkt über '__del__'. Aber nicht "__init__". "__init__" sollte innerhalb oder außerhalb des Kontextmanagements zuverlässig sein. An dieser Stelle denke ich, dass der beste Weg, ein flexibles Objekt zu schreiben, darin besteht, den Initialisierungscode in "__init__" anstatt "__enter__" zu setzen und "__exit__" dasselbe zu tun wie "__del__" (beim Schützen) gegen doppelte Ausführung). Tatsächlich vermute ich, dass genau das die File-Objekte tun. – BobDoolittle

+1

@BobDoolittle: '__init__' ist für die Initialisierung, also wenn Sie die Initialisierung tun möchten, tun Sie es dort. '__enter__' ist für Arbeit, die speziell bei Eingabe einer' with'-Anweisung passieren sollte; zum Beispiel kann '__enter__' eine Sperre sperren und' __exit__' entsperren. – user2357112

0

Durch die Verwendung dieser statt __init__/__del__ erscheine ich einen impliziten Vertrag mit Anrufern zu erschaffen, dass sie with verwenden müssen, doch gibt es keine Möglichkeit, einen solchen Vertrag

Sie haben entweder einen Vertrag zu erzwingen Weg. Wenn Benutzer Ihr Objekt verwenden, ohne zu merken, dass es nach der Verwendung aufgeräumt werden muss, werden sie Dinge vermasseln, egal wie Sie die Bereinigung implementieren. Sie können für immer einen Verweis auf Ihr Objekt behalten, beispielsweise um zu verhindern, dass __del__ ausgeführt wird.

Wenn Sie ein Objekt haben, für das eine spezielle Bereinigung erforderlich ist, müssen Sie diese Anforderung explizit angeben. Sie müssen Benutzern with Funktionalität und eine explizite close oder ähnliche Methode geben, damit die Benutzer steuern können, wann die Bereinigung stattfindet. Sie können die Bereinigungsanforderung nicht innerhalb einer __del__-Methode verbergen. Möglicherweise möchten Sie als Sicherheitsmaßnahme auch __del__ implementieren, aber Sie können nicht __del__anstelle vonwith oder eine explizite close verwenden.


Nachdem dies gesagt wurde, macht Python keine Versprechungen, dass __del__ laufen, nie. Die Standardimplementierung wird __del__ ausgeführt, wenn der refcount eines Objekts auf 0 fällt. Dies kann jedoch nicht passieren, wenn eine Referenz bis zum Ende des Skripts erhalten bleibt oder wenn sich das Objekt in einem Referenzzyklus befindet. Andere Implementierungen verwenden Refcounting nicht, so dass __del__ noch weniger vorhersehbar ist.