Innerhalb einer Schleife habe ich mehrfach auf ein Objekt in einem Dictionary zugegriffen, es transformiert und visualisiert. Die Absicht war, dass alle Transformationen voneinander isoliert bleiben sollten. Was aber tatsächlich passierte, war, dass sich diese Transformationen aufgrund von Pythons “Pass by object reference”-Verhalten kummulierten. Schauen wir uns das mal an:
# create a Dataframe and put it in a dictionary object
d = pd.DataFrame({'blog':[0,1,2,3,4]})
dict = {'hung':d}
print(f"ObjectID:{id(d)} : {d['blog'].values}")
# Loop two times and do a transformation
for i in range(2):
df = dict['hung']
df['blog'] = -df['blog']
print(f"ObjectID:{id(df)} : {d['blog'].values}")
# Output
# ObjectID:136417234566688 : [0 1 2 3 4]
# ObjectID:136417234566688 : [ 0 -1 -2 -3 -4]
# ObjectID:136417234566688 : [0 1 2 3 4]
Ich hätte erwartet, dass der im Dictionary enthaltene Dataframe d der Variablen df zugewiesen wird und dass sich jede Änderung an df nicht auf den zugrunde liegenden Dataframe d auswirken würde. Wie die Ausgabe jedoch zeigt, wird in jeder Schleife dieselbe objectid verwendet, was bedeutet, dass der zugrunde liegende Dataframe d tatsächlich von der Änderung in jeder Schleife betroffen ist.
Das liegt daran, dass in Python der Dataframe nicht als isolierte Kopie, sondern immer als Referenz übergeben wird. Genauer gesagt wurde der “Zeiger” auf das Objekt d und den entsprechenden Speicherplatz an die Variable df übergeben. Jede Änderung an df würde sich daher auch auf den zugrunde liegenden Dataframe d auswirken.
Um das noch etwas tiefer zu beleuchten: Das Konzept des “call by value” würde eine Kopie der übergebenen Variablen erzeugen, während “call by reference” den Speicherplatz übergibt. In Python werden Variablen per “Value” übergeben, mit dem Unterschied, dass die Values Referenzen auf einen Speicherbereich sind. Wenn also eine Variable einer anderen Variablen zugewiesen wird, wird das Objekt, auf das sie verweist, nicht kopiert. Stattdessen wird ein neuer Verweis auf dasselbe zugrunde liegende Objekt erstellt. Wenn die neue Variable geändert wird, wird auch das zugrundeliegende Objekt geändert, was den Eindruck von “call by reference” erwecken kann.
Dies gilt jedoch nur für veränderbare Objekte. Unveränderliche Objekte wie int oder strings können nicht verändert werden, und jede Operation, die sie zu verändern scheint, erzeugt tatsächlich ein neues Objekt. Dieses Verhalten kann manchmal daher auch den Anschein erwecken, dass das Objekt mittels “call by value” übergeben wurde.
Referenzen:
https://plainenglish.io/blog/pass-by-object-reference-in-python-79a8d92dc493
https://robertheaton.com/2014/02/09/pythons-pass-by-object-reference-as-explained-by-philip-k-dick