Zugriff aus einer Klasse

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
norgsmen
User
Beiträge: 55
Registriert: Samstag 26. Juni 2021, 22:09

Hallo Leute,

es geht um diesen Code hier:

Code: Alles auswählen

from typing import TypeVar, Generic, List


T = TypeVar('T')

class Stack(Generic[T]):
    def __init__(self) -> None:
        self._container: List[T] = []
    def push(self, item: T) -> None:
        self._container.append(item)
    def pop(self) -> T:
        return self._container.pop()
    def __repr__(self) -> str:
        return repr(self._container)

num_discs = 3
tower_a: Stack[int] = Stack()
tower_b: Stack[int] = Stack()
tower_c: Stack[int] = Stack()
for i in range(1, num_discs + 1):
    tower_a.push(i)

def hanoi(begin: Stack[int], end:Stack[int], temp:Stack[int], n: int) -> None:
    if n == 1:
        end.push(begin.pop())
    else:
        hanoi(begin, temp, end, n - 1)
        hanoi(begin, end, temp, 1)
        hanoi(temp, end, begin, n - 1)
Es ist doch prinzipiell so das alles was in einer Klasse/Funktion geschrieben wird auch gekapselt sich nur in diesem Raum bewegt. Also wenn ich eine Funktion habe und diese nicht aufrufe oder ein Objekt einer klasse instanziiere dann hat man ja keinen Zugriff auf die Methoden, Variablen usw. richtig?

Weshalb kann dann

Code: Alles auswählen

end.push(begin.pop())
auf push Zugreifen?

Code: Alles auswählen

tower_a: Stack[int] = Stack()
hier wurde ein Objekt von der Klasse Stack instanziiert aber in wie fern hängt es damit zusammen das die Funktion hanoi nun Zugriff auf push hat? Bzw. hängt es überhaupt damit zusammen?

Danke fürs Antworten
Sirius3
User
Beiträge: 17755
Registriert: Sonntag 21. Oktober 2012, 17:20

Du hast eine Funktion Hanoi, die als Argumente Stack Objekte bekommen. Warum wunderst du dich dann, dass du die push-Methode eines diese Objekte aufrufen kannst?

Die __repr__-Methode ist falsch. Sie sollte eine Repräsentation des Objekts liefern. Stattdessen liefert es die Repräsentation einer Liste. Damit sieht es so aus als ob ein stackobjekt eine Liste wäre, was ja nicht der Fall ist.
Die ganze Klasse liefert gegenüber einer einfachen Liste eh keinen Mehrwert und kann weg. Läßt man jetzt noch alle überflüssigen Typannovationen weg, wird der Code um einiges übersichtlicher.

Code: Alles auswählen

def hanoi(begin, end, temp, n):
    if n == 1:
        end.append(begin.pop())
    else:
        hanoi(begin, temp, end, n - 1)
        hanoi(begin, end, temp, 1)
        hanoi(temp, end, begin, n - 1)

num_discs = 3        
tower_a = list(range(1, num_discs + 1))
tower_b = []
tower_c = []
hanoi(tower_a, tower_b, tower_c, num_discs)
norgsmen
User
Beiträge: 55
Registriert: Samstag 26. Juni 2021, 22:09

Bekommt die Funktion hanoi als Argument die Klasse Stack? Ich dachte

Code: Alles auswählen

begin: Stack[int]
bedeutet das begin Stack[int] erwartet. Also

Code: Alles auswählen

:Stack[int]
ist doch ein type hints die haben doch keine Auswirkung auf dem Code
Sirius3
User
Beiträge: 17755
Registriert: Sonntag 21. Oktober 2012, 17:20

Da Du den dazugehörigen Code nicht gezeigt hast, kannst Du beim Aufruf was ganz anderes übergeben haben. Aber ändert ja nichts an der Sache, dass Stackobjekte eine push-Methode haben.
Ich verstehe Deine Frage nicht.
norgsmen
User
Beiträge: 55
Registriert: Samstag 26. Juni 2021, 22:09

Also die Frage lauet nach wie vor wie

Code: Alles auswählen

