Speichermanagement in Python für R Entwickler

R-Programmierer leben das Paradigma des Unveränderlichen – und begehen dadurch Fehler bei ihren ersten Schritten in Python. Die Rede ist von Mutable und Immutable Objects und dem Objekt- und Speichermanagement der beiden dominierenden Data-Science Programmiersprachen. Erfahren Sie in diesem Beitrag wie unterschiedlich R und Python in ihren internen Prozessen mit Objekten umgehen und was Sie als R-Programmierer über das Objektmanagement wissen sollten, wenn Sie erste Programmiererfahrungen in Python sammeln.

  1. Mutable und Immutalbe Objects – Eine Einordnung
  2. R – Ein unveränderlicher Kosmos
  3. Python – Alles ist im Fluss
  4. Der klassische Fehler
  5. Fazit

Mutable und Immutalbe Objects – Eine Einordnung

Als mutable Object (veränderliches Objekt) bezeichnen wir Programmiersprachenübergreifend ein Objekt, welches, nachdem es erzeugt wurde, innerhalb einer Session wieder verändert werden kann. Im Gegenzug ist Objekt immutable (unveränderlich), welches nach seiner Erzeugung nicht wieder verändert werden kann.

Versuchen wir diese triviale Beschreibung anhand von Pseudo-Code aufzulösen. Wir erzeugen uns dafür eine Integervariable x und wiesen dieser den Wert 42 zu. Anschließend wenden wir auf x eine Funktion an, die uns den physikalischen Speicherort von x zurückgibt.

x = 42
locInMem(x)
>>> 12345

Unserem Objekt x weisen wir im nächsten Schritt einen anderen Wert zu und prüfen wieder die Addresse im Speicher. Grundsätzlich sind nun 2 Szenarien für diese Adresse denkbar:

1. Die Adresse für den neuen Wert der Variable x hat sich nicht geändert.
2. Die Adresse ist nun einen andere.

x = 43
locInMem(x)

>>> 12345 “’Szenario 1“‘
>>> 54321 “’Szenario 2“‘

Für ein veränderliches Objekt gilt das hypothetische erste Szenario: Wenn die Möglichkeit besteht, dass sich die Speicherreferenz auf x nach der Zuweisung des Wertes 43 nicht verändert hat, dann muss sich der Wert im Speicher selbst verändert haben – und es sich somit um den Typ mutable handeln.

Besteht dagegen das unbedingte Paradigma, dass sich aus der Zuweisung eine neue Referenz ergibt, handelt es sich um ein unveränderliches Objekt. In unserem Beispiel ist der Wert 43 demzufolge an anderer Stelle im Speicher zu finden – und auch wenn der Wert 42 nicht mehr über x referenziert wird, ist der Wert dennoch im Speicher vorhanden. Wenn wir mit einer Funktion direkt Werte aus dem Speicher lesen, könnten wir also weiterhin auf den Wert 42 zugreifen. Hier übergeben wir an die Funktion die Speicheradresse und erhalten den entsprechenden Wert:

getWithLoc(12345)
>>> 42

R – Das Unveränderliche

Wenn es um die Performance von R geht, fällt häufig ein Ausspruch wie: „Intern wird immer kopiert“. Die Ursache dafür liegt in dem R-Implementierungsgrundsatz alle Objekt immutable zu halten.

Folgendes (ausführbare) Beispiel zeigt dieses Verhalten. Darin wird ein Vektor erstellt und seine Adresse im Speicher ausgegeben. Anschließend wird ein Element dieses Vektors geändert und erneut die Adresse im Speicher erfragt. Bei der zweiten Abfrage hat sich die Adresse geändert. R verfährt hier nach dem Prinzip: Copy-On-Modify.

install.packages(‚pryr‘)
library(pryr)

x <- c(1,2,3)
pryr::address(x)
>>> „0x6d06b78“

x[1] <- 11
pryr::address(x)
>>> „0x6d2e4e8“

Python – Alles ist im Fluss

In der Python-Welt existieren sowohl unveränderliche als auch veränderliche Objekte. Beginnen wir mit dem Typ mutable.

Der Beispielcode macht nichts anderes als wir in dem R-Beispiel gesehen haben. Hier hat sich die Adresse des Objekts im Speicher allerdings nicht geändert.

x = [1,2,3]
id(x)
>>> „2755836095728“

x[0] = 11
id(x)
>>> „2755836095728“

Bestimmte Objekttypen sind in Python jedoch (und hier entsprechen sich Python und R 1:1) als unveränderlich implementiert. Dies trifft bspw. auf bool, int, float, char zu. Hier sehen wir anhand eines int-Objekts, dass sich dessen Referenz nach der Zuweisung geändert hat.

x = 42
print(id(x))
>>> „1500055312“

x = 43
print(id(x))
>>> „1500055344“

Der klassische Fehler

R-Entwickler die ihre ersten Schritte in Python gehen, können durch die ungewohnten Eigenschaften von Mutable-Objects sehr leicht in eine Falle tappen. Nehmen wir folgenden R-Beispielcode:

x <- c(1,2,3) “’Erstellen eines Vektors“‘
y <- x “’Kopieren des Vektors“‘

y[1] <- 42 “’Ändern von y“‘

Für einen R-Entwickler ist es selbstverständlich, dass x ein Vektor der Gestalt (1,2,3) ist, auch nachdem y geändert wurde. Intern wird bei jeder Änderung eines Objekts eine Kopie im Speicher vorgenommen.

In Python würde äquivalenter Code so aussehen:

x = [1,2,3]
y = x

y[0] = 42

Nachdem y geändert wurde, sind allerdings sowohl y als auch x der Gestalt (42,2,3). Python kopiert nicht das Objekt selbst, sondern die Referenz auf das Objekt.

Schauen wir uns noch einmal Grafisch an, was passiert, wenn die Zuweisung y=X erfolgt:

PythonVsR

Fazit

R und Python gehen beim Thema Speichermanagement unterschiedliche Wege. Copy-On-Modify verschafft dem Entwickler die angenehme Gewissheit, Objekte nur explizit verändern zu können – leider auf Kosten von Speicherineffizienz durch den ständigen Kopiervorgang. Grundsätzlich sollten R-Programmierer Einfluss und Fehlerpotenzial durch das unterschiedliche Objektmanagement in Python nicht unterschätzen.