Slotcar Zeitmessung

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
swege
User
Beiträge: 6
Registriert: Sonntag 6. Juni 2010, 15:37

Hey,
als Abschluss dieses Schuljahres haben wir uns in kleinen Gruppen daran versucht, Rundenanzahl und Zeit einer Slotcarbahn zu messen, sowohl mit eigener Software als auch selbst zusammengebauter Hardware. Die Software habe ich komplett übernommen, da es allerdings einerseits wegen Zeitmangel höchstwahrscheinlich nicht mehr von unserem Lehrer bewertet werden kann, andererseits wir in der Schule nicht Python, sondern C lernen und ich nicht weiß, wie gut er sich mit Python auskennt, würde ich mich darüber freuen, wenn ihr mich auf typische Anfänger- / "C-Umsteiger"fehler hinweisen würdet, ich habe wenig praktische Erfahrung mit Python und möchte mir von vornherein keinen schlechten Stil angewöhnen. Beispielsweise habe ich es anfangs immerwieder geschafft, Dinge wie:

Code: Alles auswählen

liste = ['a', 'b', 'c', 'd']
for i in range(0, len(liste)):
    tu_was_mit(liste[i])
zu bringen, anstatt direkt über die Liste zu iterieren (oder heißt es korrekt "durch die Liste iterieren"?), kommt wohl durch die Gewöhnung an C zustande - und sicherlich habe ich auch einige andere "unpythonische" Dinge verbrochen :mrgreen:

Einige Fragen habe ich im Quelltext in Form von Kommentaren festgehalten, einige davon poste ich noch hier mit hinzu, damit sie nicht in Vergessenheit geraten:
  • Eine Methode verwendet mehrfach "self.builder". Ist es in Ordnung, zuvor "b = self.builder" zu schreiben, um die Zeilen zu kürzen und Zeilenumbrüche zu sparen (siehe PEP 8, maximal 79 Zeichen pro Zeile)? Verwirrt ein Kürzel eher, oder erhöht es die Leserlichkeit durch weniger Zeichen? (imho letzteres, weshalb ich es hier so gemacht habe) (siehe auch Zeile 73)
  • Laut PEP 8 soll man zu jeder Methode, Klasse, Funktion und Modul entsprechende Docstrings verfassen. Gilt dies auch für "spezielle" Methoden wie __init__? Mir fällt da nicht sonderlich viel zu schreiben ein...
  • PEP 8 hat geschrieben:Limit all lines to a maximum of 79 characters.
    Sollte man sich daran strikt halten, oder geht es auch in Ordnung, gelegentlich knapp über 80 Zeichen zu verwenden, statt gleich eine neue Zeile zu verbrauchen (was ja auch nicht unbedingt leserlich sein muss)?
  • PEP 8 hat geschrieben:The preferred way of wrapping long lines is by using [...]. Make sure to indent the continued line appropriately.
    Es sind zwar kurze Beispiele angegeben, trotzdem bin ich mir oft unsicher, was nun eine "angebrachte Einrückung" ist. Wenn man das in PEP 8 gegebene Beispiel

    Code: Alles auswählen

    if width == 0 and height == 0 and (color == 'red' or
                                       emphasis is None):
    
    nimmt und noch etwas verlängert:

    Code: Alles auswählen

    if width == 0 and height == 0 and (color == 'red' or
                                       emphasis is None or
                                       (foobar26 is None and
                                        foopy30 is None and
                                        (color == 'red' or
                                         color == 'blue')):
    
    sieht es auch irgendwann merkwürdig aus, wenn alles in die letzten paar Spalten gequetscht wird. (Ein besseres Beispiel fällt mir gerade nicht ein :lol: )
  • Leerzeilen und deren Einrückung - im Folgenden zwei Beispiele, die Leerzeichen habe ich zur Verdeutlichung durch Punkte ersetzt:

    Code: Alles auswählen

    class foo():
    ....
    ....
    ....def tuwas():
    ........pass
    ....
    ....def machwas():
    ........pass
    ....
    

    Code: Alles auswählen

    class foo():
    
    
    ....def tuwas():
    ........pass
    
    ....def machwas():
    ........pass
    
    Gibt es für solch "unsichtbaren Einrückungen" Konventionen?
  • Und weil's so schön ist, noch ein Codeschnipsel:

    Code: Alles auswählen

            self.players = ({'name': 'Gast',
                             'current_round': 0,
                             'round_time': [0],
                             'best_round': 0,
                             'current_time': 0,},
                            {'name': 'Gast',
                             'current_round': 0,
                             'round_time': [0],
                             'best_round': 0,
                             'current_time': 0,},)
    Das Ganze ist mir zu redundant, da beide Dictionarys dasselbe enthalten. Wie würde man das am elegantesten umsetzen?
Sollte für's erste an Fragen reichen, hier der ganze Kram: http://paste.pocoo.org/show/229124/
Bei der GTK Oberfläche war mir Glade behilflich, ich hoffe das war kein Fehler (ja, habe auch zuvor schon das ein oder andere manuell mit GTK erstellt) - hier die dazugehörige XML Datei: http://paste.pocoo.org/show/229128/

So, dann reißt mich und den Code mal auseinander... :)

