Tic-Tac-Toe mit curses - OOP Frage

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
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

Hallo,

habe die letzten Tage mein erstes richtiges Projekt mit OOP realisiert. Curses war für mich auch Neuland.
Und zwar habe ich das Spiel Tic-Tac-Toe mit curses für die Konsole programmiert, funktioniert auch wunderbar.
Hatte das ganze erst prozedural, also ohne OOP programmiert, was dann aber zum Schluss sehr unübersichtlich wurde und ich musste viele Argumente an andere Funktionen "durchschleifen", was die Unübersichtlichkeit noch mehr erhöht hat. Vier Gewinnt habe ich auch schon prozedural fertig, aber da wurde es noch viel unübersichtlicher, da das Spiel schon komplexer ist. Wollte alles andere Geplante nicht so weiter programmieren und habe mich dann doch an OOP gewagt. Finde das Ganze jetzt auch recht übersichtlich und einfach zu lesen. Hatte halt etwas Respekt, weil ich außerhalb von Tutorials noch nichts groß mit Klassen gemacht habe. Da Tic-Tac-Toe erst der Anfang vom gesamten Projekt ist (Soll eine Spielesammlung für die Konsole werden) dachte ich mir, ich muss einen anderen Weg gehen. Also habe ich das Spiel nochmal mit OOP programmiert und alles in entsprechende Module verpackt, die ich dann entsprechend importiere. Vier Gewinnt folgt als Nächstes. Lange Rede kurzer Sinn, mir geht es bei meiner Frage um Folgendes.

Da dies meine erste richtige Verwendung von Klassen ist, ist meine Bitte an euch, dass ihr mal über den Code drüberschaut und mir schreibt, ob ich OOP vernünftig umgesetzt habe. Habe schon oft gelesen, dass bei vernünftiger OOP Vererbung vorkommen sollte, daher bin ich etwas verunsichert, weil ich nirgends Vererbung verwendet habe. Wüsste auch nicht, wie sich Vererbung in meinem Fall positiv auswirken sollte, weil eigentlich alles selbstständige "Einheiten" sind, daher auch die Kapselung in Module.
Ob Vererbung bei einem anderen Spiel sinnvoll ist, wird sich dann erst zeigen.

Da ich keine Dateien hoch laden kann, habe ich alles in eine .zip-Datei gepackt und extern hochgeladen.

https://www.file-upload.net/download-14 ... e.zip.html

Wenn dies hier nicht erlaubt und oder unerwünscht ist, kann ich den Code gerne noch reinstellen. Da das Ganze aber über 600 Zeilen sind, dachte ich mir, ich stelle euch alles erstmal so zur Verfügung.

ps.: Die Dateien screen.py und menu.py habe ich absichtlich so allgemein gehalten, da diese Beiden später eine Ordnerebene höher kommen und von allen Spielen verwendet werden sollen. Da menu.py die Spieleigenen Texte aus einer Datei importiert, dachte ich mir, dass ich in jedem Spielordner eine "mini" menu.py Datei erstelle, die lediglich die menu.py aus dem Hauptordner und die Spieleigenen Texte aus dem jeweiligen Spieleordner importiert. Wollte ungerne in jedem Spielordner Dateien haben, die zu 99% gleich aussehen, deswegen habe ich die beiden Dateien direkt so allgemein gehalten.

Gruß

_Mala_Fide_
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

Hatte noch vergessen zu schreiben, dass main.py ausgeführt werden muss um das Spiel zu starten. Obwohl sich das die Meisten hier sicher denken können.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@_Mala_Fide_: Das mit den Modulen ist ”falsch”. Das ist Python und kein Java, also nicht ein Modul pro Klasse. Und dann noch mal weitere Module (Mehrzahl!) für verschiedene Konstanten.

Das mit der Vererbung ist Unsinn. Insbesondere bei dynamischen Programmiersprachen mit „duck typing“ ist es recht normal, dass man relativ wenig bis gar keine Vererbung hat, weil man nicht zwingend von Klassen erben muss oder zumindest Interfaces explizit benennen muss, damit der Compiler glücklich ist.

Die Platzierung der Kommentare geht gar nicht. Wenn man Glück hat dann sind die einfach grundsätzlich nicht sichtbar, wenn man Pech hat werden die mitten in den Quelltext umgebrochen. Zumal das meiste davon beschreibt wofür Klassen da sind oder was Funktionen/Methoden machen. Das sind also keine Kommentare sondern sollten eigentlich Docstrings sein.

Der Aufruf der Hauptfunktion ist nicht gegen importieren geschützt, da fehlt das ``if __name__ == "__main__":``.

`terminal_test()` macht mehr als das Terminal zu testen. Das sollte weder das Programm abbrechen, noch die Grösse des Terminals zurückgeben, weil damit niemand rechnet. `sys.exit()` sollte wenn überhaupt nur in der `main()`-Funktion oder einer sehr ”nahen” Funktion verwendet werden, und auch nur wenn mindestens potentiell ein anderer Wert als 0 an den Aufrufer gemeldet wird. Ein `sys.exit()` ohne Argument ist ein „code smell“.

`size_x` und `size_y` würde ich `width` und `height` nennen. Das mit x und y ist verwirrend insbesondere da `curses` die oft (immer?) in der Reihenfolge y und x haben will. Da wäre dann meistens auch `row` und `column` besser als Namen.

Es gibt da einiges an magischen Zahlen die von anderen Werten abhängig sind und aus denen Abgeleitet werden sollten, statt das man die von Hand ausrechnet und in den Quelltext schreibt. Zum Beispiel die magischen Zahlen für die Mindesgrösse des Terminals hängen ja offensichtlich mit `FIELD` zusammen.

``continue`` sollte man vermeiden, insbesondere wenn das überhaupt gar nicht notwendig wäre, und in dem Programm sind alle ``continue``-Anweisungen überflüssig. Man kann die einfach durch ``pass`` ersetzen und es ändert sich überhaupt nichts am Programmablauf. Und damit ist dann auch der jeweilige ``if``-Zweig überflüssig:

Code: Alles auswählen

            if menu.decision(win_figure):
                continue
            else:
                break
            
            # => 
            
            if not menu.decision(win_figure):
                break
Ich würde in der Hauptschleife nicht einfach so davon ausgehen, das im ``else``-Zweig `key` den Wert "q" haben muss. Das trifft nur zu wenn man als Programmierer keinen Fehler gemacht hat.

Code: Alles auswählen

        else:
            break
        
        # =>
        
        elif key == "q":
            break
        else:
            assert False, f"unexpected key {key!r}"
„people“ heisst „Volk“. Ich denke Du willst eher „Mensch vs. Rechner“ statt „Volk vs. Rechner“. Also „human“. Ist ja nicht Skynet gegen das man da spielt. 😎

„Lap“ ist eine Runde beim Laufen oder Autorennen. Hier wäre eine Runde eher „round“.

Bei „Beginner“ denkt man eher an „Anfänger“ im Sinne von Schwierigkeitsgrad oder jemand der gerade angefangen hat ein Spiel zu spielen, nicht an den der das Spiel anfängt.

„power“ wäre eher „level“ wenn man Schwierigkeitsgrad meint.

Attribute sollten entweder da sein oder nicht. Das bei `Logic.__init__()` bestimmte Attribute gesetzt werden oder auch nicht, ist falsch. Wenn man keinen Wert für ein Attribut hat, kann man auch `None` als Platzhalter für ”nichts” nehmen.

Aber speziell hier ist das auch aus OOP-Sicht suboptimal bis falsch. Es sollte nicht wahlweise ein `Computer`-Objekt als Attribut geben oder nicht, sondern es sollte zwei Spieler-Objekte geben die abwechselnd dran sind. Ob diese Objekte dann `Computer` oder `Human` sind, sollte den Code in `Logic` nicht wirklich interessieren. Das können dann zwei `Human`-Objekte sein, oder ein `Human` und ein `Computer` oder auch zwei `Computer`. Das sollte an `Logic` nichts ändern. Das übergibt einfach das Spielfeld an eine Methode und je nach dem ob man da ein `Computer`- oder ein `Human`-Objekt hat, entscheidet ”KI”-Code über den Zug oder er wird von einem Benutzer abgefragt.

`pitch` (das ist wohl auch ein falsches Wort für die Werte), sieht so berechenbar aus.

Die Methode `move_cursor()` gibt entweder ein "q" zurück oder ein Tupel mit einer Positon. Funktionen/Methoden sollten möglichst *einen* „duck type“ als Rückgabetyp haben. Hier würde sich `None` anbieten. Es ist aber auch nicht wirklich gut an welchen Stellen überall Code wissen muss das "q" zum Abbrechen gedrückt werden kann/muss, beziehungsweise wo die Tastatur abgefragt wird.

`Logic` ist gar nicht die reine Spiellogik, da steck viel UI-Code mit drin. Das trennt man eigentlich.

Die `win()`-Funktion ist komisch. Das sollte wohl eher einfach eine Liste auf Modulebene sein.

Der Namensteil `field` kommt an einigen Stellen zu oft vor. In Attributnamen und Methoden von `Field` braucht man den nicht noch mal, denn das ist ja schon durch den Klassennamen klar. `field.draw_field()` oder `field.field_pos_x` bieten dem Leser nicht wirklich Mehrwert über `field.draw()` oder `field.x`.

Ich denke das reicht erst mal zum überarbeiten.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

Hallo,

erstmal vielen Dank, dass Du dir die Zeit genommen und über alles drüber geschaut hast.

Werde mich heute Abend mit allen Vorschlägen beschäftigen und mich dann die Tage nochmal melden.

Eine Frage hätte ich aber noch, undzwar wie würdest Du das mit den Modulen machen? Ich fand das so eigentlich recht übersichtlich und einfach zu lesen. Ich wollte damit ja vermeiden, dass der Code so dolle lang wird. Sollte ich lieber alles in eine Datei packen, also Code in eine und Texte und Grafiken in eine?

Ab wann wäre es denn sinnvoll Programmteile in einzelne Module zu kapseln?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@_Mala_Fide_: Also ich würde erst einmal alles in ein Modul packen. Das müssten so zwischen 600 und 700 Zeilen sein, wenn ich das von vorhin noch richtig in Erinnerung habe. Bei 1000 bis 1500 Zeilen fange ich normalerweise an mir Gedanken ums aufteilen zu machen, falls es vorher noch keinen Grund gab. Wobei ich in dem Fall von dem Spiel wohl auch die Texte und Grafiken als erstes auslagern würde, aber nicht in ein anderes Modul, denn das sind ja passive Daten, da würde ich eine Datendatei anlegen. YAML würde sich vielleicht als Dateiformat anbieten wegen der mehrzeiligen Textblöcke.

Ansonsten würde ich als nächstes an Logik und Benutzerinteraktion teilen, weil man die Logik ja wiederverwenden kann. Unit-Tests hast Du ja (noch?) nicht, aber das wäre auch ein Kandidat für ein eigenes Modul.

Und was ich auf jeden Fall bei mehr als einem Modul mache: alle Module in einem Package zusammenfassen. Damit da nicht mehrere Module mit allen möglichen anderen Package- und Modulnamen konkurrieren.

Prozedural (oder auch funktional) muss übrigens nicht durch viele Argumente unübersichtlich werden, denn man würde bei prozeduralen oder funktionalen Programmiersprachen zusammengehörende Einzelwerte in Verbunddatentypen zusammenfassen. Das Mittel dafür ist in Python auch die Klasse. Einfache, ”dumme” Datenklassen und/oder `collections.namedtuple` machen ein Programm ja nicht automatisch objektorientiert, also kann man auch mit Klassen immer noch prozedural oder funktional arbeiten.

Was ich noch erwähnen wollte: Für bestimmte Werte würde sich auch ein Aufzählungstyp (`enum`-Modul) anbieten, statt magische Zahlen oder Zeichenketten zu verwenden. Also Beispielsweise für die Schwierigkeitsstufen (EASY, MEDIUM, HARD) oder was in einem Spielfeld sein kann (X, O, oder NONE).

Bei den Schwierigkeitsstufen würde ich übrigens die Entscheidung in das `Computer`-Objekt verlagern. Dem würde ich beim erstellen den Schwierigkeitsgrad mitgeben und dann eine Methode zur Verfügung stellen die den nächsten Spielzug für einen gegebenen Spielzustand ermittelt. Die gleiche Methode gibt es dann auf `Player`. Wie der Zug dann ermittelt wird, ist dann die Verantwortung der jeweiligen Objekte.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

@__blackjack__

Hallo,

ich habe jetzt soweit alles durchgearbeitet und mich größtenteils an deine Tips gehalten.

Habe alles so in Ordner und Module gepackt, wie ich mir vorstelle, das gesamte Projekt gut weiter bauen zu können. Die Klassen und Funktionen, die ich in jedem Spiel verwenden will, in ein Modul (Habe es imports.py genannt, ein besserer Name ist mir nicht eingefallen... :roll: ), dann den Rest des Spiels in ein Modul und ein main Modul. Im main Modul sollen später die Spiele ausgesucht und eine Highscoreliste eingesehen werden können, der jetzige Inhalt von mian.py ist so, wie ich mir später den Start der verschiedenen Spiele vorstelle. Menüs und alles, sollen natürlich wieder mit curses realisiert werden.

Der Tip mit einer Datei für Texte und Grafiken, hat mir sehr geholfen. Hatte ja erst vor, in jedem Spieleordner eine menu.py Datei zu erstellen, die lediglich die Klasse Menu aus einem übergeordnetem Modul und die jeweiligen Texte des Spiels importiert. So hätte das aber nicht funktioniert, weil die importierte Klasse die Daten aus den text-Modulen garnicht hätte verwenden können. Jetzt kann ich alles aus einer Datei lesen und das Datenobjekt an die importierte Klasse übergeben. Habe mich aber statt für YAML für JSON entschieden, da Python direkt ein Modul für die Verwendung von .json-Dateien bereitstellt und die Syntax recht ähnlich mit der von YAML ist. Hatte vorher auch noch nie Kontakt zu solchen Dateien, werde sie jetzt aber sicher häufiger mal verwenden.
edit:
"""
min_sizes.json habe ich erstellt um die mindest Terminalmaße der einzelnen Spiele in main.py abrufen zu können. Ich wollte nicht jede Text/Grafik-Datei der Verschiedenen Spiele in main.py laden, nur damit ich die mindest Terminalmaße abrufen kann.
"""

Einzigstes, was ich noch als Modul belassen habe, ist die win() Funktion. Jeder Gewinnerbildschirm soll sich bewegen und den entsprechenden Gewinner angeben (später sollen Spielernamen eingegeben werden können und diese sollen dann entsprechend im Gewinnerbildschirm angezeigt werden). Ich hätte auch gerne die Gewinnachricht in der Mitte des sich bewegenden Gewinnerbildschirms. Würde ich den Namen oben anzeigen und unten den Bildschirm sich bewegen lassen, wäre das nicht ganz so kompliziert. Diese Funktionalität kann ich mit JSON nicht realisieren. Wenn es dort eine Möglichkeit gibt, wie im f-String, Variablen für eine Textstelle anzugeben, würde ich das auch gerne noch auslagern. Ich möchte auch ungerne diese Funktion in die jeweilige Spieldatei einfügen, weil manche Gewinnerbildschirme sehr viele "Bilder" brauchen um sich zu bewegen. Hatte mir zb. für vier gewinnt überlegt, den Bildschirm hinter dem Text von unten nach oben mit Steinen zubauen zu lassen. Das werden, so wie ich das jetzt mache, sicher über 50 "Bilder", weil ich ja jede Bewegung "vorzeichnen" muss.
Vllt fällt mir noch eine Möglichkeit ein, die Sterne in diesem Beispiel von einer Funktion zufällig erstellen zu lassen und den Text in der Mitte einfach im Vordergrund zu "zeichnen". Ein wenig schwebt mir dazu schon etwas vor, werde ich gleich mal versuchen... :geek:
Eine Idee habe ich noch, ich könnte die Bilder des sich bewegenden Bildschirms in JSON speichern, diese zeichnen lassen und nach jedem Bild den Namen des Gewinners an eine bestimmt Position schreiben lassen... :geek:
Muss nur noch überlegen, wie ich noch Farbe mit in den Gewinnerbildschirm einbauen kann... :geek:
Sorry für meinen Gedankfluss in den letzten drei Sätzen, aber mir ist die Idee beim Schreiben gekommen und bevor ich sie vergesse, habe ich sie hier kurzerhand notiert. 8)

sys.exit() aus terminal_test() habe ich jetzt komplett raus gelassen, weil wenn ich aus main.py das Spiel starte und das Spiel nach dem Spielen beende, komme ich direkt zurück zu main.py und könnte dann später ein anderes Spiel starten. Also war sys.exit() komplett überflüssig.

Alle Zahlen und Werte lese ich jetzt aus der .json Datei, was auch viel komfortabler ist.

In der Hauptschleife das

Code: Alles auswählen

else:
	break
erwartet kein "q" sondern ein False. Von Menu werden nur die Werte "p", "l", "m" und "s" zurückgegeben, wenn ein Spiel gestartet wird, sonst wird nur noch von goodbye() False zurückgegeben. Da die Module keine anderen Tasten entgegennehmen, als die die ich definiert habe und goodbye() nichts anderes zurückgibt, als False, habe ich das so belassen, wie es war.
Meinst Du es wäre dennoch sinnvoll dies in einem elif-Zweig abzufragen und einen else-Zweig, so wie Du es vorgeschlagen hast, hinzuzufügen?

Habe mich auch mit der Benennung verschiedener Namen auseinandergesetzt und fand jetzt im Endeffekt, dass ich es mit den von Dir vorgeschlagenen Namen auch besser Lesen konnte. Ich hatte immer y und x für die Koordinaten verwendet, weil curses immer ( y , x ) statt ( x , y ) haben will (zumindestens bei allem was ich von curses bis jetzt verwendet habe).
Hatte lap statt round verwendet, da round ja eine eingebaute Funktion ist und ich nicht wollte, dass diese so eventuell überschrieben wird. Hatte nämlich wirklich zuerst vor round als Namen zu verwenden... :D
Versuche eigentlich immer Namen, die in Python eingebaut sind nicht zu verwenden.

Logic habe ich fast komplett umgebaut, so dass jetzt zwei Objekte immer abwechselnd am Zug sind. Das war auch ein sehr guter Tip, danke. Kann jetzt auch den Computer gegen sich selber Spielen lassen. Vllt bastele ich daraus noch soetwas wie ein Demo Beispiel, damit man beobachten kann, wie ein Spiel gespielt wird.

In move_cursor() gebe ich jetzt statt "q" None zurück. Musste aber dennoch in Logic und 'Human darauf prüfen, damit auch mitten im Spiel abgebrochen werden kann.

Den Namensteil field habe ich größtenteils entfernt, außer bei draw_field(), da ich den Namen recht aussagekräftig finde. So weiß man, was genau "gezeichnet" wird und es grenzt sich ab von draw_figure().

Das enum-Modul habe ich nicht verwendet, da ich mich dort noch einarbeiten muss. Aber eventuell baue ich das nachträglich mit ein, möchte mich aber erstmal auf Klassen und OOP konzentrieren und am Projekt weiter basteln. Habe schonmal in der Python Doku etwas gelesen, sieht interessant aus. Verstehe ich das richtig, dass ich damit zb. die Strings "X" und "O" als Konstanten definieren könnte, die ich dann im kompletten Programm verwenden könnte? Wäre der Vorteil dies zu verwenden ein Speicherplatzvorteil, weil ich nicht überall die Strings "X" und "O" erstellen müsste?

Mit unittest oder pytest werde ich mich später sicher noch beschäftigen, weil ich das Testen von Code auch noch üben möchte. Habe beide im Tutorial schonmal benutzt, sonst noch nicht. Muss auch nochmal genau überlegen wie ich in diesem Projekt Tests realisieren könnte. Habe jetzt auf Anhieb überhaupt keine Idee, was ich wie testen könnte, da ja alles irgendwie zusammenhängt.

Nochmal vielen Dank, dass Du dir die Zeit genommen hast!

Und bevor ich es vergesse, vor lauter schreiben. Hier ist die geänderte Version vom Spiel, wieder extern hoch geladen:

https://www.file-upload.net/download-14 ... s.zip.html

Würde mich freuen, wenn Du dort auch nochmal drüber schauen und mir eine Einschätzung geben könntest.

ps.: Ein Problem hätte ich doch noch. Und zwar importiere ich in tic_tac_toe.py aus einer Ordnerebene höher aus imports.py und aus dem Ordner text. Dafür musste ich ganz am Anfang in tic_tac_toe.py den überübergeordeneten Pfad zu sys.path hinzufügen, weil sonst das Paket console_games nicht gefunden werden konnte und die imports daraus fehlschlugen. Vllt reicht es aus, nur eine Ordnerebene höher zu path hinzuzufügen, damit ich mich sozusagen im Ordner console_games "befinde" und daraus importiere. So könnte ich dann console_games. in den import-Anweisungen weg lassen.
Hatte erst in main.py versucht den Pfad an os.environ zu hängen und das veränderte environ an env von subprocess.run() zu übergeben.

Code: Alles auswählen

from os.path import dirname, realpath
from os import environ

my_env = environ.copy()
my_env["PATH"] = my_env["PATH"] + f":{dirname(dirname(realpath(__file__)))}"
Das hat auch funktioniert. Habe in tic_tac_toe.py dann environ ausgegeben und gesehen, dass es das von main.py an subprocess.run() übergebene war. Aber der import aus console_games hat dennoch nicht funktioniert.
Kann mir vorstellen, das meine Lösung nicht die Optimalste ist, weiß aber nicht wie ich das anders realisieren kann. Wäre mega, wenn Du, oder jemand Anderes, dafür eine Lösung hast/hat.

Gruß

_Mala_Fide_
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

Mein Problem mit dem sich bewegendem Gewinnerbildschirm und dem Import aus dem übergeordnetem Ordner hat sich soebend gelöst.

Mit dem Gewinnerbildschirm habe ich es so gelöst wie oben schon überlegt. Ich "zeichne" die Bilder, mit einer leeren Stelle für den Gewinnernamen auf den Bildschirm und schreibe nach jedem Bild den Namen des Gewinners sozusagen auf die Lücke drauf. So konnte ich jetzt die Bilder mit in die JSON-Datei packen und habe noch die Position für den Namen in der JSON-Datei vermerkt. Habe mich für eine maximale Namenslänge von 12 Zeichen entschieden, denke das sollte für viele Namen reichen.

Jetzt muss ich mir nur noch überlegen, wie ich den Gewinnerbildschirm farbig bekomme. Würde gerne rot und gelb für X und O verwenden und dann statt Sterne Xes und Os verteilen. Ich vermute da muss ich von meiner Lösung nochmal abweichen. :?
Aber da denke ich morgen drüber nach, wird langsam etwas spät...

Zwecks import-Problem: Ich hatte vorher schon überlegt, imports.py mit in den games Ordner zu packen, da das Modul nur von den Spielen benötigt wird. Hatte aber das Problem, dass das Modul mit der Gewinnerfunktion im Ordner text lag. Da ich die Texte/Grafiken und Codes in seperate Ordner trennen möchte, hätte ich trotzdem das aktuelle Verzeichnis zu sys.path hinzufügen müssen, um auf den Ordner text Zugriff zu haben. Da ich aber das Modul mit der Gewinnerfunktion jetzt löschen konnte, konnte ich imports.py mit in den Ordner games packen. So klappt der import jetzt problemlos, da ja alles in einem Ordner liegt.

Würde mich aber trotzdem freuen, wenn mir wer erklären kann, wie ich das import Problem gelöst bekommen hätte und warum das mit environ nicht funktioniert hat. Kann ja gut vorkommen, das ein Skript mal etwas aus einem Übergeordneten Ordner importieren muss.

Habe auch noch einen Fehler in tic_tac_toe.py gefunden.
Vorher stand das so dort:

Code: Alles auswählen

with open("./text/tic_tac_toe.json") as file:
    game_data = json_load(file)
