Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 442 – Sichere Objektfinalisierung

Autor:
Antoine Pitrou <solipsis at pitrou.net>
BDFL-Delegate:
Benjamin Peterson <benjamin at python.org>
Status:
Final
Typ:
Standards Track
Erstellt:
18. Mai 2013
Python-Version:
3.4
Post-History:
18. Mai 2013
Resolution:
Python-Dev Nachricht

Inhaltsverzeichnis

Zusammenfassung

Dieses PEP schlägt vor, die aktuellen Einschränkungen der Objektfinalisierung zu beheben. Das Ziel ist es, Finalisierer für jedes Objekt definieren und ausführen zu können, unabhängig von seiner Position im Objektgraphen.

Dieses PEP erfordert keine Änderungen am Python-Code. Objekte mit bestehenden Finalisierern werden automatisch davon profitieren.

Definitionen

Referenz
Ein gerichteter Link von einem Objekt zu einem anderen. Das Ziel der Referenz wird durch die Referenz am Leben erhalten, solange die Quelle selbst am Leben ist und die Referenz nicht gelöscht wird.
Schwache Referenz
Ein gerichteter Link von einem Objekt zu einem anderen, der sein Ziel nicht am Leben erhält. Dieses PEP konzentriert sich auf nicht-schwache Referenzen.
Referenzzyklus
Ein zyklischer Teilgraph von gerichteten Links zwischen Objekten, der verhindert, dass diese Objekte in einem reinen Referenzzählsystem gesammelt werden.
Zyklisches Isolat (CI)
Ein eigenständiger Teilgraph von Objekten, in dem kein Objekt von außen referenziert wird, der einen oder mehrere Referenzzyklen enthält *und* dessen Objekte sich noch in einem benutzbaren, nicht defekten Zustand befinden: Sie können von ihren jeweiligen Finalisierern aus aufeinander zugreifen.
Zyklischer Garbage Collector (GC)
Ein Gerät, das zyklische Isolate erkennen und in zyklischen Müll umwandeln kann. Objekte in zyklischem Müll werden schließlich durch die natürliche Wirkung des Löschens von Referenzen und das Fallen ihrer Referenzzählungen auf Null freigegeben.
Zyklischer Müll (CT)
Ein ehemaliges zyklisches Isolat, dessen Objekte vom GC zu löschen begonnen wurden. Objekte in zyklischem Müll sind potenzielle Zombies; wenn auf sie von Python-Code zugegriffen wird, können die Symptome von seltsamen AttributeErrors bis hin zu Abstürzen reichen.
Zombie / defektes Objekt
Ein Objekt, das Teil von zyklischem Müll ist. Der Begriff betont, dass das Objekt nicht sicher ist: Seine ausgehenden Referenzen wurden möglicherweise gelöscht, oder eines der Objekte, auf die es verweist, könnte ein Zombie sein. Daher sollte nicht von beliebigem Code (wie Finalisierern) darauf zugegriffen werden.
Finalisierer
Eine Funktion oder Methode, die aufgerufen wird, wenn ein Objekt zur Freigabe vorgesehen ist. Der Finalisierer kann auf das Objekt zugreifen und alle vom Objekt gehaltenen Ressourcen freigeben (z. B. Mutexe oder Dateideskriptoren). Ein Beispiel ist eine `__del__`-Methode.
Wiederbelebung
Der Prozess, durch den ein Finalisierer eine neue Referenz auf ein Objekt in einem CI erstellt. Dies kann als kuriose, aber unterstützte Nebenwirkung von `__del__`-Methoden geschehen.

Auswirkungen

Obwohl dieses PEP CPython-spezifische Implementierungsdetails behandelt, wird erwartet, dass die Änderung der Finalisierungssemantik das Python-Ökosystem als Ganzes beeinflusst. Insbesondere macht dieses PEP die aktuelle Richtlinie ungültig, dass „Objekte mit einer `__del__`-Methode kein Teil eines Referenzzyklus sein sollten“.

Vorteile

Die primären Vorteile dieses PEP betreffen Objekte mit Finalisierern, wie z. B. Objekte mit einer `__del__`-Methode und Generatoren mit einem `finally`-Block. Diese Objekte können nun wiederhergestellt werden, wenn sie Teil eines Referenzzyklus sind.

