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

Python Enhancement Proposals

PEP 205 – Schwache Referenzen

Autor:
Fred L. Drake, Jr. <fred at fdrake.net>
Status:
Final
Typ:
Standards Track
Erstellt:
14-Jul-2000
Python-Version:
2.1
Post-History:
11-Jan-2001

Inhaltsverzeichnis

Motivation

Es gibt zwei grundlegende Anwendungen für schwache Referenzen, die von Python-Programmierern bemerkt wurden: Objekten-Caches und die Reduzierung von Problemen durch zirkuläre Referenzen.

Caches (schwache Dictionaries)

Es besteht die Notwendigkeit, Objekte zu verwalten, die externen Zustand repräsentieren und eine einzelne Instanz der externen Realität zuordnen, wobei die Zulassung mehrerer Instanzen, die derselben externen Ressource zugeordnet sind, die Synchronisation zwischen den Instanzen unnötig erschweren würde. In diesen Fällen ist die Unterstützung eines Caches von Instanzen ein gängiges Idiom; eine Factory-Funktion wird verwendet, um entweder eine neue oder eine bestehende Instanz zurückzugeben.

Die Schwierigkeit bei diesem Ansatz besteht darin, dass eines von zwei Dingen toleriert werden muss: Entweder wächst der Cache unbegrenzt, oder es muss eine explizite Verwaltung des Caches an anderer Stelle in der Anwendung erfolgen. Letzteres kann sehr mühsam sein und führt zu mehr Code, als zur Lösung des Problems eigentlich notwendig ist, und ersteres kann für langlaufende Prozesse oder sogar relativ kurze Prozesse mit erheblichen Speicheranforderungen inakzeptabel sein.

  • Externe Objekte, die durch eine einzelne Instanz repräsentiert werden müssen, unabhängig davon, wie viele interne Benutzer es gibt. Dies kann nützlich sein, um Dateien darzustellen, die als Ganzes zurück auf die Festplatte geschrieben werden müssen, anstatt für jeden Gebrauch gesperrt und modifiziert zu werden.
  • Objekte, deren Erstellung teuer ist, die aber möglicherweise von mehreren internen Konsumenten benötigt werden. Ähnlich wie im ersten Fall, aber nicht unbedingt an externe Ressourcen gebunden und möglicherweise kein Problem für gemeinsam genutzten Zustand. Schwache Referenzen sind in diesem Fall nur nützlich, wenn es eine Art von "weichen" Referenzen gibt oder wenn die Wahrscheinlichkeit hoch ist, dass die Benutzer einzelner Objekte eine überlappende Lebensdauer haben.

Zirkuläre Referenzen

  • DOMs erfordern eine riesige Menge an zirkulären (zu Eltern- und Dokumentknoten) Referenzen, aber diese könnten eliminiert werden, indem ein schwaches Dictionary verwendet wird, das jeden Knoten seinem Elternknoten zuordnet. Dies könnte besonders nützlich sein im Kontext von etwas wie xml.dom.pulldom, wodurch die Operation .unlink() zu einem No-Op werden könnte.

Dieser Vorschlag ist in die folgenden Abschnitte unterteilt

  • Vorgeschlagene Lösung
  • Implementierungsstrategie
  • Mögliche Anwendungen
  • Frühere Arbeiten zu schwachen Referenzen in Python
  • Schwache Referenzen in Java

Der vollständige Text eines frühen Vorschlags ist als Anhang enthalten, da er nicht online verfügbar zu sein scheint.

Aspekte des Lösungsraums

Es gibt zwei unterschiedliche Aspekte des Problems der schwachen Referenzen

  • Invalidierung von schwachen Referenzen
  • Darstellung von schwachen Referenzen für Python-Code

Invalidierung

Frühere Ansätze zur Invalidierung von schwachen Referenzen basierten oft darauf, eine starke Referenz zu speichern und alle Instanzen von schwachen Referenzobjekten prüfen zu können, um sie zu invalidieren, wenn der Referenzzähler ihres Referenten auf eins fällt (was anzeigt, dass die vom schwachen Referenz gespeicherte Referenz die letzte verbleibende Referenz ist). Dies hat den Vorteil, dass die Speicherverwaltung in Python nicht geändert werden muss und dass jeder Typ schwach referenziert werden kann.