Hat anscheinend funktioniert, weil das Arbeitsverzeichnis ja der Speicherort von main.py ist und sich durch den Aufruf von tic_tac_toe.py das Verzeichnis nicht ändert.
Ob das auf jedem System so funktioniert, weiß ich nicht, deswegen wollte ich lieber den kompletten Pfad angeben.

Code: Alles auswählen

path = dirname(dirname(realpath(__file__)))
with open(f"{path}/text/tic_tac_toe.json") as file:
    game_data = json_load(file)
Hier nochmal das neue Paket:

https://www.file-upload.net/download-14 ... s.zip.html

Gruß

_Mala_Fide_
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

Habe das Problem mit dem sich bewegenden Gewinnerbildschirm in Farbe gelöst bekommen.

Ich lasse das Menüfenster einfach mit den entsprechenden Figuren in Farbe füllen und überschreibe die Mitte mit dem Gewinntext.

Habe dazu einfach einen Generator und eine Funktion erstellt und die Funktion übergebe ich dann an die Klasse Menu.

Code: Alles auswählen

def animation_generator(obj):
    """
    Generator fuer animation()
    gibt Figurpositionen, Figuren und
    Farben fuer Figuren zurueck
    """
    animations = [(row, column)
                  for row in range(1,
                                   obj.data["MENU_HEIGHT"]-1,
                                   len(obj.data["WIN_FIGURES"][0]),
                                   )
                  for column in range(1,
                                      obj.data["MENU_WIDTH"]-1,
                                      len(obj.data["WIN_FIGURES"][0][0]),
                                      )
                  ]
    figure_index = 0
    color = 1
    for row, column in sample(animations, len(animations)):
        if not has_colors():
            color = 0
        yield (row, column, obj.data["WIN_FIGURES"][figure_index], color)
        figure_index += 1
        if figure_index > 1:
            figure_index = 0
        color += 1
        if color > 2:
            color = 1

def animation(obj, win_figure):
    """
    zeichnet den Gewinnerbildschirm
    """
    obj.menu.timeout(0)
    while True:
        obj.menu.clear()
        obj.menu.border()
        for animation in animation_generator(obj):
            (row, column, figure, color) = animation
            for align, text in enumerate(figure):
                obj.menu.addstr(row + align,
                                column,
                                text,
                                color_pair(color),
                                )
            for align, text in enumerate(obj.data["WIN"]):
                obj.menu.addstr(obj.data["WIN_ROW"] + align,
                                obj.data["WIN_COLUMN"],
                                text,
                                )
            obj.menu.addstr(obj.data["NAME_ROW"],
                            obj.data["NAME_COLUMN"],
                            f"{win_figure:^12}",
                            )
            obj.menu.refresh()
            sleep(0.3)
            if obj.menu.getch() == 113:
                obj.menu.timeout(-1)
                return
Die JSON-Datei habe ich auch entsprechend geändert, damit ich alle Daten direkt dort raus lesen kann.

Code: Alles auswählen

"WIN": ["  Herzlichen Glückwunsch!!! ",
        "                            ",
        "               hat gewonnen "
        ],

"WIN_ROW": 7,
"WIN_COLUMN": 12,

"NAME_ROW": 9,
"NAME_COLUMN" : 14,

"WIN_FIGURES": [["     ",
                 " # # ",
                 "  #  ",
                 " # # ",
                 "     "
                 ],

                ["     ",
                 " ### ",
                 " # # ",
                 " ### ",
                 "     "
                 ]
                ],
Gruß

_Mala_Fide_
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

Hallo,

ich habe jetzt lange wieder gebastelt und würde gerne als letzten Post (wenn es Seitens der Community keine Verbesserungsvoschläge mehr gibt, weil alles so weit ok ist) die fertige Version des Spiels bereitstellen.

Wieder extern hochgeladen, weil alles zusammen jetzt über 1400 Zeilen sind:

https://www.file-upload.net/download-14 ... s.zip.html

Habe noch eine Highscorefunktionalität erstellt, welche die Daten persistent mit pickle speichert. Der farbige Gewinnerbildschirm funktioniert auch nach meinen Vorstellungen. Der Tip mit den Texten/Grafiken/Daten aus einer Datei zu laden, war sehr hilfreich, danke nochmal.

Bitte nicht über die main.py Datei wundern, die ist nur übergangsweise so erstellt. Werde mich jetzt die Tage dran setzen und das Hauptmenü erstellen, indem die Spiele dann ausgesucht werden können.
Wenn ich das fertig habe, werde ich die Spielregeln in tic_tac-toe.json noch abändern, weil dort die Erklärung für die Verwendung der Taste "q" nicht rein gehört, dies werde ich in eine Anleitung des Hauptmenüs packen.

@__blackjack__
Ich bedanke mich nochmal herzlich für deine Hilfe und vorallem dafür, das Du dir die Zeit genommen hast und so viel Code durchgegangen bist.


Gruß

_Mala_Fide_


ps.: Würde mich noch freuen, wenn mir Jemand das Problem mit environ und dem Import aus übergeordneten Ordnern erklären könnte.
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

Schwerwiegender Fehler in main.py

Code: Alles auswählen

pickle.dump(dict(), file)
muss

Code: Alles auswählen

pickle_dump(dict(), file)
heißen... :? :shock: :o
narpfel
User
Beiträge: 643
Registriert: Freitag 20. Oktober 2017, 16:10

`pickle` ist nicht dazu gedacht, Sachen persistent zu speichern. Zum einen ändert sich da gerne mal das Format zwischen Python-Versionen. Außerdem hat man das Problem, dass die Daten nicht mehr deserialisiert werden können, wenn sich die Datentypen, die man serialisiert hat, ändern.

Und drittens ist das noch ein potentielles Sicherheitsrisiko, weil ein `pickle.load` Code, der in der Pickle-Datei gespeichert ist, einfach ausführt.