end.push(begin.pop())
in der hanoi Funktion Zugriff auf die Push Methode in der Stack Klasse hat. Die Stack Klasse wird doch nirgendwo mitgeben oder erzeugt.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

die wird mitgegeben, weil 'end' ein Stack-Objekt sein sollte. So steht es zumindest im Code.
Wenn du für 'end' etwa anderes wie ein Stack-Objekt übergibst, das keine 'push'-Methode hat, dann gehts auch nicht.

Zeig doch mal den vollständigen Code, mit dem Funktionsaufruf.

Grüße
Dennis

Edit: Das ist ein Codebeispiel aus einem Buch? Kommt mir irgendwie bekannt vor.
"When I got the music, I got a place to go" [Rancid, 1993]
nezzcarth
User
Beiträge: 1637
Registriert: Samstag 16. April 2011, 12:47

norgsmen hat geschrieben: Donnerstag 28. Dezember 2023, 23:25 Also die Frage lauet nach wie vor wie

Code: Alles auswählen

end.push(begin.pop())
in der hanoi Funktion Zugriff auf die Push Methode in der Stack Klasse hat.
Na ja, dein Code Beispiel enthält ja auch gar keinen eigentlichen Aufruf der Funktion "hanoi". Daher macht das in dem Moment – also wenn man das Code-Beispiel so, wie du es gegeben hast ausführt – auch keine Probleme. Wenn du "hanoi" mit den falschen Argumenten aufrufst, macht es natürlich schon Probleme. Es ist deine Aufgabe, dafür zu sorgen, dass da das Richtige an- bzw. reinkommt. Und das "Richtige" ist in dem Zusammenhang einfach irgendein Objekt, das zum Zeitpunkt der Ausführung der Passage auch über die geforderten Methoden verfügt. Rein theoretisch (bitte nicht machen!) könntest du die Methode zum Objekt "begin" in dem Fall sogar auch erst in der Zeile vor deren Aufruf innerhalb der Funktion hinzufügen: "begin.push = lambda x: print("foo")"
norgsmen hat geschrieben: Also wenn ich eine Funktion habe und diese nicht aufrufe oder ein Objekt einer klasse instanziiere dann hat man ja keinen Zugriff auf die Methoden, Variablen usw. richtig?
Ich habe irgendwie den Eindruck, dass du vielleicht davon ausgehst, das Python funktioniert wie ganz prototypische kompilierte Sprachen, die Code als Ganzes auf Konsistenz prüfen. Die Fehler, die du erwartest, zeigen sich in einer dynamischen Sprache wie Python, das tendenziell eher als interpretierte Sprache gilt (die Realität ist etwas komplizierter) aber erst zur Laufzeit.
norgsmen
User
Beiträge: 55
Registriert: Samstag 26. Juni 2021, 22:09

Hier der vollständige Code:

Code: Alles auswählen

from typing import TypeVar, Generic, List


T = TypeVar('T')

class Stack(Generic[T]):
    def __init__(self) -> None:
        self._container: List[T] = []
    def push(self, item: T) -> None:
        self._container.append(item)
    def pop(self) -> T:
        return self._container.pop()
    def __repr__(self) -> str:
        return repr(self._container)

num_discs = 3
tower_a: Stack[int] = Stack()
tower_b: Stack[int] = Stack()
tower_c: Stack[int] = Stack()
for i in range(1, num_discs + 1):
    tower_a.push(i)

def hanoi(begin: Stack[int], end:Stack[int], temp:Stack[int], n: int) -> None:
    if n == 1:
        end.push(begin.pop())
    else:
        hanoi(begin, temp, end, n - 1)
        hanoi(begin, end, temp, 1)
        hanoi(temp, end, begin, n - 1)

hanoi(tower_a, tower_c, tower_b, num_discs)
print(tower_a)
print(tower_b)
print(tower_c)
Ich kann die hanoi Funktion auch so umändern:

Code: Alles auswählen

def hanoi(begin, end, temp, n):
    if n == 1:
        end.push(begin.pop())
    else:
        hanoi(begin, temp, end, n - 1)
        hanoi(begin, end, temp, 1)
        hanoi(temp, end, begin, n - 1)