Der Nachteil dieses Invalidierungsansatzes ist, dass er davon ausgeht, dass die Verwaltung der schwachen Referenzen häufig genug aufgerufen wird, sodass schwach referenzierte Objekte innerhalb eines angemessenen Zeitrahmens bemerkt werden. Da dies einen Scan einer Datenstruktur zur Invalidierung von Referenzen bedeutet, eine Operation, die O(N) bezüglich der Anzahl der schwach referenzierten Objekte ist, wird dies für ein einzelnes schwach referenziertes Objekt nicht effektiv amortisiert. Dies setzt auch voraus, dass die Anwendung Code aufruft, der schwach referenzierte Objekte häufig behandelt, was schwache Referenzen für Bibliotheks-Code weniger attraktiv macht.

Ein alternativer Ansatz zur Invalidierung besteht darin, dass der Deallokationscode die Möglichkeit von schwachen Referenzen berücksichtigt und beim Deallokieren eines Objekts einen spezifischen Aufruf an den schwachen Referenzverwaltungs-Code zur Invalidierung vornimmt. Dies erfordert eine Änderung im tp_dealloc-Handler für schwach referenzierbare Objekte; ein zusätzlicher Aufruf ist am "Anfang" des Handlers für Objekte erforderlich, die schwache Referenzierung unterstützen, und es wird eine effiziente Möglichkeit benötigt, von einem Objekt zu einer Kette von schwachen Referenzen für dieses Objekt zu gelangen.

Darstellung

Zwei Möglichkeiten, wie schwache Referenzen der Python-Ebene präsentiert wurden, sind explizite Referenzobjekte, mit denen eine Operation ausgeführt werden muss, um eine nutzbare Referenz auf das zugrunde liegende Objekt abzurufen, und Proxy-Objekte, die die ursprünglichen Objekte so weit wie möglich maskieren.

Referenzobjekte sind einfach zu handhaben, wenn zusätzliche Objektverwaltungs-Schichten in Python hinzugefügt werden. Referenzen können explizit auf Lebendigkeit geprüft werden, ohne Operationen auf den Referenten aufrufen und eine spezielle Ausnahme abfangen zu müssen, die ausgelöst wird, wenn eine ungültige schwache Referenz verwendet wird.

Eine Reihe von Benutzern bevorzugen jedoch den Proxy-Ansatz, einfach weil die schwache Referenz dem ursprünglichen Objekt so sehr ähnelt.

Vorgeschlagene Lösung

Schwache Referenzen sollten in der Lage sein, auf jedes Python-Objekt zu verweisen, das eine erhebliche Speichergröße haben kann (direkt oder indirekt) oder Referenzen auf externe Ressourcen hält (Datenbankverbindungen, offene Dateien usw.).

Ein neues Modul, weakref, wird neue Funktionen zum Erstellen schwacher Referenzen enthalten. weakref.ref() erstellt ein "schwaches Referenzobjekt" und hängt optional eine Callback-Funktion an, die aufgerufen wird, wenn das Objekt kurz vor der Finalisierung steht. weakref.mapping() erstellt ein "schwaches Dictionary". Eine dritte Funktion, weakref.proxy(), erstellt ein Proxy-Objekt, das sich etwas wie das ursprüngliche Objekt verhält.

Ein schwaches Referenzobjekt ermöglicht den Zugriff auf das referenzierte Objekt, falls es noch nicht gesammelt wurde, und bestimmt, ob das Objekt noch im Speicher vorhanden ist. Das Abrufen des Referenten erfolgt durch Aufruf des Referenzobjekts. Wenn der Referent nicht mehr lebendig ist, gibt dies stattdessen None zurück.

