Wieso entnimmt Python innerhalb einer While-Scheife im Command-Attribut den letzten Wert

Fragen zu Tkinter.
Antworten
Python1906
User
Beiträge: 29
Registriert: Mittwoch 9. November 2016, 13:52

Hallo,
ich bin erst neu hier im Forum und programmiere auch öfters mal mit Python.
Ich habe das Problem, dass wenn ich bei Programmen innerhalb einer While - Schleife mehrere Buttons erstelle,
denen ich einen Parameter übergebe, nicht der Wert übergeben wird, der während dem Schleifendurchlauf besteht,
sondern, bei jedem Button der Parameter übergeben wird, der nur für den letzten sein sollte.

Eines dieser Programme ist der "Prostarter".
Der soll dazu dienen, Programme von verschiedenen Programmiersprachen auszuführen bzw. sie anzuzeigen.

Das Problem liegt in Zeile 165 - 182 in showLabels().
Er soll die Datei PyData (siehe unten) lesen und die Informationen als Button auf dem Fenster root zeigen.
Wenn Sie diesen Code ausführen, werden Sie merken, dass wenn sie auf die zwei Buttons klicken,
die durch showLabels() erstellt wurden, geben beide xData2.py aus, obwohl der obere xData1.py und der untere xData2.py
ausgeben soll.
(Programm starten -> Klick auf Menü Python -> Meine Dateien)

Können Sie mir vielleicht weiter helfen?


PS:
Derzeit bin ich nur bei dem Menü "Python" zugange, wodurch Sie nicht auf Funktionen wie showCsData() etc.
achten müssen, sondern auf Funktionen mit "Py" und allgemeine (z.B.: showLabels()) Funktionen achten müssen.

ProStarter:

Code: Alles auswählen

from tkinter import *
import tkinter
from tkinter.simpledialog import askstring

root = tkinter.Tk(className = "ProStarter")
root.geometry("600x500")
root.language = ""

root.myDataB_py_ = []
root.data_py_ = []
root.reading_py_ = False

root.myDataB_cpp_ = []
root.data_cpp_ = []
root.reading_cpp_ = False

root.myDataB_cs_ = []
root.data_cs_ = []
root.reading_cs_ = False

root.myDataB_java_ = []
root.data_java_ = []
root.reading_java_ = False

root.myDataB_js_ = []
root.data_js_ = []
root.reading_js_ = False


def addData(lang, extension):
    addRoot = tkinter.Tk(className = lang + " - Datei hinzuf\xfcgen")
    addRoot.geometry("400x100")
    name = 0
    addRoot.x = False

    def send():
        if addRoot.x == False:
            addRoot.var1 = Label(addRoot)
            addRoot.var1.grid(row = 1, column = 3)
            addRoot.var2 = Label(addRoot)
            addRoot.var2.grid(row = 2, column = 3)
            addRoot.var3 = Label(addRoot)
            addRoot.var3.grid(row = 3, column = 3)
            addRoot.x = True
        else:
            addRoot.var1.config(text = "")
            addRoot.var2.config(text = "")
            addRoot.var3.config(text = "")

        def printERROR(row, var, exception):
            addRoot.geometry("900x100")
            var.config(text = exception)


        def check(T, variant, extension):
            if variant == 1:
                extensions = extension.split(" ")
                print(extensions)
                tmp = False
                for i in extensions:
                    if T.find(i) != -1:
                        tmp = True
                        break

                return tmp

            elif variant == 2:
                try:
                    checkData = open(T + name, "r", encoding = "UTF-8-sig")
                    checkData.close()
                    return True

                except Exception as ex:
                    print(ex)
                    return False


            else:
                if T != "":
                    return True
                else:
                    return False





        name = addRoot.fileT.get()
        where = addRoot.whereT.get()
        label = addRoot.labelT.get()
        nameBool = check(name, 1, extension)
        whereBool = check(where, 2, extension)
        labelBool = check(label, 3, extension)
        print(nameBool)
        print(whereBool)
        print(labelBool)

        addRoot.geometry("400x100")

        if nameBool == False:
            printERROR(1, addRoot.var1, "Die angegebene Datei kann durch falscher/fehlender Dateiendung nicht genommen werden!")

        if whereBool == False:
            printERROR(2, addRoot.var2, "Der angegebene Dateipfad bzw. die Datei existiert nicht bzw!")

        if labelBool == False:
            printERROR(3, addRoot.var3, "Das Label muss beschriftet sein!")




    addRoot.fileL = Label(addRoot, text = "Dateiname: ")
    addRoot.fileL.grid(row = 1, column = 1)
    addRoot.whereL = Label(addRoot, text = "Ort: ")
    addRoot.whereL.grid(row = 2, column = 1)
    addRoot.labelL = Label(addRoot, text = "Label: ")
    addRoot.labelL.grid(row = 3, column = 1)

    addRoot.fileT = Entry(addRoot)
    addRoot.fileT.grid(row = 1, column = 2)
    addRoot.whereT = Entry(addRoot)
    addRoot.whereT.grid(row = 2, column = 2)
    addRoot.labelT = Entry(addRoot)
    addRoot.labelT.grid(row = 3, column = 2)

    addRoot.backB = Button(addRoot, text = "zur\xfcck", command = addRoot.destroy)
    addRoot.backB.grid(row = 4, column = 1)

    addRoot.sendArgB = Button(addRoot, text = "erstellen", command = send)
    addRoot.sendArgB.grid(row = 4, column = 2)


    addRoot.mainloop()

def clear():
    dataB = 0
    tmp = False
    if root.language == "python":
        dataB = root.myDataB_py_

    elif root.language == "cpp":
        dataB = root.myDataB_cpp_

    elif root.language == "cs":
        dataB = root.myDataB_cs_

    elif root.language == "java":
        dataB = root.myDataB_java_

    elif root.language == "js":
        dataB = root.myDataB_js_

    else:
        tmp = True

    if tmp == False:
        for i in dataB:
            i.destroy()
            del i


def test(text):
    print(text)

def showLabels(data, arrayB, xdata):
    Rdata = open(data, "r", encoding = "UTF-8-sig")

    tmp = True
    while tmp == True:
        actLine = Rdata.readline()
        line = actLine.replace("\n","")
        if line == "":
            tmp = False

        else:
            actList = line.split(" | ")
            tmp2 = ""
            print(actList)



            arrayB.append(Button(root, text = actList[0], command = lambda: test(actList[3])))
            arrayB[len(arrayB)-1].grid( row = actList[1], column = actList[2])
            xdata.append(actList[3])



    Rdata.close()

def showPyData():
    root.myDataL.config(text = "Python:")
    clear()
    root.language = "python"
    showLabels("PyData", root.myDataB_py_, root.data_py_)
    if root.addBbool == False:
        root.addB = Button(root, text = "Datei\nhinzuf\xfcgen", command = lambda: addData("Python", ".py"))
        root.addB.grid(row = 1, column = 2)
        root.addBbool = True

    else:
        root.addB.configure(command = lambda: addData("Python", ".py"))

def showCppData():
    root.myDataL.config(text = "C++:")
    clear()
    root.language = "cpp"
    showLabels("CppData", root.myDataB_cpp_, root.data_cpp_)
    if root.addBbool == False:
        root.addB = Button(root, text = "Datei\nhinzuf\xfcgen", command = lambda: addData("C++", ".cpp .h"))
        root.addB.grid(row = 1, column = 2)
        root.addBbool = True

    else:
        root.addB.configure(command = lambda: addData("C++", ".cpp .h"))

def showCsData():
    root.myDataL.config(text = "C#:")
    clear()
    root.language = "cs"
    showLabels("CsData", root.myDataB_cs_, root.data_cs_)
    if root.addBbool == False:
        root.addB = Button(root, text = "Datei\nhinzuf\xfcgen", command = lambda: addData("C#", "unbekannt"))
        root.addB.grid(row = 1, column = 2)
        root.addBbool = True

    else:
        root.addB.configure(command = lambda: addData("C#", "unbekannt"))

def showJavaData():
    root.myDataL.config(text = "Java:")
    clear()
    root.language = "java"
    showLabels("JavaData", root.myDataB_java_, root.data_java_)
    if root.addBbool == False:
        root.addB = Button(root, text = "Datei\nhinzuf\xfcgen", command = lambda: addData("Java", ".java .class .jar"))
        root.addB.grid(row = 1, column = 2)
        root.addBbool = True

    else:
        root.addB.configure(command = lambda: addData("Java", ".java .class .jar"))


def showJsData():
    root.myDataL.config(text = "JavaScript:")
    clear()
    root.language = "js"
    showLabels("JSData", root.myDataB_js_, root.data_js_)
    if root.addBbool == False:
        root.addB = Button(root, text = "Datei\nhinzuf\xfcgen", command = lambda: addData("JavaScript", ".js"))
        root.addB.grid(row = 1, column = 2)
        root.addBbool = True

    else:
        root.addB.configure(command = lambda: addData("JavaScript", ".js"))

root.mainM = Menu(root)
root.config(menu = root.mainM)

root.pythonM = Menu(root)
root.mainM.add_cascade(label = "Python", menu = root.pythonM)
root.pythonM.add_command(label = "Meine Dateien", command = showPyData)

root.cppM = Menu(root)
root.mainM.add_cascade(label = "C++", menu = root.cppM)
root.cppM.add_command(label = "Meine Dateien", command = showCppData)

root.csM = Menu(root)
root.mainM.add_cascade(label = "C#", menu = root.csM)
root.csM.add_command(label = "Meine Dateien", command = showCsData)

root.javaM = Menu(root)
root.mainM.add_cascade(label = "Java", menu = root.javaM)
root.javaM.add_command(label = "Meine Dateien", command = showJavaData)

root.jsM = Menu(root)
root.mainM.add_cascade(label = "JavaScript", menu = root.jsM)
root.jsM.add_command(label = "Meine Dateien", command = showJsData)

root.myDataL = Label(root)
root.myDataL.grid(row = 1, column = 1)
root.addBbool = False
root.addB = Label(root)
root.addB.grid(row = 1, column = 2)
root.mainloop()
PyData:

Code: Alles auswählen

Test-Button | 2 | 1 | xData1.py
2. Test-Button | 3 | 1 | xData2.py
BlackJack

@Python1906: Freie Variablen in Funktionen werden erst aufgelöst wenn der Code ausgeführt wird, also in diesem Falle `actList`. Die Funktion wird ausgeführt wenn die Schaltfläche betätigt wird und zu *dem* Zeitpunkt ist `actList` an den Wert vom letzten Schleifendurchlauf gebunden.

Das ist aber alles kein Python. Das ist irgendeine andere Programmiersprache in Python-Syntax, aber so gar keine idiomatischer Python-Code.

Der Code hält sich nicht an den Style Guide for Python Code.

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

Für GUI-Programmierung braucht man objektorientierte Programmierung. Also eigene Klassen. Alle möglichen Attribute an ”fremde” Objekte zu hängen ist ein hässlicher Hack.

Die Daten für die verschiedenen Programmiersprachen sollten selbst wieder in einer Datenstruktur stehen statt für jede ”parallele” Attribute einzuführen. Die ganzen `show*Data()`-Funktionen sind nahezu identisch, das sollte auch nur eine Funktion oder ein Datentyp sein, der entsprechend parametrisiert werden kann.

Es darf in einem Programm nur ein `Tk`-Exemplar geben. Das ist *das* Hauptfenster, da hängt der Tk-Interpreter dran und der ist nicht „reentrant“. Wenn man da mehr als ein Exemplar hat, dann ist das Programmverhalten undefiniert. Für Zusätzliche Fenster ist `Toplevel` da.

Man macht keine expliziten Vergleiche mit `True` oder `False`. Bei `True` kommt da sowieso nur wieder `True` heraus, das ist also sinnfrei, und wenn man auf das Gegenteil testen will, kann man den Wert mit ``not`` negieren. Also statt ``if spam == True:`` einfach nur ``if spam:`` und statt ``if spam == False:`` besser ``if not spam:``.

Die Ausmasse eines Fensters oder Widgets sollte man nicht selber ändern. Der jeweilige Layoutmanager sorgt schon dafür, dass Fenster/Widgets so gross sind, das ihr Inhalt hinein passt. Wenn man das selbst festlegt ist bestenfalls zu Freifläche um den Inhalt und im schlechten Fall muss der Benutzer das Fenster erst grösser ziehen um den Inhalt zu sehen.

Wenn man anfängt Namen durchzunummerieren, möchte man in dem meisten Fällen eigentlich keine Einzelnamen sondern eine Datenstruktur. Oft ist das eine Liste.

Funktionen sollten genau eine Aufgabe erledigen. Die `check()`-Funktion tut drei verschiedene Sachen, je nachdem ob man eine 1, 2, oder 3 als ”magische” Zahl übergibt. Das sollten drei Funktionen sein, denen man am Namen ablesen kann *was* sie prüfen.

`i` sollte man insbesondere wenn es Laufvariable in einer Schleife ist, nicht an etwas anderes als ganze Zahlen binden. Das ist sehr verwirrend.

`str.find()` ist nicht die richtige Methode um zu prüfen ob eine Zeichenkette in einer anderen enthalten ist. Dafür ist der ``in``-Operator da. Das ist hier aber auch die falsche Operation, denn Du willst ja gar nicht wissen ob die Endung irgendwo *innerhalb* des Dateinamens vorhanden ist, sondern ob der mit der Endung *endet* → `str.endswith()`. Der Test ist dann eigentlich auch nur Ein einzeiler: ``return filename.endswith(tuple(extensions.split()))``.

