Python Wert/Objekt Lebensdauer (Lifetime) (C) 2017-2024 T.Birnthaler OSTC GmbH ========================================= --> python-scope.txt Scope/Sichtbarkeitsbereich --> python-namespace.txt Namespace/Namensraum --> python-lifetime.txt Lebensdauer/Lifetime/Existenz/Gültigkeitsbereich Der Begriff "Lebensdauer/Lifetime/Existenz/Gültigkeitsbereich" (wie lange ein Python-Objekt = Wert/Objekt existiert und benutzbar ist) hat mit den Begriffen "Scope/Sichtbarkeitsbereich" und "Namespace/Namensraum" nichts zu tun! In Python ist ein WERT/OBJEKT existent und benutzbar ("am Leben"), solange MINDESTENS EINE REFERENZ darauf zeigt und diese REFERENZ von Python aus direkt per NAME oder indirekt über andere Wert/Objekte erreichbar ist. Sobald die LETZTE REFERENZ auf ein Wert/Objekt verschwindet, ist es nicht mehr erreichbar und sein Speicherplatz kann von Python für andere Zwecke verwendet werden. Python zählt daher die ANZAHL der Referenzen auf ein Wert/Objekt ständig in einem "Reference Counter" mit, der Teil des Wert/Objektes ist (abfragbar per sys.getrefcount(OBJ)). Jede zusätzliche Referenz erhöht den Referenz-Zähler um 1, jede aufgelöste Referenz verringert den Referenz-Zähler um 1. Erreicht der Referenz-Zähler den Wert 0, ist sichergestellt, dass ein Wert/Objekt nicht mehr erreichbar ("referenzierbar") und damit benutzbar ist und sein Speicherplatz wieder für andere Aufgaben zur Verfügung steht. text = "hallo" # 1. Referenz auf str-Objekt "hallo" ref1 = text # 2. Referenz auf str-Objekt "hallo" ref2 = [ 1, 2, text ] # 3. Referenz auf str-Objekt "hallo" assert id(text) == id(ref1) == id(ref2[2]) # OK da ID immer die gleiche ref2[2] = 3 # 3. Referenz auf str-Objekt zerstört ref1 = 123 # 2. Referenz auf str-Objekt zerstört del text # 1. Referenz auf str-Objekt zerstört (Name gelöscht) Sich gegenseitig ZYKLISCH referenzierende aber insgesamt nicht mehr erreichbare Wert/Objekte werden von einer periodisch stattfindenden GARBAGE COLLECTION aufgeräumt (Modul "gc"): import gc # Modul "gc" importieren l1 = [] # --> l1 ist leere Liste l2 = [l1] # --> Liste l2 referenziert Liste l1 l1.append(l2) # --> Liste l1 referenziert Liste l2 --> Zyklus! print(l1) # --> [[[...]]] --> Zyklus! print(l2) # --> [[[...]]] --> Zyklus! gc.collect() # --> ? (0 oder beliebiger anderer Wert) gc.collect() # --> 0 (d.h. Speicher für 0 Wert/Objekte freigegeben) del l1 # --> Name l1 löschen --> Referenz-Zähler sinkt um 1 del l2 # --> Name l2 löschen --> Referenz-Zähler sinkt um 1 # --> Wert/Objekte im Zyklus l1 <-> l2 bleiben erhalten obwohl nicht erreichbar! gc.collect() # --> 2 (d.h. Speicher für 2 Wert/Objekte freigegeben) gc.collect() # --> 0 (d.h. Speicher für 0 Wert/Objekte freigegeben) Am Programmende löscht der Python-Interpreter immer ALLE Referenzen und daher werden auch ALLE Wert/Objekte AUFGERÄUMT (d.h. ihr Speicherplatz wird freigegeben). Dies kann durchaus zu einer WARTEZEIT nach der Ausführung der letzten Anweisung bis zum endgültigen Verlassen des Programms führen. ACHTUNG: Das Verhalten im Rahmen einer IDE (Integrated Development Environment) ist anders, da der Python-Interpreter am Programmende von der IDE üblicherweise NICHT automatisch beendet wird (damit man noch alle im Programm definierten Wert/Objekte nutzen kann). In der Regel wird der Python-Interpreter in einer IDE erst DIREKT VOR dem nächsten Programmlauf beendet. Datenübergabe (d.h. Übergabe von Wert/Objekten) an Funktionen und Datenrückgabe aus Funktionen erfolgt in Python grundsätzlich in Form von REFERENZEN (CALL BY/RETURN BY REFERENCE). Diese Art der Datenübergabe ist sogar sehr EFFIZIENT: def f(a, b, c): # Referenzen auf Wert/Objekte in a, b, c übergeben; t = (a + b, b + c) # Neues Tupel-Objekt mit Name t erzeugt return t # Referenz auf Tupel-Objekt zurückgeben erg = f(1, 2, 3) # Referenzen auf Wert/Objekte 1, 2 und 3 übergeben; # erg enthält zurückgegebene Referenz auf # in Funktion erzeugtes Tupel-Objekt zugewiesen PARAMETER-Variablen zeigen also auf Wert/Objekte, die VON AUSSEN stammen (d.h. sie sind LOKALE NAMEN, die von außen mit Wert/Objekten initialisiert werden) und stellen eine weitere Referenz auf diese da. Sofern diese Wert/Objekte IM-MUTABLE sind, ist das problemlos. Falls ein übergebenes Wert/Objekt MUTABLE ist, kann durch Manipulation seines INNEREN in der Funktion ein SEITENEFFEKT (side-effect) nach außen entstehen (den man dem Funktionaufruf nicht ansieht). def f(a): # --> Lokale Variable a von außen initialisieren a.append("c") # --> Inneres von lokaler Variable a manipulieren return len(a) # --> Länge von lokaler Variable a zurückgeben # lst = ["a", "b"] # --> Globales list-Objekt erzeugen print(len(lst), lst) # --> 2 ["a", "b"] print(f(lst)) # --> 3 (Fkt. aufrufen, globales Wert/Objekt übergeben) print(len(lst), lst) # --> 3 ["a", "b", "c"] (globales Wert/Objekt verändert) In einer Funktion NEU ERZEUGTE WERT/OBJEKTE können problemlos als RÜCKGABEWERT per "return" zurückgegeben werden (auch wenn der lokale Variablenname außerhalb der Funktion nicht mehr existiert), da schon eine einzige Referenz auf sie außerhalb der Funktion dafür sorgt, dass sie nicht freigegeben werden können. def f(a, b): # tpl = (a+b, a-b) # --> Tupel-Objekt erzeugen, Name tpl ist lokal return tpl # --> Tupel-Objekt zurückgeben, Name tpl verschwindet # erg = f(1, 2) # --> Funktion aufrufen + Rückgabe-Wert speichern # --> Name "erg" zeigt auf in Fkt. erzeugtes Wert/Objekt print(erg) # --> (3, -1) ACHTUNG: Per Schlüsselwort "del" (delete) wird ein NAME gelöscht und die darin abgelegte REFERENZ auf ein Wert/Objekt entfernt. D.h. der Referenz-Zähler eines Wert/Objekts sinkt dadurch um 1. Weder wird dadurch der Destruktor "__del__" des Wert/Objekts aufgerufen noch wird sein Speicherplatz freigegeben. Sobald der Referenz-Zähler eines Wert/Objekts auf 0 sinkt, ruft Python den zugehörigen DESTRUKTOR "__del__" (delete) auf (falls er definiert ist). Dieser dient dazu, evtl. von dem Wert/Objekt belegte RESSOURCEN (z.B. Dateien, Netzwerk-Verbindungen, Datenbank-Anmeldung, Login, Speicher, ...) freizugeben. Der SPEICHERPLATZ des Wert/Objekts wird weder durch "del" noch durch das Sinken des Referenz-Zählers auf 0 freigegeben und wiederverwendet. Dies entscheidet und erledigt Python selbständig zum einem beliebigen späteren Zeitpunkt. Zwei Wert/Objekte mit NICHT ÜBERLAPPENDER Lebensdauer können während der Laufzeit eines Programms sogar die gleiche ID bekommen, da der einem Wert/Objekt zugeordnete Speicher nur dann für andere Zwecke wiederverwendet wird, falls keine einzige Referenz mehr darauf existiert, d.h. der Speicher hinter dem Wert/Objekt nicht mehr erreichbar ist und im "Nirwana schwebt". An der Ausgabe der folgenden Schleife kann man dieses Verhalten beobachten, da 2 verschiedene IDs alternierend zum Speichern des Wert/Objekts 123456 ... 123465 genutzt werden. for i in range(123456, 123456+10): print("I", i, id(i)) HINWEIS: Aus Effizienzgründen werden einige häufig benötigte Wert/Objekte wie die Ganzzahlen -5 .. 256, die Booleschen Werte True und False, der Wert None, der leere String "" sowie die Fließkommawerte 0.0 und 1.0 bereits beim Start des Python-Interpreters erzeugt und während dem Programmlauf nie freigegeben (IMMORTAL Wert/Objekte). Erreicht wird dies durch Setzen eines speziellen Referenz-Zählerwerts 4294967295 = 2^32 - 1 in diesen Wert/Objekten, der das Hoch/Runterzählen des Referenz-Zählers unterbindet. Bei obigen Wert/Objekten ist somit auch garantiert, dass sie nur 1x erzeugt und immer wiederverwendet werden. Andere IM-MUTABLE Wert/Objekte können MEHRFACH mit dem gleichen Inhalt erzeugt werden, d.h. für Sie kann zwar gelten OBJ1 == OBJ2 (wertemäßig gleich), aber daraus folgt nicht unbedingt OBJ1 is OBJ2 (Wert/Objekt identisch, d.h. "geteilt") text1 = "welt" # text2 = "we" # text2 = text2 + "lt" # print("TEXT1", text1, id(text1)) # --> welt 432946942 print("TEXT2", text2, id(text2)) # --> welt 432946308 print("TEXT1 == TEXT2:", text1 == text2) # --> True print("TEXT1 is TEXT2:", text1 is text2) # --> False