Das PEP ebnet auch den Weg für weitere Vorteile

  • Das Modul-Shutdown-Verfahren muss möglicherweise keine globalen Variablen mehr auf None setzen. Dies könnte eine bekannte Klasse von ärgerlichen Problemen lösen.

Das PEP ändert die Semantik von nicht

  • Schwache Referenzen, die in Referenzzyklen gefangen sind.
  • C-Erweiterungstypen mit einer benutzerdefinierten `tp_dealloc`-Funktion.

Description

Referenzzählbasierte Freigabe

Bei der normalen referenzzählbasierte Freigabe wird der Finalisierer eines Objekts kurz vor der Freigabe des Objekts aufgerufen. Wenn der Finalisierer das Objekt wiederbelebt, wird die Freigabe abgebrochen.

Jedoch, wenn das Objekt bereits finalisiert wurde, wird der Finalisierer nicht aufgerufen. Dies verhindert, dass wir Zombies finalisieren (siehe unten).

Freigabe von zyklischen Isolaten

Zyklische Isolate werden zuerst vom Garbage Collector erkannt und dann freigegeben. Die Erkennungsphase ändert sich nicht und wird hier nicht beschrieben. Die Freigabe eines CI erfolgt traditionell in folgender Reihenfolge:

  1. Schwache Referenzen auf CI-Objekte werden gelöscht und ihre Rückrufe aufgerufen. Zu diesem Zeitpunkt sind die Objekte noch sicher zu verwenden.
  2. Das CI wird zu einem CT, da der GC systematisch alle bekannten Referenzen darin löscht (mithilfe der `tp_clear`-Funktion).
  3. Nichts. Alle CT-Objekte sollten in Schritt 2 freigegeben worden sein (als Nebenwirkung des Löschens von Referenzen); diese Sammlung ist abgeschlossen.

Dieses PEP schlägt vor, die CI-Freigabe in die folgende Sequenz umzuwandeln (neue Schritte sind fett gedruckt):

  1. Schwache Referenzen auf CI-Objekte werden gelöscht und ihre Rückrufe aufgerufen. Zu diesem Zeitpunkt sind die Objekte noch sicher zu verwenden.
  2. Die Finalisierer aller CI-Objekte werden aufgerufen.
  3. Das CI wird erneut durchlaufen, um festzustellen, ob es immer noch isoliert ist. Wenn festgestellt wird, dass mindestens ein Objekt im CI jetzt von außerhalb des CI erreichbar ist, wird diese Sammlung abgebrochen und das gesamte CI wiederbelebt. Andernfalls wird fortgefahren.
  4. Das CI wird zu einem CT, da der GC systematisch alle bekannten Referenzen darin löscht (mithilfe der `tp_clear`-Funktion).
  5. Nichts. Alle CT-Objekte sollten in Schritt 4 freigegeben worden sein (als Nebenwirkung des Löschens von Referenzen); diese Sammlung ist abgeschlossen.

Hinweis

Der GC berechnet das CI nach Schritt 2 oben nicht neu, daher ist Schritt 3 erforderlich, um zu überprüfen, ob der gesamte Teilgraph noch isoliert ist.

Änderungen auf C-Ebene

Typobjekte erhalten einen neuen `tp_finalize`-Slot, dem `__del__`-Methoden zugeordnet sind (und umgekehrt). Generatoren werden modifiziert, um diesen Slot anstelle von `tp_del` zu verwenden. Eine `tp_finalize`-Funktion ist eine normale C-Funktion, die mit einem gültigen und lebendigen `PyObject` als einzigem Argument aufgerufen wird. Sie muss den Referenzzähler des Objekts nicht manipulieren, da dies vom Aufrufer übernommen wird. Sie muss jedoch sicherstellen, dass der ursprüngliche Ausnahmezustand vor der Rückkehr zum Aufrufer wiederhergestellt wird.

Aus Kompatibilitätsgründen bleibt `tp_del` in der Typenstruktur erhalten. Die Handhabung von Objekten mit einem nicht-NULL `tp_del` bleibt unverändert: Wenn sie Teil eines CI sind, werden sie nicht finalisiert und landen in `gc.garbage`. Ein nicht-NULL `tp_del` wird jedoch im CPython-Quellbaum (außer zu Testzwecken) nicht mehr angetroffen.

Zwei neue C-API-Funktionen werden bereitgestellt, um das Aufrufen von `tp_finalize` zu erleichtern, insbesondere aus benutzerdefinierten Deallokatoren.