Besser man benutzt ein Format wie JSON.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Fangen wir mit der main.py an:
Kommentare, die nur aus Minuszeichen bestehen, sind wenig erklärend, stören nur das Lesen. Jeder Vernünftige Editor hilft Dir besser, die Übersicht zu behalten, als solche komischen Trenner.
Die Namen aus os.path importiert man normalerweise nicht direkt. os.path ist eh veraltet, man benutzt statt dessen pathlib.
`terminal_test` testet gar nicht das Terminal, sondern gibt True zurück, wenn es groß genug ist, sonst None; `is_terminal_large_enough` wäre also ein besserer Name, außerdem sollte es nicht implizit None zurückliefern, sondern False, und das geht am besten ohne if, sondern, indem man die Bedingung direkt per `return` zurückliefert.
In `pickle_exists` stückelst Du Pfade per Formatstring zusammen, das macht man nicht, sondern nutzt pathlib. Außerdem ist es wieder der falsche Name, weil es gar nicht prüft ob pickel existiert, sondern es schreibt eine Datei, wenn sie nicht existiert. Würde ich aber gar nicht machen, sondern nur beim Laden der Highscore-Datei eine entsprechende Behandlung einbauen.
In `main` wieder der selbe Fehler mit dem Pfad. Dann hast Du zwei verschachtelte if-Abfragen, aber nur bei einer ein else-Block mit entsprechender Fehlermeldung, im anderen Fall passiert einfach nichts.
Eigentich würde ich gar nicht `run` benutzen, sondern die Spiele so schreiben, dass sie importiert werden können. Aber wenn run, dann benutze sys.executable. Die anderen Dateien suchst Du relativ zur main.py, `games/tic_tac_toe.py` soll aber relativ zum aktuellen Arbeitsverzeichnis liegen, ist also falsch. Die drei Argumente sind alles Dinge, die tic_tac_toe auch selbst ermitteln könnte.
Nun ist `games` aber schon ein Paket-Verzeichnis (wegen der __init__.py), Du benutzt aber daraus ein Modul als ob es ein Ausführbares Skript wäre.

Die restlichen Dateien sind mir etwas unübersichtlich.
Wenn ich in tictactoe.main schaue, gibt es da eine `highscore`, der sehr viel Code dafür braucht, obwohl es nur darum geht, Punkten Namen zuzuordnen. Du hast da low-level-Steuerung des Highscores auf der obersten Ebene, das macht das ganz schwer verständlich.
Dann doppelt sich der Code, ob man mit oder ohne Computer spielt, das sollte nicht sein.
Die innere Schleife sollte ungefähr auf dem Abstraktionsniveau sein:

Code: Alles auswählen

    while True:
        screen.init()
        key = menu.welcome()
        if key in ["p", "l", "m", "s"]:
            player_one = Human(highscore.ask_player())
            if key == "p":
                player_two = Human(highscore.ask_player())
            else:
                player_two = Computer(highscore.get_computer(), key)

            game = Logic(game_data, field, player_one, player_two)
            game_result = game.run()
            highscore.write_scores(game_result)
            if not menu.decision(game_result):
                break
        elif key == "h":
            highscore.print_scores()
        else:
            break
`game_result` ist dann natürlich nicht nur X oder O, sondern enthält alle Information, um im Highscore dem richtigen Spieler die richtigen Punkte zuordnen zu können
Explizite Typprüfungen sind bei einem richtigen Objektdesign selten nötig.
Im Moment liefert `Logic.start` (was nicht nur das Spiel startet sondern es kompett durchführt, Stichwort falsche Namensgebung) `False`, "X", "O" oder implizit None zurück, es ist also sehr komplex, mit dem Rückgabewert weiterzuarbeiten.
Das implizite None taucht noch an vielen anderen Stellen im Code auf, hier bedeutet das ja Abbruch des Spiels, für solche Zwecke sind Exceptions da.
Menu.welcome ruft Menu.rules auf, was wiederum Menue.welcome aufruft. Solche Art Rekursion ist ein Fehler, weil rules direkt wieder zu welcome zurückkehren sollte. `vs_computer` hat das selbe Problem.
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

Hallo,

danke für eure Mühe. Hatte bevor ich eure Nachrichten gesehen hatte schon einiges am Programm geändert.

Ich hatte bereits game.start() (jetzt game.run()) so geändert, dass 'X', 'O', 'draw' und None zurückgegeben werden, weil ich das mit False auch sehr umständlich fand. So konnte ich auch in increment_score() die Punkte offentsichtlicher verteilen.

Weiterhin hatte ich field_position() in win_position() geändert, damit diese Funktion die Position eines beliebigen Fensters zurück gibt. So konnte ich die umständlichen '..._ALIGN'-Werte für die Fenster aus den json-Dateien entfernen und jede Klasse bekommt für ihr Fenster eigene Werte geliefert.

In Highscore habe ich auch einiges geändert. Die Werte für sminrow, smincol, smaxrow und smaxcol habe ich viel zu umständlich berechnet und die Fenstererstellung in __init__ habe ich auch angepasst. Von win_position() bekommt Higscore jetzt die Position für das große Ausgabefenster und für das Menü habe ich mit derwin() ein Fenster relativ zum großen Fenster erstellt.

Die Spielernamenabfrage habe ich angepasst. Jetzt wird vor jedem Spiel gefragt ob die eingegebenen Namen weiter verwendet werden sollen und es wird nicht mehr das Gleiche für jeden Namen gefragt.

Sonst habe ich weiter an main.py gearbeitet, weil ja dort das Menü rein soll, indem die Spiele ausgewählt werden, deswegen sieht die Datei noch so wüst aus.

Und dann habe ich noch eine Demofunktion, in der der Computer gegen sich selber spielt, in tic_tac_toe.py erstellt, mit der vom großen Hauptmenü angesehen werden kann, wie ein Spiel gespielt wird. Das finde ich recht cool, mal sehen, ob ich das für jedes Spiel hin bekomme. Bei jedem Spiel mit Computergegner sicher, bei Einzelspielerspielen, muss ich noch überlegen, wie ich das mache. Aber das dauert noch eine ganze Weile, muss erstmal noch vier gewinnt umschreiben und mit einfügen.

@narpfel

Auf das mit den json-Dateien hätte ich auch von selbst drauf kommen können. :roll:
Ich habe in den anderen json-Dateien ja schon die ganze Zeit mit Dictionarys gearbeitet, um die Texte und Grafiken abrufen zu können. Warum ich nicht auf die Idee gekommen bin, auch die Highscoreliste in so einer Datei zu speichern und aus ihr zu lesen. Hatte auch gelesen, dass pickle-Dateien als Angriff verwendet werden können und ein Sicherheitsrisiko darstellen, hatte aber keine andere Idee die Daten zu Speichern. pickle und shelve wurden im Tutorial behandelt, war deshalb anscheinend darauf fixiert. Manchmal sieht man den Wald vor lauter Bäumen nicht. Danke

@Sirius3

Die Kommentare/Trennlinien verwende ich schon ewig, da ich dort auch die 72 und 79 Zeichengrenze, die in pep8 beschrieben wird markiert habe. Am Anfang hatte ich nur eine Linie davon ganz oben unter der Shebang- und Codierungs-Zeile. Fand sie aber als Trennlinien jetzt bei meinem ersten längeren Code praktisch, da es für mich so einfacher zu lesen ist. Muss mich erst noch daran gewöhnen so viel Code zu lesen, vllt entferne ich sie Später, wenn es mir leichter fällt mich zurecht zu finden.

pathlib kannte ich noch nicht, finde es jetzt aber nach kurzem Einlesen sehr praktisch. Danke für den Tipp.

Die zwei if-Abfragen in main.py bleiben nicht so, hatte erst nur auf die min Größe von tic_tac_toe geprüft und danach kam Highscore dazu. Da das Highscorefenster Größer ist als das Spielfeld von tic_tac_toe, habe ich den if-Zweig des Spiels einfach nach rechts verschoben und den anderen darüber gepackt. Wird so nicht bleiben. Bin eh noch am Überlegen, wie ich das mit der Mindestterminalgröße mache. Ist an sich eigentlich nur sinnvoll die Größten Maße aller anzuzeigenden Inhalte zu nehmen und einmal ganz am Anfang zu testen, ob alles dargestellt werden könnte. Wäre sinnlos für jedes Spiel extra zu testen, so können manche Spiele bei einem kleinen Bildschirm angezeigt werden und manche nicht. Macht wenig Sinn bei einer Spielesammlung.
Habe schonmal überlegt, vielleicht die Textgröße des Terminals automatisch anpassen zu lassen, so dass alles drauf passt. Aber da weiß ich noch garnicht, wie ich vorgehen muss. Kommt auch erst später in Betracht, da erstmal Spiele und Hauptmenü wichtiger sind.

run() wollte ich benutzen, weil ich mir dachte alle Spiele zu importieren, wäre überflüssiger Speicherverbrauch, wenn ich mal nur ein Spiel spiele. Da ich schon oft gelesen habe, dass imports immer an den Anfang der Datei gehören und nicht mitten in den Code, hätte ich ja alle Spiel importieren müssen, um sie später zu starten. Hatte erst überlegt nach einer Spielauswahl das entsprechende Spiel zu importieren und dann zu starten, so würden aber imports mitten im Code stehen.
Beispiel:

Code: Alles auswählen

if game == "tic-tac-toe:
    import tic_tac_toe
    tic_tac_toe.start()
Weiterhin habe ich ja noch das Problem mit dem import aus übergeordneten Ordnern, oder Ordnern an einem ganz anderen Speicherort. Wollte nicht in jedem Spiel den Pfad des Übergeordneten Ordners zu path hinzufügen. Dachte mir, es wäre schön, Daten, die Jedes Spiel braucht, in main.py abzurufen und von dort an jedes Spiel zu übergeben. Daher auch die Übergabe der Terminalgröße und ehemalig path beim Aufruf mit run(). sys.executable kannte ich vorher auch noch nicht.

Das mit dem Paket war eigentlich sinnlos, jetzt so im Nachhinein betrachtet, weil dort ja nur ein Modul enthalten ist, von dem alle Spiele etwas importieren sollen. Habe jetzt wieder einen normal Ordner daraus gemacht.

Stichwort falsche Namensgebung: Da ich Englisch nicht so gut behersche (lesen kann ich es an sich sehr gut, aber selber schreiben fällt mir recht schwer) benutze ich meißt ein Übersetzungsprogramm oder ein Wörterbuch um passende Namen zu finden. Da passiert es mir recht oft, dass ich schlechte Namen gewählt habe und die später nochmal ändere.

Statt None für den Abbruch des Spiels habe ich jetzt exceptions verwendet und konnte damit die Abfrage auf Abbruch in game.run() entfernen. Sieht auch so verständlicher aus. Habe eine Quit-Klasse erstellt, die ich dann immer mit raise auslöse und mit except behandeln kann. So sehe ich gleich an welcher Stelle Das Spiel abgebrochen werden kann. Danke auch für diesen Tipp

Das mit den Sinnlosen Rekursionen habe ich auch entfernt.

Sonst habe ich auch soweit alles andere von Dir vorgeschlagene umgesetzt.

main() in tic_tac_toe.py wurde mit deinem Voschlag viel übersichtlicher. Enthält aber etwas mehr als dein Beispielcode, weil das Spiel ja auch ohne Namen gespielt werden kann und beim Spiel gegen den Computer der Startspieler zufällig gewählt wird. Habe überlegt den zufälligen Start auch beim Mensch gegen Mensch Modus zu verwenden. Werde ich morgen noch mit einbauen. Sollte ich in Highscore gut mit einbauen können, da dort eh die Namen gespeichert werden. Aber selbst dann wird main() glaube nicht noch kompakter, eher kommt noch eine Zeile mit rein.
Ich habe player_one, player_two und player_cpu verwendet, weil ich gerne wollte, das ein bereits eingegebener Name für Spieler 2 auch dann gespeichert bleibt, wenn gegen den Computer gespielt wird.

Zum Schluss wieder ein Link für die neue Version:

https://www.file-upload.net/download-14 ... s.zip.html

Vielen Dank nochmal für eure Zeit und Mühe, habe schon einiges Gelernt in diesem Projekt.


Gruß

_Mala_Fide_
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

Hatte noch vergessen zu schreiben, dass ich bei allen Tastenabfragen jetzt den Anfangsbuchstaben der Englischen Begriffe verwende, da q für quit ja schon englisch war und ich wenn dann alles gerne gleich habe. Musste für den schweren Computergegner leider t für tough verwenden, da h für highscore schon vergeben war. Hatte erst h, aber dann wurde das Spiel für Gegner hard beim drücken von h für highscore gestartet.
Benutzeravatar
Dennis89
User
Beiträge: 1124
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,
_Mala_Fide_ hat geschrieben: Sonntag 26. Juni 2022, 23:41 Die Kommentare/Trennlinien verwende ich schon ewig, da ich dort auch die 72 und 79 Zeichengrenze, die in pep8 beschrieben wird markiert habe. Am Anfang hatte ich nur eine Linie davon ganz oben unter der Shebang- und Codierungs-Zeile. Fand sie aber als Trennlinien jetzt bei meinem ersten längeren Code praktisch, da es für mich so einfacher zu lesen ist. Muss mich erst noch daran gewöhnen so viel Code zu lesen, vllt entferne ich sie Später, wenn es mir leichter fällt mich zurecht zu finden.
Ein ordentlicher Editor wurde schon angesprochen, da kannst du die gewünschte Zeilenlänge einstellen oder sogar die Kontrolle nach PEP8-Einhaltung.

Dann gibt es noch geschickte Programme, die viel hilfreicher sind, als Striche:

https://pypi.org/project/black/

https://pypi.org/project/isort/

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

Hallo,

habe jetzt das Hauptmenü für die Spielauswahl in main.py fertig gestellt. Hatte erst das Spiel daraus mit run gestartet, was auch funktioniert hat, aber ein komisches Verhalten ist nach dem Beenden des Spiels aufgetreten. Und zwar habe ich ja die Sichtbarkeit vom Cursor in alles Menüs deaktiviert. Habe ich dann das Spiel gestartet und wieder beendet, war der Cursor auf einmal überall im Hauptmenü sichtbar, was ich auch nicht dadurch ändern konnte, das ich curs_set(0) direkt nach verlassen des Spiels nochmal aufgerufen habe. Ich denke es liegt daran, dass das Hauptmenü und das Spiel jeweils mit wrapper() gestartet wurden. Und da wrapper() das Terminal nach beenden des Spiels wieder in den Normalzustand versetzt, wurde dieser Normalzustand an das Hauptmenü weitergegeben, bzw, wurden die von wrapper() im Hauptmenü gemachten Einstellungen (unter Anderem den Cursor ausblenden) einfach überschrieben. Da ich das so absolut nicht wollte, habe ich den Vorschlag von sirius3 mit dem Importieren der Spiele nochmal überdacht und es im Endeffekt auch hin bekommen, so dass alles funktioniert. Da ich aber nicht für jedes Spiel eine Abfrage mit einbauen wollte, habe ich den kompletten Ordner games importiert und starte dann je nach Spiel und Modus die Entsprechende Funktion. Im Ordner Games (bzw ist er jetzt wieder ein Paket) befinden sich jetzt nur noch die Spiele, imports.py liegt jetzt im gleichen Verzeichnis wie main.py
Und zwar habe ich das Ganze mit eval() ermöglicht bekommen. Weiß nicht, ob das so eine gute Lösung ist und ob es noch einen anderen Weg gibt, das so Ähnlich zu realisieren, wie ich. Wie schon geschrieben, würde ich ungerne für jedes Spiel den Quellcode von main.py nochmal anpassen müssen, damit ich es aufrufen kann.

Code: Alles auswählen

    def start(self, game, modus):
        """
        startet Spiel im Spiel- oder Demomodus
        """
        eval(f"games.{game}.{modus}")(self.screen, self.terminal_size)
        clear_border(self.screen, True, True)
        self.screen.refresh()
Weiß nicht, ob es in dieser Konstellation überhaupt Möglich ist Schadcode einzuschmuggeln?
game wird aus einer Liste der main.json Datei geladen und modus wird von der Funktion übergeben, die start() aufruft.

Ich stelle auch wieder das Komplette Paket zur Verfügung, habe diesmal auch alle Kommentare/Trennlinien entfernt...

https://www.file-upload.net/download-14 ... s.zip.html

Bin mir auch nicht ganz sicher, ob ich das mit dem Import des Spiels und dem Paket, welches das Spiel enthält so richtig gemacht habe. Könnte jetzt zum Beispiel nicht mehr tic_tac_toe.py selbständig, also ohne über main.py aufrufen, da so der import von imports nicht mehr funktioniert. Hatte erst überlegt, das mit try und except zu versuchen

Code: Alles auswählen

try:
    from imports import Screen, Menu, Highscore, Quit, win_position, clear_border
except ModuleNotFoundError:
    from ..imports import Screen, Menu, Highscore, Quit, win_position, clear_border
Beim Import kommt direkt ein Fehler, wenn ich das so schreibe.

Code: Alles auswählen

python3
Python 3.8.10 (default, Mar 15 2022, 12:22:08) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import tic_tac_toe
Traceback (most recent call last):
  File "/home/mala_fide/python/projekte/spielesammlung_konsole/console_games/games/tic_tac_toe.py", line 18, in <module>
    from imports import Screen, Menu, Highscore, Quit, win_position, clear_border
ModuleNotFoundError: No module named 'imports'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/mala_fide/python/projekte/spielesammlung_konsole/console_games/games/tic_tac_toe.py", line 20, in <module>
    from ..imports import Screen, Menu, Highscore, Quit, win_position, clear_border
ImportError: attempted relative import with no known parent package
Ist es denn wichtig jedes Modul so zu schreiben, dass es auch alleine aufgerufen werden kann, oder ist dies in meinem Fall kein Problem?


Gruß

_Mala_Fide_
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@_Mala_Fide_: Wenn man kein `eval()` braucht, dann verwendet man kein `eval()`. Und man braucht kein `eval()`. `getattr()` tut es hier beispielsweise auch.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

@__blackjack__
Super Tip, das hat funktioniert. Danke

Wusste garnicht, dass man getattr() so verwenden kann.
Wenn ich das richtig verstehe, sind Module sozusagen Attribute eines Paketes, Klassen/Funktionen/Variablen sind Attribute eines Moduls und ...?

Wofür wird eval() gebraucht?
Hatte im Tutorial bis jetzt gelesen, dass man damit Objekte von Klassen mit einem repr-String erstellen kann. Denke aber, da gibt es noch einiges Anderes?
Antworten