und es trotzdem funktioniert noch aber warum? In tower_a, tower_b und in tower_c wird die Klasse Stack instanziiert sonst nirgendwo im Code. Wie kann es jetzt sein das die Funktion hanoi auf die Methode push aus der Klasse Stack zugreifen kann? weder tower_a, tower_b und tower_c wird als Argument der Funktion hanoi mitgegben noch gehört die Funktion hanoi der klasse Stack an
Sirius3
User
Beiträge: 17755
Registriert: Sonntag 21. Oktober 2012, 17:20

Doch. Hier werden die tower-Variablen als Argumente an hanoi übergeben:

Code: Alles auswählen

hanoi(tower_a, tower_c, tower_b, num_discs) 
norgsmen
User
Beiträge: 55
Registriert: Samstag 26. Juni 2021, 22:09

@Sirius3 oh... habe ich doch glatt übersehen:D danke an alle
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Ohne Type-Annotations wäre dir das möglicherweise nicht passiert ;)
Benutzeravatar
__blackjack__
User
Beiträge: 13118
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@norgsmen: Ein paar Punkte zum Quelltext:

Fangen wir mit der Namensgebung an. Attribute auf Objekten beschreiben woraus das Objekt besteht. Ein `Stack` besteht aus Elementen und nicht aus einem Container. Der Stack selbst ist ja ein/der Container für diese Elemente. Wir haben bereits an einer Stelle einen generischen Namen für ein einzelnes Element: `item`, also wäre `_items` ein passender Attributname.

Abkürzungen in Namen sind nicht gut, weil sie den Quelltext schwerer lesbar/verständlich machen. `temp` ist eine recht generische Abkürzung steht in anderen Programmen auch gerne mal für `temperature`, es ist also keine allgemein bekannte Abkürzung mit der so gut wie jeder das gleiche verbindet.

Das die `__repr__()`-Methode falsch ist, wurde ja von Sirius3 bereits angemerkt. Richtig wäre entweder eine Zeichenkette mit einem Ausdruck, der als Quelltext ausgeführt wieder ein gleichwertiges `Stack`-Objekt liefern würde, oder die Darstellung sollte in ”spitze Klammern” ("<…>") eingefasst werden. So machen das alle anderen Datentypen. Da man einen `Stack` mit Inhalt nicht in einem Ausdruck erstellen kann, müsste man entweder *das* ändern, oder man ist bei den ”spitzen Klammern”.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Wenn Code auf Modulebene steht, sollte der nicht auch noch Variablendefinitionen und Funktionsdefinitionen mischen. Das macht es noch schwerer einen Überblick zu behalten.

Das man der Funktion beim ersten Aufruf die Anzahl der Scheiben mitgeben muss ist ja eigentlich redundant, denn das ist immer die Anzahl der Elemente von `begin`. Das Argument kann man also Optional machen und vom `Stack` abfragen wenn der eine `__len__()`-Methode bekommt.

Nichts an der `hanoi()`-Funktion erfordert, das die Elemente ganze Zahlen sind. Der Funktion ist das schlicht egal. Wenn man schon Type-Annotationen macht, sollte man die nicht unnötig einschränkend machen.

Zwischenstand:

Code: Alles auswählen

from typing import Generic, List, Optional, TypeVar

T = TypeVar("T")


class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: List[T] = []

    def __len__(self) -> int:
        return len(self._items)

    def push(self, item: T) -> None:
        self._items.append(item)

    def pop(self) -> T:
        return self._items.pop()

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} {self._items!r}>"


def hanoi(
    begin: Stack[T],
    end: Stack[T],
    temporary: Stack[T],
    n: Optional[int] = None,
) -> None:
    if n is None:
        n = len(begin)

    if n == 1:
        end.push(begin.pop())
    else:
        hanoi(begin, temporary, end, n - 1)
        hanoi(begin, end, temporary, 1)
        hanoi(temporary, end, begin, n - 1)


def main():
    tower_a: Stack[int] = Stack()
    tower_b: Stack[int] = Stack()
    tower_c: Stack[int] = Stack()
    for i in [1, 2, 3]:
        tower_a.push(i)

    hanoi(tower_a, tower_c, tower_b)
    print(tower_a)
    print(tower_b)
    print(tower_c)