Intern wird ein Bit im GC-Header für von GC verwaltete Objekte reserviert, um zu signalisieren, dass sie finalisiert wurden. Dies hilft zu vermeiden, ein Objekt zweimal zu finalisieren (und insbesondere ein CT-Objekt zu finalisieren, nachdem es vom GC unterbrochen wurde).

Hinweis

Objekte, die nicht GC-aktiviert sind, können ebenfalls einen `tp_finalize`-Slot haben. Sie benötigen das zusätzliche Bit nicht, da ihre `tp_finalize`-Funktion nur aus dem Deallokator aufgerufen werden kann: Sie kann daher nicht zweimal aufgerufen werden, es sei denn, sie wird wiederbelebt.

Diskussion

Vorhersagbarkeit

Nach diesem Schema wird der Finalisierer eines Objekts immer genau einmal aufgerufen, auch wenn es danach wiederbelebt wurde.

Für CI-Objekte ist die Reihenfolge, in der Finalisierer aufgerufen werden (Schritt 2 oben), undefiniert.

Sicherheit

Es ist wichtig zu erklären, warum die vorgeschlagene Änderung sicher ist. Es gibt zwei Aspekte zu diskutieren:

  • Kann ein Finalisierer auf Zombie-Objekte zugreifen (einschließlich des zu finalisierenden Objekts)?
  • Was passiert, wenn ein Finalisierer den Objektgraphen so verändert, dass er das CI beeinträchtigt?

Lassen Sie uns das erste Problem diskutieren. Wir werden mögliche Fälle in zwei Kategorien unterteilen:

  • Wenn das zu finalisierende Objekt Teil des CI ist: Per Konstruktion sind noch keine Objekte im CI Zombies, da CI-Finalisierer aufgerufen werden, bevor jegliche Referenzunterbrechung erfolgt. Daher kann der Finalisierer nicht auf Zombie-Objekte zugreifen, die nicht existieren.
  • Wenn das zu finalisierende Objekt nicht Teil des CI/CT ist: Per Definition haben Objekte im CI/CT keine Referenzen, die von außerhalb des CI/CT auf sie zeigen. Daher kann der Finalisierer kein Zombie-Objekt erreichen (selbst wenn das zu finalisierende Objekt selbst von einem Zombie-Objekt referenziert wurde).

Nun zum zweiten Problem. Es gibt drei mögliche Fälle:

  • Der Finalisierer löscht eine bestehende Referenz auf ein CI-Objekt. Das CI-Objekt kann vor der Unterbrechung durch den GC freigegeben werden, was in Ordnung ist (der GC muss sich dieser Möglichkeit einfach bewusst sein).
  • Der Finalisierer erstellt eine neue Referenz auf ein CI-Objekt. Dies kann nur von einem CI-Objekt-Finalisierer geschehen (siehe oben, warum). Daher wird die neue Referenz vom GC erkannt, nachdem alle CI-Finalisierer aufgerufen wurden (Schritt 3 oben), und die Sammlung wird abgebrochen, ohne dass Objekte unterbrochen werden.
  • Der Finalisierer löscht oder erstellt eine Referenz auf ein Nicht-CI-Objekt. Dies ist per Konstruktion kein Problem.

Implementierung

Eine Implementierung ist im Branch `finalize` des Repositories unter http://hg.python.org/features/finalize/ verfügbar.

Validierung

Neben der Ausführung der normalen Python-Testsuite fügt die Implementierung Testfälle für verschiedene Finalisierungsmöglichkeiten hinzu, einschließlich Referenzzyklen, Objektwiederbelebung und Legacy-`tp_del`-Slots.

Die Implementierung wurde auch daraufhin überprüft, keine Regressionen auf den folgenden Testsuiten zu verursachen:

Referenzen

Hinweise zu Referenzzyklussammlung und Rückrufen schwacher Referenzen: http://hg.python.org/cpython/file/4e687d53b645/Modules/gc_weakref.txt

Speicherleck bei Generatoren: http://bugs.python.org/issue17468

Objekten die Entscheidung ermöglichen, ob sie vom GC gesammelt werden können: http://bugs.python.org/issue9141

Modul-Shutdown-Verfahren basierend auf GC: http://bugs.python.org/issue812369


Quelle: https://github.com/python/peps/blob/main/peps/pep-0442.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT