Python Objekt Lebensdauer (Lifetime) (C) 2017-2023 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 Referenzzä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 welt" # 1. Referenz auf str-Objekt "hallo welt" ref1 = text # 2. Referenz auf str-Objekt "hallo welt" ref2 = [ 1, 2, text ] # 3. Referenz auf str-Objekt "hallo welt" assert id(text) == id(ref1) == id(ref2[2]) # OK 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 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 Objekte freigegeben) del l1 # Referenz auf l1 löschen del l2 # Referenz auf l2 löschen # --> Objekte im Zyklus l1 <-> l2 bleiben erhalten obwohl nicht erreichbar! gc.collect() # --> 2 (d.h. Speicher für 2 Objekte freigegeben) gc.collect() # --> 0 (d.h. Speicher für 0 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 zwischen dem inneren und dem äußeren Programmende 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 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 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 Objekte 1, 2 und 3 übergeben; # erg enthält zurückgegebene Referenz auf # in Funktion erzeugtes Tupel-Objekt zugewiesen PARAMETER-Variablen zeigen also auf Objekte, die von außen stammen (d.h. sie sind LOKALE NAMEN, die von außen mit Objekten initialisiert werden) und stellen eine weitere Referenz auf diese da. Sofern diese Objekte IM-MUTABLE sind, ist das problemlos. Falls ein übergebenes 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 (Funktion aufrufen, globales Objekt übergeben) print(len(lst), lst) # --> 3 ["a", "b", "c"] (globales Objekt verändert) In einer Funktion NEU ERZEUGTE OBJEKTE können problemlos als return-Wert 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 Funktion erzeugtes Objekt print(erg) # --> (3, -1)