Ein schwaches Dictionary bildet beliebige Schlüssel auf Werte ab, besitzt aber keine Referenz auf die Werte. Wenn die Werte finalisiert werden, werden die (Schlüssel, Wert)-Paare, für die es ein Wert ist, aus allen Dictionaries entfernt, die solche Paare enthalten. Wie Dictionaries sind schwache Dictionaries nicht hashbar.

Proxy-Objekte sind schwache Referenzen, die versuchen, sich so gut wie möglich wie das Objekt zu verhalten, das sie proxyen. Unabhängig vom zugrunde liegenden Typ sind Proxies nicht hashbar, da ihre Fähigkeit, als schwache Referenz zu fungieren, auf einer grundlegenden Veränderlichkeit beruht, die zu Fehlern bei der Verwendung als Dictionary-Schlüssel führt – selbst wenn der richtige Hashwert berechnet wird, bevor der Referent stirbt, kann der resultierende Proxy nicht als Dictionary-Schlüssel verwendet werden, da er nicht verglichen werden kann, sobald der Referent abgelaufen ist, und Vergleichbarkeit ist für Dictionary-Schlüssel erforderlich. Operationen auf Proxy-Objekten nach dem Tod des Referenten lösen in den meisten Fällen weakref.ReferenceError aus. "is"-Vergleiche, type() und id() funktionieren weiterhin, beziehen sich aber immer auf den Proxy und nicht auf den Referenten.

Die an schwache Referenzen registrierten Callbacks müssen einen einzigen Parameter akzeptieren, bei dem es sich um die schwache Referenz oder das Proxy-Objekt selbst handelt. Das Objekt kann im Callback nicht zugegriffen oder wiederbelebt werden.

Implementierungsstrategie

Die Implementierung schwacher Referenzen beinhaltet eine Liste von Referenz-Containern, die für jedes schwach referenzierbare Objekt gelöscht werden müssen. Wenn die Referenz aus einem schwachen Dictionary stammt, wird zuerst der Dictionary-Eintrag gelöscht. Dann wird jeder zugehörige Callback mit dem Objekt als Parameter aufgerufen. Sobald alle Callbacks aufgerufen wurden, wird das Objekt finalisiert und deallokiert.

Viele eingebaute Typen werden an der Verwaltung schwacher Referenzen teilnehmen, und jeder Erweiterungstyp kann sich dafür entscheiden. Die Typstruktur enthält ein zusätzliches Feld, das einen Offset in der Instanzstruktur angibt, die eine Liste von schwachen Referenzstrukturen enthält. Wenn der Wert des Feldes <= 0 ist, nimmt das Objekt nicht teil. In diesem Fall lösen weakref.ref(), <weakdict>.__setitem__() und .setdefault() sowie die Zuweisung von Elementen TypeError aus. Wenn der Wert des Feldes > 0 ist, kann eine neue schwache Referenz generiert und der Liste hinzugefügt werden.

Dieser Ansatz wird gewählt, um beliebigen Erweiterungstypen die Teilnahme zu ermöglichen, ohne einen Speicher-Hit für Zahlen oder andere kleine Typen zu verursachen.

Standardtypen, die schwache Referenzen unterstützen, umfassen Instanzen, Funktionen sowie gebundene und ungebundene Methoden. Mit der Hinzufügung von Klassentypen ("new-style classes") in Python 2.2 wuchs die Unterstützung für schwache Referenzen für Typen. Instanzen von Klassentypen sind schwach referenzierbar, wenn sie einen Basistyp haben, der schwach referenzierbar ist, die Klasse nicht __slots__ spezifiziert oder ein Slot namens __weakref__ ist. Generatoren unterstützen ebenfalls schwache Referenzen.

Mögliche Anwendungen

PyGTK+-Bindings?

Tkinter – könnte zirkuläre Referenzen vermeiden, indem schwache Referenzen von Widgets zu ihren Eltern verwendet werden. Objekte werden im typischen Fall nicht früher verworfen, aber es gibt nicht mehr so starke Abhängigkeit von der Programmierung, die .destroy() aufruft, bevor eine Referenz freigegeben wird. Dies würde hauptsächlich langlaufenden Anwendungen zugute kommen.

DOM-Bäume.

Frühere Arbeiten zu schwachen Referenzen in Python

Dianne Hackborn hat etwas namens "virtuelle Referenzen" vorgeschlagen. 'vref'-Objekte sind den java.lang.ref.WeakReference Objekten sehr ähnlich, außer dass es kein Äquivalent zu den Invalidierungs-Queues gibt. Die Implementierung eines "schwachen Dictionarys" wäre genauso schwierig wie die Verwendung von schwachen Referenzen (ohne die Invalidierungs-Queue) in Java. Informationen hierzu sind aus dem Web verschwunden, sind aber unten als Anhang aufgeführt.

Marc-André Lemburgs mx.Proxy-Paket

Das weakdict-Modul von Dieter Maurer ist in C und Python implementiert. Es scheint, dass die Webseiten seit Python 1.5.2a nicht mehr aktualisiert wurden, daher bin ich mir noch nicht sicher, ob die Implementierung mit Python 2.0 kompatibel ist.

PyWeakReference von Alex Shindich

Eric Tiedemann hat eine Implementierung für schwache Dictionaries

Schwache Referenzen in Java

http://java.sun.com/j2se/1.3/docs/api/java/lang/ref/package-summary.html

Java bietet drei Formen von schwachen Referenzen und eine interessante Hilfsklasse. Die drei Formen werden als "weak", "soft" und "phantom" Referenzen bezeichnet. Die relevanten Klassen sind im Paket java.lang.ref definiert.

Für jeden Referenztyp gibt es die Option, die Referenz einer Queue hinzuzufügen, wenn sie vom Speicherzuweiser invalidiert wird. Der Hauptzweck dieser Einrichtung scheint darin zu bestehen, dass sie es ermöglicht, größere Strukturen zu komponieren, um schwache Referenzsemantik zu integrieren, ohne erhebliche zusätzliche Sperranforderungen aufzuerlegen. Zum Beispiel wäre es nicht schwierig, diese Einrichtung zu nutzen, um eine "schwache" Hashtabelle zu erstellen, die Schlüssel und Referenten entfernt, wenn eine Referenz woanders nicht mehr verwendet wird. Die Verwendung schwacher Referenzen für die Objekte ohne eine Art von Benachrichtigungs-Queue für Invalidierungen führt zu einer mühsameren Implementierung der verschiedenen Operationen, die auf Hashtabellen erforderlich sind. Dies kann ein Leistungsengpass sein, wenn die Deallokationen der gespeicherten Objekte selten sind.

Javas "weak" Referenzen ähneln Dianne Hackborns altem vref-Vorschlag am meisten: Ein Referenzobjekt verweist auf ein einzelnes Python-Objekt, besitzt aber keine Referenz auf dieses Objekt. Wenn dieses Objekt deallokiert wird, wird das Referenzobjekt invalidiert. Benutzer des Referenzobjekts können leicht feststellen, dass die Referenz invalidiert wurde, oder es kann ein NullObjectDereferenceError ausgelöst werden, wenn versucht wird, das referenzierte Objekt zu verwenden.

Die "soft" Referenzen sind ähnlich, werden aber nicht so schnell invalidiert, wie alle anderen Referenzen auf das referenzierte Objekt freigegeben wurden. Die "soft" Referenz besitzt zwar eine Referenz, erlaubt aber dem Speicherzuweiser, den Referenten freizugeben, wenn der Speicher anderswo benötigt wird. Es ist nicht klar, ob dies bedeutet, dass Soft-Referenzen vor dem Aufruf der malloc()-Implementierung sbrk() oder sein Äquivalent freigegeben werden, oder ob Soft-Referenzen nur gelöscht werden, wenn malloc() NULL zurückgibt.

"Phantom" Referenzen sind etwas anders; im Gegensatz zu schwachen und weichen Referenzen wird der Referent nicht gelöscht, wenn die Referenz zu ihrer Queue hinzugefügt wird. Wenn alle Phantom-Referenzen für ein Objekt aus der Queue entfernt wurden, wird das Objekt gelöscht. Dies kann verwendet werden, um ein Objekt am Leben zu erhalten, bis eine zusätzliche Bereinigung durchgeführt wird, die erfolgen muss, bevor die .finalize()-Methode des Objekts aufgerufen wird.

