Zahlen aus Datei laden und Daten danach weiterverarbeiten

Fragen zu Tkinter.
Antworten
Frank37
User
Beiträge: 3
Registriert: Samstag 8. Februar 2014, 20:16

Hallo,
ich beschäftige mich seit einem knappen Monat mit Python, bin also noch Anfänger mit etwas Scheu vor Objektorientierung, was sich aber ändern soll. :D

Mein Programm soll folgendes tun:
Aus einer Textdatei, die mit kommaseparierten Zufallszahlen von 1-6 gefüllt ist, sollen die Zahlen eingelesen werden und dann verschiedenen
Tests unterzogen werden, z.B. soll auf Gleichverteilung geprüft werden, also sind genauso viele 1en wie 2en ... wie 6en vorhanden usw.

Code: Alles auswählen

from Tkinter import *
from ScrolledText import *
import tkFileDialog
import tkMessageBox

def callback():
    print "Menuitem geklickt"

def open_command():
        file = tkFileDialog.askopenfile(parent=root,mode='rb',title='Select a file')
        if file != None:
            contents = file.read()
            textPad.insert('1.0',contents)
            filename = file.name
            file.close()
            return filename
            
def save_command(self):
    file = tkFileDialog.asksaveasfile(mode='w')
    if file != None:
    # slice off the last character from get, as an extra return is added
        data = self.textPad.get('1.0', END+'-1c')
        file.write(data)
        file.close()
            
def exit_command():
    if tkMessageBox.askokcancel("Quit", "Do you really want to quit?"):
        root.destroy()

def gleichverteilung_command(data_file):
    for line in file(data_file):
        arr = line.split(',')
        max_value = max(arr)
        anz = len(arr)
        eins = arr.count('1')
        zwei = arr.count('2')
        drei = arr.count('3')
        vier = arr.count('4')
        fuenf = arr.count('5')
        sechs = arr.count('6')
        
        print max_value
        print anz
        print eins, zwei, drei, vier, fuenf, sechs

root = Tk()
root.title('Zufallszahlenprogramm')
myMenu = Menu(root)
root.config(menu = myMenu)

textPad = ScrolledText(root, width=100, height=50)

filemenu = Menu(myMenu)
myMenu.add_cascade(label="Datei", menu=filemenu)
filemenu.add_command(label="Neu", command=callback)
filemenu.add_command(label="Laden", command=open_command)
filemenu.add_command(label="Speichern", command=callback)
filemenu.add_separator()
filemenu.add_command(label="Exit!", command=exit_command)

analysismenu = Menu(myMenu)
myMenu.add_cascade(label="Analyse", menu=analysismenu)
analysismenu.add_command(label="Gleichverteilung", command=lambda arg='1.txt': gleichverteilung_command(arg))

textPad.pack()
root.mainloop()
Der Menupunkt "Laden" ruft die open_command()-Funktion auf.
Diese liest die ausgewählte Datei ein und gibt sie mittels textPad.insert im Fenster aus.
Als return-Wert möchte ich gern den Filenamen zurückgeben, denn es soll ja möglich sein, aus dem Analysemenu später noch verschiedene andere Zufallstests aufzurufen, die sich dann auf die anfangs gewählte Datei beziehen.

Jetzt zu meinem Problem. Wie komme ich an den per Return zurückgegeben Filenamen?
filename = filemenu.add_command(label="Laden", command=open_command) funktioniert nicht.

Ist mein Ansatz für dieses Vorhaben überhaupt sinnvoll?
Eine zweite Variante wäre ja, nicht den Filenamen, sondern eine Liste mit den Zufallszahlen aus der open_command()-Funktion zurückzugeben und damit zu arbeiten. Daran bin ich aber im Moment noch gescheitert.

Damit ich die Funktion gleichverteilung_command überhaupt aufrufen kann, habe ich testweise der Lambdafunktion als Argument den Dateinamen mitgegeben, was ja so nicht bleiben kann.
Vielleicht verstehe ich das mit der Lambdafunktion auch noch irgendwann, im Moment ist es mir noch nicht ganz klar, da werde ich noch etwas lesen ...
Die print-Anweisungen in der Funktion gleichverteilung_command dienen im Moment nur Testzwecken, später sollen die Anzahl 1en, 2en usw. grafisch als Balkendiagramm im Fenster ausgegeben werden.

Könnt Ihr mir mal einen Anschubser geben?
Kennt jemand 'ne Seite, mit Code von so ähnlichen Programmen, den ich studieren könnte.
Bis jetzt fand ich nur sehr komplexe Programme, die meinen Verständnishorizont leider noch überstiegen.

Gruß
Frank
BlackJack

@Frank37: An den Rückgabewert kommst Du gar nicht denn die Funktion wird nicht aufgerufen wenn die `add_command()`-Methode ausgeführt wird, sondern irgendwann viel später von der GUI-Hauptschleife wenn der Benutzer den Menüpunkt auswählt. Und die GUI-Hauptschleife kann mit solchen Rückgabewerten nichts anfangen.

Du musst die Scheu vor objektorientierter Programmierung (OOP) überwinden, denn damit löst man das.

Wobei das `gleichverteilung_command()` auch die Datei nicht selber noch mal öffnen und durchgehen sollte. Der Benutzer würde hier erwarten, dass die Analyse über die angezeigten Daten gemacht wird. Die Datei kann sich in der Zwischenzeit ja verändert haben. Oder der Benutzer hat etwas an den angezeigten Daten verändert und würde zurecht erwarten, dass das beim Auswerten berücksichtigt wird.

Wenn man auf Zahlen operieren möchte, dann sollte man die Werte auch tatsächlich in Zahlen umwandeln. Abkürzungen und zu allgemeine Namen sollte man vermeiden. Also zum Beispiel `arr` und `anz`. `values` und `total_count` wäre zum Beispiel etwas passender. Statt sechs mal die Werte durchzugehen um jedweils einen Wert zu zählen böte sich `collections.Counter` an.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert und Funktionen sollten nur auf Werte zugreifen die als Argumente übergeben wurden. Da kommt dann ins Spiel das man hier OOP braucht.

Bezüglich Konventionen bei der Namensschreibweise könnte ein Blick in den Style Guide for Python Code nicht schaden.

Sternchenimporte machen den Quelltext unübersichtlich. Man weiss nicht mehr wo welcher Name her kommt und wenn mehrere Module gleiche Namen definieren bekommt man Namenskollisionen.
Frank37
User
Beiträge: 3
Registriert: Samstag 8. Februar 2014, 20:16

Hallo BlackJack,
danke für die vielen Tipps.
@Frank37: An den Rückgabewert kommst Du gar nicht denn die Funktion wird nicht aufgerufen wenn die `add_command()`-Methode ausgeführt wird, sondern irgendwann viel später von der GUI-Hauptschleife wenn der Benutzer den Menüpunkt auswählt. Und die GUI-Hauptschleife kann mit solchen Rückgabewerten nichts anfangen.
Verstehe, das heißt, es ist auch bei anderen Anwendungsfällen nie möglich aus einer mittels Menupunkt aufgerufenen Funktion einen Rückgabewert zu erhalten?
Du musst die Scheu vor objektorientierter Programmierung (OOP) überwinden, denn damit löst man das.
Bisher habe ich mich in die Theorie eingelesen, aber richtig verstehen kann man es wohl nur, wenn man konkret etwas programmiert.
Deshalb wage ich mich auch an dieses für einen Anfänger etwas zu schwierige Projekt. :)
Kannst Du mir aus dem Stehgreif für den Einstieg in die OOP etwas empfehlen?
Das Problem beim Lesen fremden Quellcodes ist für mich, dass Anfänger wie ich, den Code ins Internet setzen und dieser dann schlechten Stil vermittelt, den ich mangels besseren Wissens übernehme.
Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert und Funktionen sollten nur auf Werte zugreifen die als Argumente übergeben wurden. Da kommt dann ins Spiel das man hier OOP braucht.
Modulebene bezeichnet das, was man als Hauptprogramm aus anderen Programmiersprachen kennt?
Also steht auf Modulebene nur:
textPad.pack()
root.mainloop()?
Bezüglich Konventionen bei der Namensschreibweise könnte ein Blick in den Style Guide for Python Code nicht schaden.
Danke für den link, bin dabei es zu assimilieren.
Sternchenimporte machen den Quelltext unübersichtlich. Man weiss nicht mehr wo welcher Name her kommt und wenn mehrere Module gleiche Namen definieren bekommt man Namenskollisionen.
Habe jetzt nur noch

Code: Alles auswählen

from Tkinter import *
im Quelltext. Kann das so bleiben?
BlackJack

@Frank37: Wie gesagt, die Rückruffunktionen die man für bestimmte Ereignisse registeriert, wie das der Anwender eine Schaltfläche gedrückt hat oder einen Menüpunkt ausgewählt hat, werden von der GUI-Hauptschleife aufgerufen. Wenn man der jetzt irgend einen Wert zurück gibt, wie beispielsweise einen Dateinamen, was sollte der Code in der GUI-Hauptschleife damit anfangen?

Für absolute Programmieranfänger wird oft Learn Python The Hard Way empfohlen. Keine Ahnung ob das für Dich nicht mittlerweile *zu* langsam/detailliert vorgeht.

Das Problem ist das nicht nur Anfänger schlechtes, unpythonisches OOP ins Netz stellen, sondern dass das auch Leute machen die auf den ersten Blick wie erfahrene Autoren aussehen, die aber ihre Programmiererfahrungen offenbar von anderen Programmiersprachen 1:1 auf Python übertragen. So etwas geht in der Regel nur gut wenn die Sprachen sich sehr ähnlich sind.

Code auf Modulebene bezeichnet alles was direkt ausgeführt wird wenn man das Modul importiert. Also im Modul im ersten Beitrag wären das die Importe, die ``def``-Anweisungen welche die Funktionen definieren und alles ab Zeile 46 (inklusive). In in diesem letzten Abschnitt werden einige Namen auf Modulebene eingeführt die an Werte gebunden werden die veränderbar sind, und aus so benutzt werden, wie `root`, `myMenu`, `textPad`, `filemenu`, und `analysismenu`.

