Passwort Generator GUI

Fragen zu Tkinter.
Antworten
Luca
User
Beiträge: 5
Registriert: Donnerstag 6. Juni 2019, 21:14

Moin,
Ich befasse mich erst seit kurzem mit Python und dies ist mein erstes richtiges kleines Programm,
daher wollte ich mal fragen ob es "sinnvoll" geschrieben ist oder ob vielleicht jemand einen Verbesserungsvorschlag hat.

Code: Alles auswählen

from tkinter import * #Ich weiß Sternchen sind bei so großen Modulen unnötig, ich werde es demnächst ändern.
import string
from random import choice

letters = string.ascii_letters+string.digits+"?!"

root = Tk(className=" Passwort Generator")

def gen():
    output.delete(1.0, END)
    a = int(anzahl.get())
    l = int(laenge.get())
    if l > 50:
        output.insert(END, "Das Passwort darf maximal 50 Zeichen lang sein!")
    else:
        for a in range(a):
            pw = ""
            for b in range(l):
                x = choice(letters)
                pw = pw+x
            output.insert(END, pw+"\n")



#text = Label(root, text="Passwort Generator")
#text.grid(row=0, column=0, sticky=W)
text1 = Label(root, text="Anzahl der Passwörter: ")
text1.grid(row=1, column=0, sticky=W)
anzahl = Entry(root)
anzahl.grid(row=2, column=0, sticky=W)
text2 = Label(root, text="Länge der Passwörter: ")
text2.grid(row=3, column=0, sticky=W)
laenge = Entry(root)
laenge.grid(row=4, column=0, sticky=W)
button = Button(root, text="generieren", command=gen)
button.grid(row=5, sticky=W)
output = Text(root, height=10, width=50)
output.grid(row=6, sticky=W)

root.mainloop()
Ich bedanke mich im Voraus :D
Benutzeravatar
__blackjack__
User
Beiträge: 13099
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Luca: Den Sternchenimport hast Du ja schon selbst angemerkt.

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

Namen von Konstanten schreibt man KOMPLETT_GROSS.

Wenn man das Hauptprogramm in eine Funktion steckt, fällt auf das `gen()` auf Sachen zugreift die keine Konstanten sind aber auch nicht als Argument übergeben werden. Funktionen und Methoden sollten aber alles was sie ausser Konstanten benötigen als Argument(e) übergeben bekommen. Da braucht man dann entweder Closures oder objektoerientierte Programmierung.

Namen sollten dem Leser verraten was der Wert dahinter bedeutet. Die sollten also sorgfältig gewählt werden und nicht nur aus einem Buchstaben oder kryptischen Abkürzungen bestehen, und auch nicht nummeriert sein.

`a` und `l` sollten `anzahl` und `laenge` heissen. Und das was jetzt bereits `anzahl` und `laenge` heisst, sind gar nicht diese Werte sondern Eingabeelemente für diese Werte.

Das `a` dann noch einmal für etwas anderes verwendet wird in der gleichen Funktion ist verwirrend. Wobei der Wert davon dann ja gar nicht verwendet wird. Für solche Fälle ist es Konvention `_` als Namen zu verwenden.

Man muss nicht jedes Zwischenergebnis an einen Namen binden. `x` bringt da nicht wirklich etwas.

Zeichenketten durch immer wieder ”addieren” eines weiteren Teils aufzubauen ist ineffizient weil Zeichenketten nicht veränderbar sind. Idiomatischerweise sammelt man Teile in einer Liste und setzt die dann am Ende mit `join()` zusammen. Beziehungsweise kann man statt einer Liste hier auch einen Generatorausdruck verwenden.

Code und Daten sollte man nicht wiederholen. Das macht Änderungen nur unnötig aufwändig und fehleranfällig. Die 50 steht beispielsweise einmal im Vergleich und einmal in der Fehlerausgabe. Wenn man diese grenze mal ändern möchte, muss man das an zwei Stellen anpassen. Wenn man den Wert als Konstante definiert, muss man ihn nur an dieser einen Stelle ändern.

Das ist nicht wirklich ein `grid()`-Layout wenn man nur eine Spalte verwendet.

Das `className` nicht der richtige Weg ist um den Fenstertitel zu setzen hätte man vielleicht schon an dem Hack erkennen können den man da machen muss damit der erste Buchstabe nicht als Kleinbuchstabe angezeigt wird.

Code: Alles auswählen

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

LETTERS = string.ascii_letters + string.digits + '?!'
MAX_PASSWORD_LENGTH = 50


def generate_passwords(output, anzahl_entry, laenge_entry):
    output.delete(1.0, tk.END)
    anzahl = int(anzahl_entry.get())
    laenge = int(laenge_entry.get())
    if laenge > MAX_PASSWORD_LENGTH:
        output.insert(
            tk.END,
            f'Das Passwort darf maximal {MAX_PASSWORD_LENGTH} Zeichen lang'
            f' sein!'
        )
    else:
        for _ in range(anzahl):
            output.insert(
                tk.END, ''.join(choice(LETTERS) for _ in range(laenge)) + '\n'
            )


def main():
    root = tk.Tk()
    root.title('Passwort-Generator')
    tk.Label(root, text='Anzahl der Passwörter: ').pack(anchor=tk.W)
    anzahl_entry = tk.Entry(root)
    anzahl_entry.pack(anchor=tk.W)
    tk.Label(root, text='Länge der Passwörter: ').pack(anchor=tk.W)
    laenge_entry = tk.Entry(root)
    laenge_entry.pack(anchor=tk.W)
    button = tk.Button(root, text='generieren')
    button.pack(anchor=tk.W)
    output = tk.Text(root, height=10, width=50)
    output.pack(anchor=tk.W)
    
    button['command'] = partial(
        generate_passwords, output, anzahl_entry, laenge_entry
    )
    root.mainloop()


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Luca
User
Beiträge: 5
Registriert: Donnerstag 6. Juni 2019, 21:14

Hey @__blackjack__ ,
erstmal vielen Dank für dein Feedback und das du dir die mühe gemacht hast den Code nochmal zu überarbeiten,
ich werde zukünftig versuchen alle deine Punkte zu beachten.

Allerdings habe ich nochmal eine Frage, bisher habe ich Python nur per "Learning by Doing" und lesen gelernt, allerdings erschließt sich mir der Sinn der Klassen nicht,
ich habe schon viel über Objektorientierung und Klassen gelesen und auch ein ähnlichen Code wie meiner nur in einer Klasse gesehen, aber den einzigen unterschied den es für mich gibt ist, das es mit einer Klasse komplizierter aussieht.

Ich hoffe das du mir vielleicht dabei nochmal helfen kannst.
Benutzeravatar
__blackjack__
User
Beiträge: 13099
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Luca: Es geht halt nicht ohne Klassen. Hier habe ich das noch mit `functools.partial()` umgangen, letztlich ein Closure, also das was man in funktionalen Programmiersprachen verwendet um Klassen/OOP zu implementieren, aber das ist ja auch eine sehr triviale GUI und man muss da nur drei Widgets übergeben. Wenn das mehr wird, dann muss man viel mehr dauernd herum reichen wenn man das nicht mit einer Klasse zusammenfasst. Und skalare Werte die von Callbacks verändert werden sollen, muss man noch mal in ein Objekt verpacken (tkinter.StringVar, tkinter.IntVar, …) damit man den Wert auch tatsächlich ändern kann. Und man kann nur Typen verwenden für die es so einen Wrapper gibt. Das wird auf Dauer dann komplizierter und unübersichtlicher als wenn man einfach eine Klasse schreiben würde.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Luca
User
Beiträge: 5
Registriert: Donnerstag 6. Juni 2019, 21:14

@__blackjack__ ,
vielen Dank, ich werde mir Klassen dann jetzt nochmal genau angucken.
Luca
User
Beiträge: 5
Registriert: Donnerstag 6. Juni 2019, 21:14

Hey @__blackjack__ ,
ich habe jetzt eine neue GUI anwendungen geschrieben, und den Hauptteil in eine main() Funktion gepackt:

Code: Alles auswählen

import tkinter as tk

def use_lizenz():
    print("Test")
    lizenz = lizenz_entry.get()
    print(lizenz)
    if lizenz == "irgend-eine-Zeichenkette":
        print("Jetzt passiert irgendetwas")
    else:
        print("Ungültig")

def main():
    root = tk.Tk()
    root.title("Lizenz")
    login_text = tk.Label(root, text="Bitte den Lizenz Key eingeben").grid(row=0, column=0, sticky=tk.W)
    lizenz_entry = tk.Entry(root)
    lizenz_entry.grid(row=1, column=0, sticky=tk.W)
    bestaetigen = tk.Button(root, text="bestätigen", command=use_lizenz)
    bestaetigen.grid(row=1, column=1)
    root.mainloop()

if __name__ == "__main__":
    main()
Allerdings habe ich jetzt das Problem das ich der use_lizenz funktion die entry_get variable nicht mehr geben kann, denn wenn ich z.B. den Command so abänder:

Code: Alles auswählen

bestaetigen = tk.Button(root, text="bestätigen", command=use_lizenz(lizenz_entry)
dann wird die funktion diekt nachdem starten des Programmes ausgeführt sprich ohne das ich den Button drücken muss.

Ich habe das ganze auch schon mit einer Klasse versucht, das funktionier aber leider auch nicht:

Code: Alles auswählen

import tkinter as tk

class App:
    def use_lizenz(self):
        print("Test")
        lizenz = self.lizenz_entry.get()
        print(lizenz)
        if lizenz == "irgend-eine-Zeichenkette":
            print("Jetzt passiert etwas")
        else:
            print("Ungültig")

    #def main(self):
    root = tk.Tk()
    root.title("Lizenz")
    login_text = tk.Label(root, text="Bitte den Lizenz Key eingeben").grid(row=0, column=0, sticky=tk.W)
    lizenz_entry = tk.Entry(root)
    lizenz_entry.grid(row=1, column=0, sticky=tk.W)
    bestaetigen = tk.Button(root, text="bestätigen", command=use_lizenz)
    bestaetigen.grid(row=1, column=1)
    root.mainloop()

#if __name__ == "__main__":
    #App.main()
TypeError: use_lizenz() missing 1 required positional argument: 'self'

Ich hoffe du kannst mir nochmal helfen.
Sirius3
User
Beiträge: 17745
Registriert: Sonntag 21. Oktober 2012, 17:20

›command=use_lizenz(lizenz_entry)‹ ruft die Funktion auf und gibt den Rückgabewert (None) als Command an den Button. Statt aufzurufen, willst Du aber nur Parameter an die Funktion binden, und das macht man mit ›itertools.partial‹, wird hier im Forum bei jedem zweiten Post zu GUIs beschrieben.

Für Klassen solltest Du erst einmal die Grundkonzepte in Python lernen, bevor Du hier anfängst, GUs zusammenzuraten.
Luca
User
Beiträge: 5
Registriert: Donnerstag 6. Juni 2019, 21:14

Vielen Dank @Sirius3 ,
habe mich in den Python Docs über partial informiert und damit klappt es jetzt auch, werde dann jetzt noch einmal Klassen genau durchgehen.
Antworten