Referenzen

3.2.3. Referenzen#

Ein wichtiger Aspekt beim Arbieten mit Arrays – und auch mit Listen bzw. im Allgemeinen veränderbaren Datentypen – sind Referenzen.

import numpy as np
np.set_printoptions(precision=2, linewidth=65)

Motivation#

Im folgenden Beispiel wird ein Teilbereich eines Arrays als Variable gespeichert.

original = np.arange(5, 15, 1.0)
bereich = original[3:8]

Mit den entsprechenden Formen:

print( original.shape )
print( bereich.shape )
(10,)
(5,)

Wird nun eine Änderung im Teilbereich durchgeführt, wird auch das Original verändert, da das Array bereich dieselben Elemente referenziert wie das Array original. Konkret bedeutet dies, dass beide Arrays an der gleichen Stelle im Speicher nach ihren Elementen schauen und wird dort ein Wert verändert, ist dies immer für beide sichtbar.

print( original )
print( bereich )
[ 5.  6.  7.  8.  9. 10. 11. 12. 13. 14.]
[ 8.  9. 10. 11. 12.]
bereich[:] = -12
print( original )
print( bereich )
[  5.   6.   7. -12. -12. -12. -12. -12.  13.  14.]
[-12. -12. -12. -12. -12.]

Es ist nicht möglich, direkt abzulesen, ob zwei Arrays den gleichen Speicherplatz referenzieren. Mit der Funktion np.shares_memory ist jedoch eine Überlappung des addressierten Speicherbereichs abfragbar.

print( np.shares_memory(original, bereich) )
True

Und als Gegenprobe der Vergleich mit einem neu angelegten Array.

weiteres_array = np.arange(5, 15, 1.0)
print( np.shares_memory(weiteres_array, bereich) )
False

Kopien#

Soll auf einem Indexbereich gearbeitet werden, ohne gleichzeitig die Elemente des Originals zu ändern, muss eine Kopie der Daten genutzt werden. Dazu existiert die np.copy-Funktion, die genau eine solche Kopie erstellt.

bereich = np.copy(original[3:8])
print( np.shares_memory(original, bereich) )
False
print( original )
print( bereich )
[  5.   6.   7. -12. -12. -12. -12. -12.  13.  14.]
[-12. -12. -12. -12. -12.]
bereich[:] = 0
print( original )
print( bereich )
[  5.   6.   7. -12. -12. -12. -12. -12.  13.  14.]
[0. 0. 0. 0. 0.]

Der Vorteil von Referenzen ist, dass unnötige Kopien verhindert werden. Soll beispielsweise ein Teil eines Datensatzes visualisiert werden, ist es oft überflüssig, eine Kopie der Daten zu erstellen. Insbesondere bei großen Datenmengen wird so der Speicher- und Rechenbedarf deutlich reduziert.

Funktionen#

Auch beim Aufruf von Funktionen spielen Referenzen eine wichtige Rolle. Die folgende Funktion verdeutlicht dies.

# Funktion zum nullen von negativen Werten aus einer Liste
def entferne_negative(liste):
    ergebnis = liste
    for i in range(len(ergebnis)):
        if ergebnis[i] < 0:
            ergebnis[i] = 0
    return ergebnis

Gegeben sei eine Werteliste, welche auch negative Einträge hat. Die obige Funktion wird eine Liste zurückgeben, welche diese zu Null umgewandelt hat.

werte = [4, 8, -3, -1, 11, 2]
print( werte )
[4, 8, -3, -1, 11, 2]

Die Anwendung der Funktion auf die Werteliste und Ausgabe des Ergebnisses:

res = entferne_negative(werte)

print( res )
[4, 8, 0, 0, 11, 2]

Da aber in der Funktion implizit immer mit einer Referenz gearbeitet wurde, ist die originale Werteliste auch geändert.

print( werte )
[4, 8, 0, 0, 11, 2]

Ist dieses Verhalten nicht gewünscht, so kann auch in diesem Fall eine explizite Kopie zum Erhalt der übergebenen Werte führen. Hierzu stellt das copy-Modul eine entsprechende Funktion zum Kopieren von z.B. Listen bereit.

import copy

# Obige Funktion mit Kopie
def entferne_negative_copy(liste):
    ergebnis = copy.copy(liste)
    for i in range(len(ergebnis)):
        if ergebnis[i] < 0:
            ergebnis[i] = 0
    return ergebnis
werte = [4, 8, -3, -1, 11, 2]
res = entferne_negative_copy(werte)
print( res )
print( werte )
[4, 8, 0, 0, 11, 2]
[4, 8, -3, -1, 11, 2]