Im Gegensatz zu den beiden anderen Referenztypen müssen "phantom" Referenzen mit einer Invalidierungs-Queue verknüpft sein.

Anhang – Dianne Hackborns vref-Vorschlag (1995)

[Dies wurde eingerückt und Absätze neu formatiert, aber es gab keine Inhaltsänderungen. – Fred]

Vorschlag: Virtuelle Referenzen

In dem Versuch, die wiederkehrende Diskussion über Referenzzählung vs. Garbage Collection teilweise zu lösen, möchte ich eine Erweiterung von Python vorschlagen, die bei der Erstellung von "gut strukturierten" zirkulären Graphen helfen sollte. Insbesondere sollte sie mindestens Bäume mit Eltern-Rückverweise und doppelt verkettete Listen ohne die Sorge vor Zyklen ermöglichen.

Der grundlegende Mechanismus, den ich vorschlagen möchte, ist der einer "virtuellen Referenz" oder von hier an "vref". Eine vref ist im Wesentlichen ein Handle auf ein Objekt, das den Referenzzähler des Objekts nicht erhöht. Das bedeutet, dass das Halten einer vref auf einem Objekt das Objekt nicht davon abhält, zerstört zu werden. Dies würde es dem Python-Programmierer ermöglichen, zum Beispiel die oben erwähnte Baumstruktur zu erstellen, die automatisch zerstört wird, wenn sie nicht mehr benötigt wird – indem alle Eltern-Rückverweise zu vrefs gemacht werden, erzeugen sie keine Referenzzyklen mehr, die die Zerstörung des Baumes verhindern.

Um diesen Mechanismus zu implementieren, muss der Python-Kern sicherstellen, dass niemals "reale" Zeiger auf Objekte zeigen, die nicht mehr existieren. Die Implementierung, die ich vorschlagen möchte, beinhaltet zwei grundlegende Ergänzungen zum aktuellen Python-System:

  1. Ein neuer "vref"-Typ, über den der Python-Programmierer virtuelle Referenzen erstellt und manipuliert. Intern ist es im Grunde ein Python-Objekt auf C-Ebene mit einem Zeiger auf das Python-Objekt, auf das es sich bezieht. Im Gegensatz zu allem anderen Python-Code ändert es jedoch nicht den Referenzzähler dieses Objekts. Zusätzlich enthält es zwei Zeiger zur Implementierung einer doppelt verketteten Liste, die unten verwendet wird.
  2. Die Hinzufügung eines neuen Feldes zum grundlegenden Python-Objekt [PyObject_Head in object.h], das entweder NULL ist oder auf den Kopf einer Liste aller vref-Objekte zeigt, die darauf verweisen. Wenn sich ein vref-Objekt an ein anderes Objekt anhängt, fügt es sich dieser verknüpften Liste hinzu. Wenn dann ein Objekt mit vrefs darauf deallokiert wird, kann es diese Liste durchlaufen und sicherstellen, dass alle vrefs darauf auf einen sicheren Wert, z. B. Nothing, zeigen.

Diese Implementierung sollte hoffentlich minimale Auswirkungen auf den aktuellen Python-Kern haben – wenn keine vrefs existieren, sollte sie nur einen Zeiger zu allen Objekten hinzufügen und bei jeder Deallokation eines Objekts eine Prüfung auf einen NULL-Zeiger.

Auf der Ebene der Python-Sprache habe ich zwei mögliche Semantiken für das vref-Objekt in Betracht gezogen –

Pointer-Semantik

In diesem Modell verhält sich eine vref im Wesentlichen wie ein Python-Zeiger; das Python-Programm muss die vref explizit dereferenzieren, um das tatsächliche Objekt zu manipulieren, auf das es verweist.