Eins wollte ich noch loswerden: Python, GTK, PyGTK [...] unter Windows installieren ist doch ein Krampf, da lobe ich mir die Super Kuh Kräfte
BlackJack

@swege: Also ich halte von Methoden an einbuchstabige Namen binden um Zeilen zu kürzen nicht viel, wie von einbuchstabigen Namen grundsätzlich eher nichts. Das ist okay für die üblichen `i`, `j`, `k`-Laufvariablen an die ganze Zahlen gebunden werden -- wobei man die in Python wesentlich weniger benötigt als in Programmiersprachen wo man öfter Indexzugriffe in Listen/Arrays benötigt. Und in "list comprehensions" und Generatorausdrücken sind einbuchstabige Namen okay, weil da der Einsatz örtlich sehr begrenzt ist. Zu dem ``b = self.builder`` weiter unten mehr.

Zu `__init__()`-Methoden ohne Argumente ist es vielleicht nicht einfach Inhalt für einen Docstring zu finden, aber das gilt halt nicht generell für "spezielle" Methoden. Ich würde auch nicht sagen, dass man zu *jeder* Methode einen Docstring braucht. Zum Beispiel "Quit the program" bei der Methode `quit()` und "Run the program" bei der Methode `run()` sind ziemlich unnütze Docstrings. Docstrings sollten schon etwas verraten was nicht *so* offensichtlich ist.

Ich halte mich recht strikt an die 80-Zeichen-Grenze denn wenn man mehr tippt, verbraucht man in vielen Fällen doch eine weitere Zeile. Zum Beispiel in meinem Editor wo "überlange" Zeilen in der Anzeige umgebrochen werden und zum Beispiel auch im Terminal.

Bei dem Umbruchbeispiel hätte ich zusätzliche Klammern eingefügt und an den ``and``\s umgebrochen.

Die Player sollte man vielleicht als Klasse modellieren, dann stellt sich die Frage nach der Wiederholung nicht. Ansonsten hätte man auch eine Funktion schreiben können, die so ein Dictionary erstellt und diese dann zweimal aufrufen können.

