Wie ist dieser Code in Python zu verstehen (Fibonacci-Rechner)?

1 Antwort

Vom Fragesteller als hilfreich ausgezeichnet

Die Funktionalität dieses Codes basiert darauf, dass hier ein iterierbarer Typ erstellt wird. Das ist erkennbar an den Methoden __iter__ und __next__, die jeweils definiert werden.

Iterierbar bedeutet, dass ein Objekt dieses Typs bei jedem Aufruf von next einen neuen Wert zurückgeben kann (sofern es noch Werte bereithält). Du kannst das Objekt in eine Schleife werfen, um alle Werte zu erhalten. Ein dir sicherlich bekannter, iterierbarer Typ aus der Standardbibliothek ist die Liste.

Hier ein einfaches Beispiel, eines eigenen iterierbaren Typs:

class Counter:
  def __init__(self, max):
    self.max = max

  def __iter__(self):
    self.current = 0
    return self

  def __next__(self):
    if self.current <= self.max:
      result = self.current
      self.current += 1
      return result
    else:
      raise StopIteration

counter = Counter(10)

for i in counter:
  print(i)

Die Schleife holt sich bei Start implizit das Iteratorobjekt (ruft also __iter__ auf) und bei jedem neuen Schleifenlauf wird implizit die __next__-Methode aufgerufen, wodurch sich self.current erhöht. Der vorherige Wert geht an den Aufrufer zurück, sodass du schlussendlich in der Konsole die Ausgabe 0 bis 10 (beide Grenzen inklusiv) erhältst.

Statt der Schleife könntest du auch folgendermaßen iterieren:

counter = Counter(10)
iterator = iter(counter)

print(next(iterator))
print(next(iterator))

Mit iter holst du dir das Iteratorobjekt. Mit den next-Aufrufen kannst du Schritt für Schritt den nächsten Wert einholen.

Zu dem Fibonacci-Typ: In __iter__ wird zunächst der Startzustand festgelegt. Wenn die Iteration beginnt, sind A und N jeweils 0, B ist 1.

So könnte man das Objekt verwenden:

fibonacci = Fibonacci(5)

for element in fibonacci:  
  print(element)  

In diesem Beispiel würden nur die ersten fünf Zahlen der Fibonacci-Reihe ausgegeben werden.

Nun kann man sich einmal anschauen, was bei den einzelnen Aufrufen von __next__ passiert.

Initialzustand:
N = 0
A = 0
B = 1
MaxN = 5

1. Iteration:
N (0) < MaxN (5) => true
N = 1
A = 1
B = 0 + 1 = 1
return A (1)

Diese Zeile:

self.A, self.B = self.B, self.A + self.B

ist also im Prinzip nur eine Kurzschreibweise für diese:

self.A = self.B
self.B = self.A + self.B

Konkret handelt es sich bei den kommaseparierten Ausdrücken um Tupel. Tupel dienen der Speicherung mehrerer Elemente. Ein typisches Anwendungsbeispiel wäre eine Koordinate.

In besagter Zeile werden A und B jeweils in ein Tupel geschoben. Ich ergänze hier einmal Klammern, um visuell deutlicher zu machen, dass es sich um eine Werteauflistung handelt.

(self.A, self.B)

Außerdem wird ein Tupel mit neuen Werten kreiert:

(self.B, self.A + self.B)

Bei der Zuweisung des zweiten Tupels an das erste, läuft Python intern über die Elemente des zweiten Tupels und übergibt sie an das jeweilige Element des ersten Tupels.

Nun sollten die folgenden Iterationen auch nachvollziehbar sein:

2. Iteration:
N (1) < MaxN (5) => true
N = 2
A = 1
B = 1 + 1 = 2
return A (1)

3. Iteration:
N (2) < MaxN (5) => true
N = 3
A = 2
B = 2 + 1 = 3
return A (2)

4. Iteration:
N (3) < MaxN (5) => true
N = 4
A = 3
B = 3 + 2 = 5
return A (3)

5. Iteration:
N (4) < MaxN (5) => true
N = 5
A = 5
B = 5 + 3 = 8
return A (5)

6. Iteration:
N (5) < MaxN (5) => false

Ruhrpotter4324 
Fragesteller
 19.10.2021, 01:53

Vielen Dank! Selten so eine Antwort gesehen, die auch noch so nachvollziehbar und lehrbuchartig (im positiven Sinne) formuliert ist! Würden sich Autoren bloß so viel Mühe geben wie du 😅 Der Stern ist garantiert, danke!!!

0
Ruhrpotter4324 
Fragesteller
 19.10.2021, 17:58

Ich hätte da nochmal eine kurze Zwischenfrage: Ich habe dein Beispiel etwas umformuliert, weil ich das in einem Buch mal so gesehen habe. So kriegt man ja die Ergebnisse nicht von oben nach unten angezeigt, sondern in einer Reihe sortiert. Gibt es einen nennenswerten Vorteil für end=" " oder ist das einfach nur dafür da, dass man die Ergebnisse anders angezeigt bekommt, je nachdem, wie man es lieber mag? Danke nochmal, dank dir habe ich das Thema richtig verstanden!

#Dein Beispiel:
for i in counter:
  print(i)

#Mein Beispiel:
for i in counter:
  print(i, end=" ")
0
regex9  19.10.2021, 18:09
@Ruhrpotter4324

Der einzige Unterschied ist das Trennzeichen, welches du im zweiten Fall halt anders definierst. Standardmäßig ist es ein Zeilenumbruch, der hinter jede Ausgabe gehängt wird. Aber es könnte ebenso ein anderer String (z.B. mit Komma) sein.

print("Hello", end=",")
print("world")

# Ausgabe: Hello,world
1