Der `Tkinter`-Sternchenimport holt cirka 200 Namen in das Modul. Bei `Tkinter` wird oft ``import Tkinter as tk`` verwendet um einerseits beim Import nicht alles aufführen zu müssen was man importieren möchte, und andererseits den Quelltext durch den Modulnamen nicht zu stark zu ”belasten”.

Normalerweise trennt man GUI und Programmlogik übrigens auch schärfer, so dass man die Funktionalität auch ohne GUI oder mit einer anderen GUI verwenden kann, und auch isoliert ohne GUI testen kann. Bisher sieht es in diesem Beispiel noch so aus als wenn das übertrieben wirken könnte, weil die Funktionalität bisher recht simpel ist.
Frank37
User
Beiträge: 3
Registriert: Samstag 8. Februar 2014, 20:16

@Frank37: Wie gesagt, die Rückruffunktionen die man für bestimmte Ereignisse registeriert, wie das der Anwender eine Schaltfläche gedrückt hat oder einen Menüpunkt ausgewählt hat, werden von der GUI-Hauptschleife aufgerufen. Wenn man der jetzt irgend einen Wert zurück gibt, wie beispielsweise einen Dateinamen, was sollte der Code in der GUI-Hauptschleife damit anfangen?
Irgendwie muss man doch aber zum Beispiel den ausgewählten Dateinamen sichern können. Mir schwahnt aber schon, dass das mit der OOP anders zu lösen ist. Wahrscheinlich muss ich wohl doch ein paar Beispiele durchprogrammieren, die nicht wirklichen Anwendungsnutzen für mich haben, damit ich dass Konzept mehr verinnerliche.
Für absolute Programmieranfänger wird oft Learn Python The Hard Way empfohlen. Keine Ahnung ob das für Dich nicht mittlerweile *zu* langsam/detailliert vorgeht.
Da bin ich gerade dran, auch wenn Manches in den ersten Kapiteln zu einfach ist, gibt es immer wieder Details, auf die ich so nie gekommen wäre.
Manche englische Formulierungen sind für mich nicht so einfach zu verstehen, aber insgesamt gefällt mir das Werk sehr gut. Leider lässt mir mein Privatleben nicht so viel Zeit, wie ich gern dafür hätte, dann wäre ich mit dem Tutorial schon durch :) .
In in diesem letzten Abschnitt werden einige Namen auf Modulebene eingeführt die an Werte gebunden werden die veränderbar sind, und aus so benutzt werden, wie `root`, `myMenu`, `textPad`, `filemenu`, und `analysismenu`.
Hat das konkrete Nachteile oder ist das einfach nur unsauberer Stil?

Ich habe jetzt "import Tkinter as tk" in meinem Quelltext stehen.
Woher weiß ich nun aber, bei welchen Objekten ich das tk. voranstellen muss? Oder kennt man die nach einiger Zeit alle oder druckt man sie mit dir(Tkinter) aus und schaut immer nach?
Normalerweise trennt man GUI und Programmlogik übrigens auch schärfer, so dass man die Funktionalität auch ohne GUI oder mit einer anderen GUI verwenden kann, und auch isoliert ohne GUI testen kann. Bisher sieht es in diesem Beispiel noch so aus als wenn das übertrieben wirken könnte, weil die Funktionalität bisher recht simpel ist.
Jetzt wird mir klar, warum manche Beispiele für mich wie "mit Kanonen auf Spatzen schießen" wirkten.
Das macht natürlich Sinn.
BlackJack

@Frank37: Wenn man eine Methode für den Rückruf verwendet hat man ja Zugriff auf das Objekt auf dem die Methode aufgerufen wurde. Und dort kann man dann zum Beispiel den Dateinamen als Attribut speichern.

Unsauberer Stil ist deswegen unsauber weil er konkrete Nachteile hat. ;-) Es besteht bei Variablen auf Modulebene die Gefahr das man absichtlich oder aus versehen darauf zugreift und damit Funktionen und Methoden auf undurchsichtige Art und Weise von mehr abhängen als der Leser das erwartet. So ein Programm ist dann schwerer zu verstehen und zu warten und auch nicht leicht zu testen, weil man zum Beispiel nicht nur eine Funktion mit den entsprechenden Argumenten aufrufen muss und sich dann den Rückgabewert anschauen kann, sondern man auch wissen muss was man vorher ausserhalb der Funktion vorbereiten muss oder welche Seiteneffekte der Funktionsaufruf so alles hat.

Gegenfrage: Woher weisst Du bei einem ``from Tkinter import *`` welche Namen Du dadurch verwenden kannst die vorher nicht im Modulnamensraum vorhanden waren? Die Frage stellt sich also so oder so und die Antwort ist nachsehen, entweder in der Dokumentation oder direkt im Modul. Wobei man im Modul vielleicht mehr findet als dokumentiert ist und das dann bei einigen Module oder Paketen mit Vorsicht verwenden sollte.
Antworten