Programm umschrieben für if __name__ == "__main__":

Fragen zu Tkinter.
Antworten
LeoPold
User
Beiträge: 7
Registriert: Freitag 5. März 2021, 12:52

Hallo Zusammen,

ich würde gerne mein Programm umschreiben, damit es auch mit if __name__ == "__main__": läuft.
Leider stecke ich da irgendwie fest. Ich habe schon einiges probiert. Ich habe es dann allerdings nur geschafft ohne, dass sich die Farbe der "textbox_02" ändert.
Ansonsten habe ich die Fehlermeldung "NameError: name 'userInput' is not defined" bekommen.
Habt ihr da ein paar Tipps für mich?
Vielen Dank schon Mal.
LeoPold

Code: Alles auswählen

import tkinter as tk

# Länder Wörterbuch
country_dict = {
    "Albanien": "AL", "Andorra": "AD", "Aserbaidschan": "AZ", "Bahrain": "BH", "Belgien": "BE", "Bosnien und Herzegowina": "BA", "Brasilien": "BR",
    "Britische Jungferninseln": "VG", "Bulgarien": "BG", "Costa Rica": "CR", "Dänemark": "DK", "Deutschland": "DE", "Dominikanische Republik": "DO",
    "El Salvador": "SV", "Estland": "EE", "Färöer Inseln": "FO", "Finnland": "FI", "Frankreich": "FR", "Georgien": "GE", "Gibraltar": "GI",
    "Griechenland": "GR", "Grönland": "GL", "Grossbritannien": "GB", "Guatemala": "GT", "Irak": "IQ", "Irland": "IE"
}
country_dict_inv = {v: k for k, v in country_dict.items()}

# Funktion für die Umwandlung von Buchstaben in Zahlen:
def encode(countrycode):
    ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    number = ALPHABET.index(countrycode) + 10
    return number

# Funktion für den IBAN-Check:
def ibanCheck(iban_list, iban):
    iban_summe = ""
    for i in range(4, len(iban_list)):
        iban_summe = iban_summe + str(iban_list[i])
    summe = int(iban_summe + str(iban_list[0]) + str(iban_list[1]) + "0" + "0")
    check = str(98 - summe % 97).zfill(2)
    if check == iban[2:4]:
        return True
    else:
        return False

# Funktion für die Prüfung der IBAN und die Ausgabe der Textausgabe:
def checkInput(*args):
    iban = userInput.get()
    iban = iban.replace(" ", "").upper()
    print(iban)

    if iban[0:2].isalpha() and iban[2:4].isdigit():
        output_text_01.set("Deine Eingabe: " + str(iban))
        iban_list = [d if d.isdigit() else str(encode(d)) for d in iban]

        if ibanCheck(iban_list, iban) == True and iban[0:2] in country_dict_inv:
            output_text_02.set("Der Ländercode " + str(iban[0:2]) + " entspricht: " + str(
                country_dict_inv.get(iban[0:2])) + ". Die Prüfung war erfolgreich!")
            textbox_02["fg"] = "green"
        elif ibanCheck(iban_list, iban) == True and iban[0:2] not in country_dict_inv:
            output_text_02.set("Der Ländercode ist 'nicht' bekannt. Die Prüfung war erfolgreich!")
            textbox_02["fg"] = "#1f4f16"
        elif ibanCheck(iban_list, iban) == False and iban[0:2] in country_dict_inv:
            output_text_02.set("Der Ländercode " + str(iban[0:2]) + " entspricht: " + str(
                country_dict_inv.get(iban[0:2])) + ". Die Prüfung war nicht erfolgreich!")
            textbox_02["fg"] = "red"
        elif ibanCheck(iban_list, iban) == False and iban[0:2] not in country_dict_inv:
            output_text_02.set("Der Ländercode ist 'nicht' bekannt. Die Prüfung war nicht erfolgreich!")
            textbox_02["fg"] = "red"

    else:
        output_text_01.set("Deine Eingabe: " + str(iban))
        output_text_02.set("Dies entspricht nicht den IBAN-typischen Vorgaben!")
        textbox_02["fg"] = "#9c612f"

    # Eingabefeld leeren:
    userInput.set("")


# Erzeugung des Fensters
tkWindow = tk.Tk()
tkWindow.title("Programm zur Prüfung einer IBAN")

# Erzeugung der String-Variablen
userInput = tk.StringVar(tkWindow)
output_text_01 = tk.StringVar(tkWindow)
output_text_02 = tk.StringVar(tkWindow)

# InfoLabels:
infotext = "Programm zur Prüfung einer nationalen oder internationalen IBAN\n" \
"Anzahl der im Programm verfügbaren Länder: " + str(len(country_dict))
infoLabel_01 = tk.Label(tkWindow, text=infotext, font=("Arial, 10"))
infoLabel_01.grid(column=0, row=0, columnspan=4, padx="75", pady="5")

infoLabel_02 = tk.Label(tkWindow, text="IBAN: ", font=("Arial, 11"))
infoLabel_02.grid(column=0, row=1, padx="5", pady="5", sticky="e")

# Input- / Eingabefeld definieren
input_field = tk.Entry(tkWindow, textvariable=userInput, width=40, font=("Arial, 12"))
input_field.grid(column=1, row=1, columnspan=2, pady="5")
input_field.focus()

# Button definieren
submit = tk.Button(tkWindow, text="   prüfen   ", font=("Arial, 12"), command=checkInput)
submit.grid(column=3, row=1, padx="5", pady="5")

# Textfeld unter dem Button
textbox_01 = tk.Label(tkWindow, textvariable=output_text_01, font=("Arial, 11"))
textbox_01.grid(column=0, row=2, columnspan=4, padx="5", pady="3")

textbox_02 = tk.Label(tkWindow, textvariable=output_text_02, font=("Arial, 11"))
textbox_02.grid(column=0, row=3, columnspan=4, padx="5", pady="3", sticky="n")

# "Return"-Taste soll auch den Enter-Button auslösen / betätigen
tkWindow.bind("<Return>", checkInput)

# Aktivierung des Fensters
tkWindow.mainloop()

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

@LeoPold: Schaun wir mal:

Grunddatentypen haben nichts in Namen verloren. Den Typen ändert man gar nicht so selten mal während der Programmentwicklung und dann muss man überall im Programm die betroffenen Namen ändern, oder man hat falsche, irreführende Namen im Quelltext.

`country_dict` wird auch nicht wirklich verwendet ausser um daraus eine weiteres Wörterbuch zu erstellen bei dem Werte und Schlüssel vertauscht sind.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase). Als Konstante würde man den Namen für dieses Wörterbuch KOMPLETT_GROSS schreiben.

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

Die Kommentare bei den Funktionen wären eher Docstrings. Wobei im Kommentar/Docstring nicht erwähnt werden muss, dass es sich um eine Funktion handelt, denn das ist durch den Code sonnenklar.

Man nummeriert keine Namen. Da will man sich entweder passendere Namen ausdenken, oder gar keine Einzelnamen sondern eine Datenstrukur verwenden. Oft eine Liste.

Das zusammenstückeln von Zeichenketten und Werten mittels ``+`` und `str()` ist eher BASIC als Python. Dafür gibt es die `format()`-Methode auf Zeichenketten und f-Zeichenkettenliterale.

Für das Layout beitet sich `grid()` nicht wirklich an wenn man so viel mit `columnspan` arbeiten muss. Das `grid()` enthält auch eine im Grunde ungenutzte Spalte (Index 2).

Den Kommentar beim `bind()` würde ich wahr machen, denn momentan stimmt der nicht. Es wäre aber einfacher wenn die Eingabetaste tatsächlich einfach nur die Schaltfläche aktivieren würde.

Funktionen (und Methoden) bekommen alles was sie ausser Konstanten benötigen, als Argument(e) übergeben. Die Funktion, welche die IBAN prüft und das Ergebnis der Prüfung in der GUI anzeigt, braucht als die Ein- und Ausgabe-`StringVar`-Objekte als Argumente. Denn sonst sind die natürlich nicht bekannt. In diesem Programm geht das noch mit `functools.partial()`. Für jede nicht-triviale GUI braucht man objektorienterte Programmierung (OOP). Also mindestens eine eigene Klasse.

In beiden Zweigen des ersten ``if``/``else`` in der Funktion wird als erstes der gleiche Text zusammengebaut und in die erste Ausgabezeile geschrieben. Das sollte nur *einmal* im Code stehen — *vor* diesem ``if``/``else``.

`iban_list` wird in der falschen Funktion erstellt. Die Liste wird dort nur an eine andere Funktion übergeben, *die* wiederum auch `iban` übergeben bekommt, also alles kennt um diese Liste selbst zu erstellen.

Die Prüfungsfunktion für die IBAN wird vier mal aufgerufen obwohl das jedes mal das gleiche Ergebnis liefert.

Man macht keine Vergleiche mit literalen Wahrheitswerten. Bei dem Vergleich kommt doch nur wieder ein Wahrheitswert bei heraus. Entweder der, den man sowieso schon hatte; dann kann man den auch gleich nehmen. Oder das Gegenteil davon; dafür gibt es ``not``.

Die weiteren Prüfungen im ``if``-Zweig bestehen aus zwei Teilen die beide jeweils den vorderen oder hinteren Teil des Ausgabetextes bestimmen, wobei diese Daten und Code dann jeweils zweimal wiederholt werden. Das einzige was tatsächlich von *beiden* Bedingungsteilen abhängig ist, ist die Farbe. Das könnte man in eine Tabelle in Form eines Wörterbuchs wo man die Farbe nachschlagen kann, umwandeln.

In der Funktion zum überprüfen der Prüfziffern wird sehr oft unnötig `str()` aufgerufen auf Werten die bereits Zeichenketten *sind*.

"0" + "0" ist "00". Immer.

`d` ist kein besonders guter Name. Auch wenn in „list comprehensions“ manchmal einbuchstabige Namen nicht ganz so schlimm sind, muss man a) rätseln was `d` wohl bedeuten mag, und fragt sich dann b) ob man mit `digit` wohl richtig liegt.

Die `encode()`-Funktion ist überflüssig, denn man könnte einfach `int()` benutzen und da die Basis 36 übergeben.

Das umstellen, zusammenfügen, und ausrechnen der Prüfsumme lässt sich mit „slicing“ und `str.join()` IMHO besser ausdrücken als das bis jetzt im Code der Fall ist. Als erstes mal würde ich mit dem Umstellen für die Summendarstellung mit der IBAN selbst anfangen, statt auf einer Liste mit Zeichenkettenbestandteilen der einzelnen Ziffern. Diesen Zwischenschritt (`iban_list`) braucht man dann nicht als Wert im Speicher, das kann ein Generatorausdruck bleiben der nicht an einen Namen gebunden wird.

Ein ``if``/``else`` das in Abhängigkeit von der Bedingung ein literales `True` oder `False` zurückgibt ist unnötig kompliziert. Das Ergebnis der Bedingung beim ``if`` ist doch bereits der Wert, der zurückgegeben werden soll.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from functools import partial

ISO_CODE_TO_COUNTRY_NAME = {
    "AD": "Andorra",
    "AL": "Albanien",
    "AZ": "Aserbaidschan",
    "BA": "Bosnien und Herzegowina",
    "BE": "Belgien",
    "BG": "Bulgarien",
    "BH": "Bahrain",
    "BR": "Brasilien",
    "CR": "Costa Rica",
    "DE": "Deutschland",
    "DK": "Dänemark",
    "DO": "Dominikanische Republik",
    "EE": "Estland",
    "FI": "Finnland",
    "FO": "Färöer Inseln",
    "FR": "Frankreich",
    "GB": "Grossbritannien",
    "GE": "Georgien",
    "GI": "Gibraltar",
    "GL": "Grönland",
    "GR": "Griechenland",
    "GT": "Guatemala",
    "IE": "Irland",
    "IQ": "Irak",
    "SV": "El Salvador",
    "VG": "Britische Jungferninseln",
}


def validate_check_digits(iban):
    """
    Überprüfe IBAN-Prüfziffer.
    """
    sum_ = int(
        "".join(
            str(int(character, 36)) for character in iban[4:] + iban[:2] + "00"
        )
    )
    return int(iban[2:4]) == 98 - sum_ % 97


def check_input(iban_input_var, output_vars, label, _event=None):
    """
    Prüfe eingegeben IBAN und gibt Ergebnis aus.
    """
    iban = iban_input_var.get().replace(" ", "").upper()
    output_vars[0].set(f"Deine Eingabe: {iban}")

    country_code = iban[0:2]
    if country_code.isalpha() and iban[2:4].isdigit():
        country_name = ISO_CODE_TO_COUNTRY_NAME.get(country_code)
        is_valid = validate_check_digits(iban)
        text = (
            f"Der Ländercode {country_code} entspricht: {country_name}."
            f" Die Prüfung war{'' if is_valid else ' nicht'} erfolgreich!"
        )
        color = {
            (False, False): "red",
            (False, True): "red",
            (True, False): "#1f4f16",
            (True, True): "green",
        }[is_valid, bool(country_name)]
    else:
        text = "Dies entspricht nicht den IBAN-typischen Vorgaben!"
        color = "#9c612f"

    output_vars[1].set(text)
    label["foreground"] = color
    iban_input_var.set("")


def main():
    window = tk.Tk()
    window.title("Programm zur Prüfung einer IBAN")

    iban_input_var = tk.StringVar()
    output_vars = [tk.StringVar() for _ in range(2)]

    tk.Label(
        window,
        text=(
            f"Programm zur Prüfung einer nationalen oder internationalen IBAN\n"
            f"Anzahl der im Programm verfügbaren Länder:"
            f" {len(ISO_CODE_TO_COUNTRY_NAME)}"
        ),
        font=("Arial, 10"),
    ).pack(side=tk.TOP, padx=75, pady=5)

    frame = tk.Frame(window)

    tk.Label(frame, text="IBAN: ", font=("Arial, 11")).pack(
        side=tk.LEFT, padx=5, pady=5
    )

    iban_entry = tk.Entry(
        frame, textvariable=iban_input_var, width=40, font=("Arial, 12")
    )
    iban_entry.pack(side=tk.LEFT, pady=5)
    iban_entry.focus()

    check_button = tk.Button(frame, text="   prüfen   ", font=("Arial, 12"))
    check_button.pack(side=tk.LEFT, padx=5, pady=5)

    frame.pack()

    for output_var in output_vars:
        last_label = tk.Label(
            window, textvariable=output_var, font=("Arial, 11")
        )
        last_label.pack(side=tk.TOP, padx=5, pady=3)

    check_button["command"] = partial(
        check_input, iban_input_var, output_vars, last_label
    )
    window.bind("<Return>", lambda _event: check_button.invoke())

    window.mainloop()


if __name__ == "__main__":
    main()
Was nicht so schön gelöst ist, ist das löschen des Eingabefeldes. Zum einen natürlich wenn man eine fehlerhafte IBAN hat, denn wenn man sich vertippt hat, muss man die noch mal komplett neu eingeben, statt sie nur zu korrigieren. Aber auch wenn die Prüfung erfolgreich war, könnte es praktisch sein die als korrekt bewertete IBAN aus dem Eingabefeld kopieren zu können, statt sie dann an anderer Stelle noch einmal eingeben zu müssen, wieder mit der Gefahr, dass man sich vertippt.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
LeoPold
User
Beiträge: 7
Registriert: Freitag 5. März 2021, 12:52

Guten Morgen __blackjack__,

wow, es ist für mich immer wieder faszinierend antworten von euch Profis durchzulesen.
Da frage ich mich jedes Mal, ob ich es jemals überhaupt annähend hinbekomme guten Code zu schreiben, geschweige denn Programme zu schreiben die Probleme lösen und nicht anders herum.

Das Programm habe ich ursprünglich als Übung ohne tkinter geschrieben und mir gedacht es wäre eine schöne und nicht zu schwierige Übung das nun mit einer GUI umzusetzen. Aber wie man sieht ist es dann doch nicht ganz so einfach.
Die Kommentare im Programm sind eher für mich gedacht gewesen, damit ich es als Anfänger besser nachvollziehen kann. Bei den Sachen die mir schon etwas klarer und nicht mehr ganz so neu sind lasse ich sie weg.
Ich werde mich mit deinem Code intensiv auseinandersetzen um ihn auch zu verstehen, da sind einige neue Dinge drin die ich noch nicht kenne bzw. verstehe.

Vielen Dank für deine hilfreiche und ausführliche Antwort.

VG LeoPold
LeoPold
User
Beiträge: 7
Registriert: Freitag 5. März 2021, 12:52

Hallo __blackjack__,

könntest du mir bitte kurz erklären was ".invoke()" macht?
Irgendwie kann ich dazu nichts finden.
Vielen Dank.

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

Das ruft die als `command` hinterlegte Rückruffunktion auf. Sofern die Schaltfläche nicht deaktiviert ist:

Code: Alles auswählen

In [31]: import tkinter as tk                                                   

In [32]: tk.Button.invoke?                                                      
Signature: tk.Button.invoke(self)
Docstring:
Invoke the command associated with the button.

The return value is the return value from the command,
or an empty string if there is no command associated with
the button. This command is ignored if the button's state
is disabled.
File:      /usr/lib/python3.6/tkinter/__init__.py
Type:      function
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Antworten