if __name__ == "__main__":
    main()
Ausgabe:

Code: Alles auswählen

<Stack []>
<Stack []>
<Stack [1, 2, 3]>
Hier das ganze ohne die Typannotationen:

Code: Alles auswählen

class Stack:
    def __init__(self):
        self._items = []

    def __len__(self):
        return len(self._items)

    def push(self, item):
        self._items.append(item)

    def pop(self):
        return self._items.pop()

    def __repr__(self):
        return f"<{self.__class__.__name__} {self._items!r}>"


def hanoi(begin, end, temporary, n=None):
    if n is None:
        n = len(begin)

    if n == 1:
        end.push(begin.pop())
    else:
        hanoi(begin, temporary, end, n - 1)
        hanoi(begin, end, temporary, 1)
        hanoi(temporary, end, begin, n - 1)


def main():
    tower_a = Stack()
    tower_b = Stack()
    tower_c = Stack()
    for i in [1, 2, 3]:
        tower_a.push(i)

    hanoi(tower_a, tower_c, tower_b)
    print(tower_a)
    print(tower_b)
    print(tower_c)


if __name__ == "__main__":
    main()

Und dann noch mal ohne die Klasse, die eigentlich nur `append()` in `push()` umbenennt:

Code: Alles auswählen

def hanoi(begin, end, temporary, n=None):
    if n is None:
        n = len(begin)

    if n == 1:
        end.append(begin.pop())
    else:
        hanoi(begin, temporary, end, n - 1)
        hanoi(begin, end, temporary, 1)
        hanoi(temporary, end, begin, n - 1)


def main():
    tower_a = [1, 2, 3]
    tower_b = []
    tower_c = []

    hanoi(tower_a, tower_c, tower_b)
    print(tower_a)
    print(tower_b)
    print(tower_c)


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
norgsmen
User
Beiträge: 55
Registriert: Samstag 26. Juni 2021, 22:09

@__blackjack__ Danke dir für die hilfreiche Ergänzung und Erklärung!
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

Oder alternativ..

Code: Alles auswählen

class Tower:

    _prt_counts=0

    @staticmethod
    def print(*items):
        print(f"\n*** Step: {Tower._prt_counts:2d} ***\n")
        for item in sorted(items, key=lambda it: it._name):
            print(item)
        Tower._prt_counts += 1

    def __init__(self, items=[], name=None):
        self._items = items
        self._name = name

    def __len__(self):
        return len(self._items)

    def push(self, item):
        self._items.append(item)

    def pop(self):
        return self._items.pop()

    def __repr__(self):
        return f"{self._name}:\t{self._items}"


def hanoi(a, b, c, n):

    if n == 1:
        c.push(a.pop())
        Tower.print(a,b,c)
    else:
        hanoi(a, c, b, n-1)
        hanoi(a, b, c, 1)
        hanoi(b, a, c, n-1)


def main():

    towers = [
         Tower(['####','###','##','#'],'Start'),
         Tower([],'Temp'),
         Tower([],'Ziel')
    ];  Tower.print(*towers)

    hanoi(*towers, len(towers[0]))

    print(f"\n(( 2^{len(towers[2])} - 1 = {2**len(towers[2])-1} ))")


if __name__ == "__main__":
    main()
Ausgabe:

Code: Alles auswählen

*** Step:  0 ***

Start:  ['####', '###', '##', '#']
Temp:   []
Ziel:   []

*** Step:  1 ***

Start:  ['####', '###', '##']
Temp:   ['#']
Ziel:   []

*** Step:  2 ***

Start:  ['####', '###']
Temp:   ['#']
Ziel:   ['##']

*** Step:  3 ***

Start:  ['####', '###']
Temp:   []
Ziel:   ['##', '#']

*** Step:  4 ***

Start:  ['####']
Temp:   ['###']
Ziel:   ['##', '#']

*** Step:  5 ***

Start:  ['####', '#']
Temp:   ['###']
Ziel:   ['##']

*** Step:  6 ***