Meine Gtk+-Version ist zu alt, bei mir läuft das leider nicht. Muss doch mal irgendwann das System aktualisieren. :-(

`Slotcar` sieht mir nach einer "Gott-Klasse" aus. Da ist ja wirklich alles drin -- die GUI, die Programmlogik und einiges was man sicher ganz gut als eigene Klassen modellieren könnte.

Konstante Daten sollten sich möglichst nicht wiederholen. Der Name der Spielerdatenbank sollte nur einmal im Programm stehen. Sonst muss man daran denken ihn an mehreren Stellen im Programm anzupassen, falls man ihn mal ändern möchte. Also irgenwo oben im Quelltext eine Konstante ``PLAYER_DB_FILENAME = 'players.db'`` oder so ähnlich anlegen.

Dateien sollten mit `open()` geöffnet werden. `file` ist als konkreter Datentyp zum Beispiel zum ableiten durch Unterklassen vorgesehen. Pickle-Dateien muss man im Binärmodus öffnen, sonst sind sie nicht portabel.

`quit()` lässt sich deutlich kürzen:

Code: Alles auswählen

    def quit(self, *args):
        gtk.main_quit()
        with open(PLAYER_DB_FILENAME, 'wb') as player_file:
            pickle.dump([list(row) for row in self.playerstore], player_file)
Bei `format_race_window()` sieht's so aus, als wenn eine eigene Klasse für die Darstellung eines Spielers Sinn machen würde. Und das hier sowohl `player` als auch `p` für einen Spieler stehen, aber unterschiedliche Daten beinhalten, ist IMHO unübersichtlich. Das ``b = …`` kann man zum Beispiel dadurch abschaffen, dass man den Aufruf sowieso nur einmal in den Quelltext schreibt. Die Aufrufe sind ja fast identisch und unterscheiden sich im Grunde nur durch die Zeichenketten. Wenn Du in der GUI 'head_player*` durch `header_player*` ersetzt, dann könnte man das `player`-Dictionary zum Beispiel so erstellen:

Code: Alles auswählen

            keys = ['round', 'header', 'current_time', 'round_time']
            player = dict((k, self.builder.get_object(k + '_player%d' % i))
                          for k in keys)
Ein Rücksetzen von Daten würde ich vermeiden und stattdessen lieber neue Datenstrukturen erstellen. In der entsprechenden Methode wiederholst Du ja schon wieder alle Schlüssel und Daten für die Player. Wenn Du da jemals was anpassen oder erweitern willst, musst Du das wieder an mehreren Stellen im Programm tun. Das ist fehleranfällig.

`start_race()` macht eindeutig viel zu viel und wenn man die Bedienung der Hauptschleife eines GUI-Toolkits übernimmt, macht man IMHO in aller Regel etwas fundamental falsch.
swege
User
Beiträge: 6
Registriert: Sonntag 6. Juni 2010, 15:37

Hey blackjack, vielen Dank für deine Anmerkungen!
BlackJack hat geschrieben: Die Player sollte man vielleicht als Klasse modellieren, dann stellt sich die Frage nach der Wiederholung nicht. Ansonsten hätte man auch eine Funktion schreiben können, die so ein Dictionary erstellt und diese dann zweimal aufrufen können.
Ok, habe sie nun als Klasse modelliert, vielleicht machen noch Methoden wie "get_last_round_time()" Sinn anstatt im Hauptprogramm per "player.round_time[player.current_round]" darauf zuzugreifen. Eine Klasse welche nur Variablen enthält sieht zumindest etwas merkwürdig aus... :K
BlackJack hat geschrieben:Meine Gtk+-Version ist zu alt, bei mir läuft das leider nicht. Muss doch mal irgendwann das System aktualisieren. :-(
Oh, mir war garnicht bewusst sonderlich spezielle Dinge verwendet zu haben, vielleicht hätte ich die Finger doch von Glade lassen sollen :oops:
BlackJack hat geschrieben:`Slotcar` sieht mir nach einer "Gott-Klasse" aus. Da ist ja wirklich alles drin -- die GUI, die Programmlogik und einiges was man sicher ganz gut als eigene Klassen modellieren könnte.
Daran habe ich bisher noch nichts geändert, aber ein Beitrag von Lunar aus einem etwas betagten Thread hört sich sehr sinnvoll an, ich denke ich werde versuchen die Grundfunktionalitäten als eigenes Modul ohne GUI umzusetzen. Falls jemand gute Dokumentationen / "Guidelines" / Tutorials zum Thema Trennung von GUI und Programmlogik kennt wäre es super wenn ihr auf diese verweisen könntet. ([wikipedia:Model_View_Controller:] habe ich beispielsweise bereits gelesen, es ist eben nur sehr allgemein gehalten)
BlackJack hat geschrieben:`quit()` lässt sich deutlich kürzen:

Code: Alles auswählen

    def quit(self, *args):
        gtk.main_quit()
        with open(PLAYER_DB_FILENAME, 'wb') as player_file:
            pickle.dump([list(row) for row in self.playerstore], player_file)
"with [...] as" musste ich eben nachschlagen, daher nur kurz eine Nachfrage ob ich es richtig verstanden habe: "open()" enthält eine __enter__() Methode die von "with" aufgerufen wird, welche die geöffnete Datei zurückliefert und diese wird durch "as" player_file zugewiesen. Nachdem der Block abgearbeitet wurde wird die __exit__() Methode von open() aufgerufen, welche die Datei wieder schließt, sodass player_file.close() entfällt?
An list comprehensions und Generatoren sollte ich mich wohl langsam gewöhnen (und mal den Unterschied begreifen...), sie nachzuvollziehen ist zwar meistens nicht so schwer, aber ich komme nie auf die Idee mal selbst welche zu bauen :lol:
BlackJack hat geschrieben:Bei `format_race_window()` sieht's so aus, als wenn eine eigene Klasse für die Darstellung eines Spielers Sinn machen würde. Und das hier sowohl `player` als auch `p` für einen Spieler stehen, aber unterschiedliche Daten beinhalten, ist IMHO unübersichtlich.
Whups, habe das andere in player_gui umbenannt.
BlackJack hat geschrieben:`start_race()` macht eindeutig viel zu viel und wenn man die Bedienung der Hauptschleife eines GUI-Toolkits übernimmt, macht man IMHO in aller Regel etwas fundamental falsch.
[/quote]
Hm, da werde ich wohl noch ein wenig länger überlegen müssen, mir fällt gerade keine andere Möglichkeit ein die im Fenster angezeigte aktuelle Rennzeit auf andere Weise zu aktualisieren als mit einer Schleife...

Edit: Ganz vergessen die neue Datei zu posten, wenn auch noch keine großartigen Änderungen enthalten sind: [http://paste.pocoo.org/show/230606/]
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

Ich habe deinen Code nur überflogen, aber wozu ist erstens die Klasse Logic und zweitens die Instanz dieser Klasse (nämlich logic) da? Die Klasse erscheint mir ziemlich sinnfrei, da sie nix macht. Und wenn ich im paste nach "logic" suche, kommt nur die Zuweisung "self.logic = Logic()" aus der __init__ heraus.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

@derdon: Das ist ein Feauture :P

Code: Alles auswählen

Interface().logic.feauture = True
the more they change the more they stay the same
swege
User
Beiträge: 6
Registriert: Sonntag 6. Juni 2010, 15:37

Wollte anfangen Logik und Oberfläche zu trennen, habe aber wie gesagt noch nicht wirklich was diesbezüglich gemacht, daher diese sinnlose "Logic" Klasse, und im Vergleich zum ersten Post sind ja noch ein paar andere Änderungen dabei, hab ich nur als "Zwischenergebnis" gepostet...
Antworten