Die Mutation des Zustands von Objekten ist ein Effekt. Effekte werden auch als Seiteneffekt bezeichnet, wenn sie sozusagen nebenbei erfolgen, zum Beispiel zusätzlich zu einem berechneten Ergebnis oder unabsichtlich neben einem weiteren beabsichtigten Effekt.
Betrachten Sie diese rekursive Definition der Funktion rev
.
Anders als von der zuvor definierten Prozedur reverse
wird das Argument hier nicht mutiert.
Stattdessen wird eine umgekehrte Liste zurückgeliefert.
Diese Funktion hat keinen Effekt, also auch keinen Seiteneffekt.
def rev(a):
if len(a) > 1:
half = len(a) // 2
# a == left + right
left = a[0:half]
right = a[half:len(a)]
return rev(right) + rev(left)
else:
return a
Der rekursive Fall teilt das Argument in zwei Hälften,
berechnet deren Umkehrung rekursiv
und hängt die Ergebnisse in umgekehrter Reihenfolge aneinander.
Die Implementierung nutzt die folgende Eigenschaft,
die jede reverse
-Funktion erfüllen sollte:
reverse(l + r) == reverse(r) + reverse(l)
Ist das Argument kurz genug (seine Länge höchstens 1), so wird die Rekursion abgebrochen. In diesem Fall ist das Argument gleich seiner Umkehrung, so dass es selbst zurückgegeben wird.
Betrachten Sie die folgende Prozedur, die rev
verwendet, um aus einer übergebenen Liste eine neue zu berechnen,
in dem die Elemente erst rückwärts und dann vorwärts stehen.
def back_and_forth(a):
result = rev(a)
result[len(a):len(a)] = a
print('Aus ' + str(a) + ' wird ' + str(result))
Bei einem Aufruf dieser Prozedur wird von der enthaltenen Ausgabeanweisung sowohl die als Argument übergebene Liste als auch die neu berechnete Liste ausgegeben:
>>> back_and_forth([1,2,3])
Aus [1,2,3] wird [3,2,1,1,2,3]
Die neu berechnete Liste wird durch Mutation des Ergebnisses von rev
erzeugt, indem die ursprüngliche Liste hinten in das umgekehrte eingefügt wird.
Diese Mutation des in der Variablen result
gespeicherten Ergebnisses von rev
ist ein weiterer Effekt von back_and_forth
,
der zusätzlich zur Ausgabe erfolgt.
Da die Variable result
nur im Rumpf der Prozedur back_and_forth
sichtbar ist,
könnte man meinen,
dass dieser zusätzliche Effekt lediglich ein Implementierungsdetail
und von außen (außer in der erzeugten Ausgabe) nicht beobachtbar ist.
Allerdings hat die verwendete rev
-Funktion eine Eigenschaft,
durch die dieser zusätzliche Effekt ein unbeabsichtigter Seiteneffekt wird,
wie der folgende Aufruf zeigt.
>>> back_and_forth([1])
Aus [1,1] wird [1,1]
Das ist nicht die beabsichtigte Ausgabe. Richtig hätte die Ausgabe lauten sollen: Aus [1] wird [1,1]
.
Im Rumpf von back_and_forth
wird bei der Erzeugung der neu berechneten Liste auch die als Argument übergebene Liste mutiert, weil die Variablen a
und result
in diesem Fall auf dasselbe Objekt verweisen. Dass rev
im Fall eines kurzen Argumentes (Länge höchstens 1) das Argument selbst zurückliefert, führt hier in Verbindung mit der Mutation des Ergebnisses zu einer unerwünschten Mutation des Argumentes. In diesem Fall ist back_and_forth
eine ihr Argument mutierende Prozedur, was nicht beabsichtigt war.
Bei der Definition von back_and_forth
kann leicht vergessen werden, dass die Funktion rev
in einigen Fällen ihr Argument zurück liefert. Besser wäre es, wenn rev
auch im Fall kurzer Argumente ein neues Objekt zurück liefern würde,
damit unbeabsichtigte Mutationen wie in unserem Beispiel vermieden werden. Bei der Definition eigener Funktionen sollten Sie deshalb niemals das Argument selbst zurück liefern, es sei denn die Funktion ist als mutierend beschrieben
und die Tatsache, dass das Argument zurück geliefert wird, ergibt sich aus diesem Kontext.
Bei der oben gezeigten rev
-Funktion sollten wir dieser Maßgabe entsprechend die Anweisung return a
durch return a + []
ersetzen, um ein neues Objekt zu erzeugen, dessen Wert der gleiche ist wie der des Argumentes a
.