Start:  ['####', '#']
Temp:   ['###', '##']
Ziel:   []

*** Step:  7 ***

Start:  ['####']
Temp:   ['###', '##', '#']
Ziel:   []

*** Step:  8 ***

Start:  []
Temp:   ['###', '##', '#']
Ziel:   ['####']

*** Step:  9 ***

Start:  []
Temp:   ['###', '##']
Ziel:   ['####', '#']

*** Step: 10 ***

Start:  ['##']
Temp:   ['###']
Ziel:   ['####', '#']

*** Step: 11 ***

Start:  ['##', '#']
Temp:   ['###']
Ziel:   ['####']

*** Step: 12 ***

Start:  ['##', '#']
Temp:   []
Ziel:   ['####', '###']

*** Step: 13 ***

Start:  ['##']
Temp:   ['#']
Ziel:   ['####', '###']

*** Step: 14 ***

Start:  []
Temp:   ['#']
Ziel:   ['####', '###', '##']

*** Step: 15 ***

Start:  []
Temp:   []
Ziel:   ['####', '###', '##', '#']

(( 2^4 - 1 = 15 ))
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

Qubit hat geschrieben: Samstag 30. Dezember 2023, 01:20
def __repr__(self):
return f"{self._name}:\t{self._items}"
Hier dann aber anstatt dessen..

Code: Alles auswählen

    def __str__(self):
        return f"{self._name}:\t{self._items}"
Sirius3
User
Beiträge: 17755
Registriert: Sonntag 21. Oktober 2012, 17:20

@qubit: bitte keine globalen Variablen verwenden! Wenn Du schon Türme hast, kann jeder Turm die Anzahl seiner Züge zählen und Du kannst sie bei der Ausgabe zusammenzählen.
Benutze niemals ;. Es gibt keinen vernünftigen Grund dafür. Es sorgt nur dafür, dass man Anweisungen überliest.
Warum heißen Towers in print items? Das ist verwirrend, weil jeder Tower ja auch items enthält. Die *-Magie ist unnötig.
Warum wird die Reihenfolge bei der Ausgabe nicht so genommen, wie übergeben?
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Qubit hat geschrieben: Samstag 30. Dezember 2023, 01:20

Code: Alles auswählen

    def __init__(self, items=[], name=None):
        self._items = items
        self._name = name

Du hast noch einen weiteren beliebten Fehler im Code, der allerdings während der Ausführung nicht auffällt, da an 'items' stets eine Liste übergeben wird. 'items' sollte required sein, oder None als Defaultwert erhalten. Wenn letzteres der Fall ist, legst Du in 'init' eine neue Liste an, falls keine Liste als Argument übergeben wurde. Andernfalls teilen sich alle Instanzen die Default-Liste.
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

@Sirius3

Jup, danke für deine Kritik.

Warum sprichst du aber bei Klassenvariablen von "globalen Variablen"?
Das entspricht nicht ganz meinem Sprachgebrauch. Machen für dich Klassenvariablen generell keinen Sinn?
Wenn man den Code weitergehend nutzt, wird man wohl auch Counter auf Instanzebene verwenden, war jetzt aber für mich hier (zur Demonstration) nicht notwendig.

Der Punkt mit den "geordneten Ausgaben" ist der, dass in der Funktion "hanoi" die Parameter a, b und c mit jeweils anderen "Towern" belegt ist; es dient also der übersichtliche Ausgabe, mehr nicht. :)

@kbr

Ja, ich kenne (eigentlich) das Problem mit der "frühen Referenz".
Und ich halte das nicht für einen "Anfängerproblem", sondern eher als ein Problem von Python selbst.
Und wenn man nur gelegentlich etwas in Python macht, dann ist es auch schnell wieder da.. :|
Also dann so:

Code: Alles auswählen

class Tower:

    _prt_counts=0

    @staticmethod
    def print(*towers):
        print(f"\n*** Step: {Tower._prt_counts:2d} ***\n")
        for tower in sorted(towers, key=lambda it: it._name):
            print(tower)
        Tower._prt_counts += 1

    def __init__(self, items=None, name=None):
        self._items = [] if items is None else items
        self._name = name

    def __len__(self):
        return len(self._items)

    def push(self, item):
        self._items.append(item)

    def pop(self):
        return self._items.pop()

    def __str__(self):
        return f"{self._name}:\t{self._items}"