Ein Beispiel-vref-Modul, das dieses Modell verwendet, könnte die Funktion "new" enthalten. Wenn es als 'MyVref = vref.new(MyObject)' verwendet wird, gibt es ein neues vref-Objekt zurück, so dass MyVref.object == MyObject ist. MyVref.object würde dann auf Nothing geändert, wenn MyObject jemals deallokiert wird.

Für ein konkretes Beispiel können wir einige neue C-artige Syntax einführen

  • & – unärer Operator, erstellt eine vref auf einem Objekt, dasselbe wie vref.new().
  • * – unärer Operator, dereferenziert eine vref, dasselbe wie VrefObject.object.

Wir können dann definieren

1.     type(&MyObject) == vref.VrefType
2.        *(&MyObject) == MyObject
3. (*(&MyObject)).attr == MyObject.attr
4.          &&MyObject == Nothing
5.           *MyObject -> exception

Regel #4 ist subtil, ergibt sich aber daraus, dass wir eine vref auf (eine vref ohne echte Referenzen) erstellt haben. Daher wird die äußere vref zu Nothing gelöscht, wenn die innere unvermeidlich verschwindet.

Proxy-Semantik

In diesem Modell manipuliert der Python-Programmierer vref-Objekte so, als würde er das Objekt manipulieren, auf das es verweist. Dies wird erreicht, indem die vref so implementiert wird, dass alle Operationen darauf auf ihr referenziertes Objekt umgeleitet werden. Mit diesem Modell macht der Dereferenzierungsoperator (*) keinen Sinn mehr; stattdessen haben wir nur den Referenzoperator (&) und definieren

1.  type(&MyObject) == type(MyObject)
2.        &MyObject == MyObject
3. (&MyObject).attr == MyObject.attr
4.       &&MyObject == MyObject

Auch hier ist Regel #4 wichtig – hier ist die äußere vref tatsächlich eine Referenz auf das ursprüngliche Objekt und *nicht* die innere vref. Dies liegt daran, dass alle Operationen, die auf eine vref angewendet werden, tatsächlich auf ihr Objekt angewendet werden, so dass das Erstellen einer vref einer vref tatsächlich das Erstellen einer vref des Objekts der letzteren zur Folge hat.

Die erste, Pointer-Semantik, hat den Vorteil, dass sie sehr einfach zu implementieren wäre; der vref-Typ ist extrem einfach und erfordert mindestens ein einzelnes Attribut, Objekt, und eine Funktion zur Erstellung einer Referenz.

Ich mag jedoch die Proxy-Semantik wirklich. Sie belastet den Python-Programmierer nicht nur weniger, sondern ermöglicht auch nette Dinge wie die Verwendung einer vref überall dort, wo das tatsächliche Objekt verwendet würde. Leider wäre sie in der aktuellen Python-Implementierung wahrscheinlich extrem schwierig, wenn nicht praktisch unmöglich, zu implementieren. Ich habe jedoch einige Ideen, wie das gemacht werden könnte, wenn es interessant erscheint; eine Möglichkeit ist die Einführung neuer Typüberprüfungsfunktionen, die die vref behandeln. Dies würde hoffentlich ältere C-Module, die keine vrefs erwarten, einfach einen TypeError zurückgeben lassen, bis sie behoben werden können.

Schließlich gibt es noch einige weitere zusätzliche Fähigkeiten, die dieses System bieten könnte. Eine, die mir besonders interessant erscheint, beinhaltet die Ermöglichung, dass der Python-Programmierer "Destruktor"-Funktionen zu einer vref hinzufügen kann – diese Python-Funktion würde unmittelbar vor der Deallokation des referenzierten Objekts aufgerufen, was es einem Python-Programm ermöglichen würde, sich unsichtbar an ein anderes Objekt anzuhängen und zu beobachten, wie es verschwindet. Das scheint nett zu sein, obwohl ich noch keine praktischen Anwendungen dafür gefunden habe... :)

– Dianne


Source: https://github.com/python/peps/blob/main/peps/pep-0205.rst

Last modified: 2025-02-01 08:59:27 GMT