Zum öffnen/schliessen von Dateien sollte man wenn möglich die ``with``-Anweisung hinzuziehen.

Statt aufgrund einer ``if``-Bedingung explizit `True` oder `False` zurückzugeben ist umständlich. Das Ergebnis der Bedingung ist ja bereits ein Wahrheitswert, den man auch direkt zurückgeben kann.

`name` wird in `send()` erst an eine 0 gebunden, die nirgends verwendet wird, und später dann an eine Zeichenkette. Den selben Namen sollte man möglichst immer an Werte vom gleichen (Duck)Typ binden. Sonst verwirrt man den Leser, also potentiell auch sich selbst. Bei `dataB` in `clear()` das gleiche. Eine 0 ist kein passender Wert für eine Initialisierung wenn der eigentliche Typ ein anderer sein soll. Das wäre vielleicht `None`, aber wenn es irgend geht sollte man das gar nicht erst machen sondern dem Namen dann einen Wert zuweisen wenn man ihn auch *hat* und nicht vorher irgend einen Dummywert. Wenn die Daten statt auf verschiedene Attribute verteilt zum Beispiel in einem Wörterbuch stehen würden, könnte die gesamte `clear()`-Funktion aus nur drei Zeilen bestehen:

Code: Alles auswählen

def clear(root):
    for button in root.language2buttons.get(root.language, []):
        button.destroy()
Die ``del``-Anweisung in `clear()` ist sinnfrei der sollte weggelassen werden. Das tut nicht das was Du denkst was es tut.

Konkrete Grunddatentypen haben in Namen nichts zu suchen. Der wird während der Programmentwicklung gerne mal geändert und dann muss man überall die betroffenen Namen ändern, oder man hat irreführende Namen im Programm. Auch irgendwelche kryptischen Typabkürzungen sollte man vermeiden. Der Name soll dem Leser vermitteln was der Wert im Kontext des Programms bedeutet, und nicht zum rätseln verführen.

In `showLabels()` sollte man ``with`` für die Datei verwenden. Und eine ``for``-Schleife über die Datei statt der ``while``-Schleife in der dann die Zeilen gelesen werden. Wenn eine Leerzeile gelesen wird, sollte man einfach die Schleife mit ``break`` verlassen statt ein zusätzliches Flag zu verwenden. Zeilenenenden können in diesem Fall nur am Ende von `line` stehen, also macht es keinen Sinn die in der kompletten Zeichenkette zu ersetzen. Da wäre ein `rstrip()` ohne Argumente sinnvoller. Ohne Argumente damit auch Leerzeichen entfernt werden, denn sonst wird eine Zeile in der nichts ausser „whitespace“-Zeichen stehen nicht als Leerzeile gewertet, was überraschend wäre.

`actList` sollte wahrscheinlich `curList` heissen, weil aktuell auf Englisch „current“ heisst und nicht „actual“ (was „tatsächlich“ heisst). Aber eigentlich sollte dieses Zwischenergebnis eher gar keinen Namen haben. Es wäre besser die drei Bestandteile gleich an passende Namen zu binden. Zum Beispiel ``text, row, column = line.split(' | ')``.

``arrayB[len(arrayB)-1]`` ist ziemlich umständlich für ``arrayB[-1]``. Wobei `array*` kein guter Name für eine Liste ist. Arrays gibt es in Python auch, das ist aber ein anderer Datentyp.

Eine Liste an eine Funktion als Argument übergeben und dann in der Funktion weitere Elemente hinzufügen kann man machen, ist aber funktional gesehen nicht wirklich sauber. So etwas sollte man auf jeden Fall dokumentieren, denn damit rechnet kaum jemand. Wenn eine Funktion Ergebnisse produziert, werden die eigentlich zurückgegeben und dem Aufrufer wird überlassen was er damit macht. Also zum Beispiel schon vorhandene Listen mit den Elementen erweitern. Es werden dort auch zwei ”parallele” Listen gefüllt/erweitert. Da ist ziemlich wahrscheinlich, dass man nur eine Liste haben möchte die als Elemente zum Beispiel (named)tuple enthält oder Exemplare von einer eigenen Klasse.
Python1906
User
Beiträge: 29
Registriert: Mittwoch 9. November 2016, 13:52

@BlackJack

Vielen Dank für Ihre Hilfe,

vieles, was Sie mir schrieben wusste ich selber noch nicht.

Ich nehme Ihren Ratschlag gerne entgegen. :)
Antworten