Benutzeravatar
__blackjack__
User
Beiträge: 13118
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Klassenvariablen sind globaler Zustand und damit nicht gut. Genau wie auf Modulebene machen auf Klassen eigentlich nur Konstanten wirklich Sinn.

Das zählen der Züge gehört hier ja eigentlich eher in die `hanoi()`-Funktion, die macht die Züge ja schliesslich. Wenn man das nicht fest dort rein kodieren möchte, kann man das als Rückruf hinein geben.

Der `name` ist ja wohl doch eher öffentlich wenn da von einer Funktion heraus darauf zugegriffen wird. Und der Default-Wert `None` macht keinen Sinn wenn man allen einen anderen Wert gibt. Den Default-Wert für die `items` dagegen könnte man auch tatsächlich nutzen, wenn man die Reihenfolge der Argumente umkehrt.

Code: Alles auswählen

#!/usr/bin/env python3
from functools import partial
from itertools import count


class Tower:
    def __init__(self, name, items=None):
        self.name = name
        self._items = [] if items is None else items

    def __len__(self):
        return len(self._items)

    def __str__(self):
        return f"{self.name}:\t{self._items}"

    def push(self, item):
        self._items.append(item)

    def pop(self):
        return self._items.pop()


def print_towers(towers, numbers=None):
    if numbers is None:
        numbers = count()

    print(f"\n*** Step: {next(numbers):2d} ***\n")
    for tower in sorted(towers, key=lambda tower: tower.name):
        print(tower)


def hanoi(a, b, c, n=None, on_step=lambda towers: None):
    if n is None:
        n = len(a)

    if n == 1:
        c.push(a.pop())
        on_step([a, b, c])
    else:
        hanoi(a, c, b, n - 1, on_step)
        hanoi(a, b, c, 1, on_step)
        hanoi(b, a, c, n - 1, on_step)


def main():
    towers = [
        Tower("Start", ["####", "###", "##", "#"]),
        Tower("Temp"),
        Tower("Ziel"),
    ]
    print_towers(towers)

    hanoi(*towers, on_step=partial(print_towers, numbers=count(1)))

    print(f"\n(( 2^{len(towers[-1])} - 1 = {2**len(towers[-1])-1} ))")


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
__blackjack__
User
Beiträge: 13118
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Aaaahrg, jetzt habe ich das mit dem sortieren erst verstanden. Das ist ja gruselig. Dann kann man die Texte ja gar nicht in andere Sprachen übersetzen, ohne peinlich genau darauf zu achten das die Namen die sortierte Reihenfolge nicht ändern. Also einmal ohne sortieren:

Code: Alles auswählen

#!/usr/bin/env python3
from itertools import count


class Tower:
    def __init__(self, name, items=None):
        self.name = name
        self._items = [] if items is None else items

    def __len__(self):
        return len(self._items)

    def __str__(self):
        return f"{self.name}:\t{self._items}"

    def push(self, item):
        self._items.append(item)

    def pop(self):
        return self._items.pop()


def print_towers(step_number, towers):
    print(f"\n*** Step: {step_number:2d} ***\n")
    for tower in towers:
        print(tower)


def hanoi(towers):
    numbers = count()
    print_towers(next(numbers), towers)

    def hanoi_(a, b, c, n):
        if n == 1:
            c.push(a.pop())
            print_towers(next(numbers), towers)
        else:
            hanoi_(a, c, b, n - 1)
            hanoi_(a, b, c, 1)
            hanoi_(b, a, c, n - 1)

    a, b, c = towers
    hanoi_(a, b, c, len(a))


def main():
    towers = [
        Tower("Start", ["####", "###", "##", "#"]),
        Tower("Temp"),
        Tower("Ziel"),
    ]
    hanoi(towers)
    print(f"\n(( 2^{len(towers[-1])} - 1 = {2**len(towers[-1])-1} ))")


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten