Rückgabewerte

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Feedback1000
User
Beiträge: 88
Registriert: Dienstag 20. September 2022, 21:21

Hallo.

Die Aufgabe ist es eine Tool zu entwickeln, mit dem man Winkeleinheiten umrechnen kann.

Zwar habe ich ein UI basteln können (und das für meine Begriffe einigermaßen robust) und die Berechnung für die ersten drei habe ich auch fertig, aber ich bekomme es nicht hin, wie ich nun die berechneten wert1 und wert2 in die jeweils anderen Felder schreiben kann.

Ferner: ich musste einen Standartwert setzen, da mir bei einem leeren Eingabefeld mein Tool um die Ohren fliegt.

Was noch fehlt: Dass nur bestimmte Zeichen bei der Eingabe zulässig sind (also Ziffern und der Punkt).

Könntet ihr mir Hinweise geben, wie meine nächsten Schritte sind?

VG
Fabian

Code: Alles auswählen

import tkinter as tk
import math

class Anwendung(tk.Frame):
    RHO_RAD2GON = math.pi / 200
    RHO_RAD2DEG = math.pi / 180
    RHO_GON2DEG = 9 / 10

    def berechnungsmethode(self, einheit, eingangswert):
        if einheit == 'deg':
            wert1 = self.deg2gon(eingangswert)
            wert2 = self.deg2rad(eingangswert)
        elif einheit == 'gon':
            wert1 = self.gon2deg(eingangswert)
            wert2 = self.gon2rad(eingangswert)
        elif einheit == 'rad':
            wert1 = self.rad2deg(eingangswert)
            wert2 = self.rad2gon(eingangswert)
        print(f'Einheit: {einheit}, Wert1: {wert1}, Wert2: {wert2}')

    def gon2deg(self, gon):
        return gon * self.RHO_GON2DEG

    def gon2rad(self, gon):
        return gon * self.RHO_RAD2GON

    def deg2rad(self, deg):
        return deg * self.RHO_RAD2DEG

    def deg2gon(self, deg):
        return deg / self.RHO_GON2DEG

    def rad2gon(self, rad):
        return rad / self.RHO_RAD2GON

    def rad2deg(self, rad):
        return rad / self.RHO_RAD2DEG

    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.grid()
        self.erzeuge_elemente()

    def erzeuge_elemente(self):
        einheiten = [
            ['deg', 'Altgrad:', '°'],
            ['gon', 'Neugrad:', 'gon'],
            ['rad', 'Radiant:', '', ],
        ]

        for i, einheit in enumerate(einheiten):
            einheit_text = einheit[0]
            label_text = einheit[1]
            einheit_masseinheit = einheit[2]

            tk.Label(self, text=label_text).grid(row=i + 1, column=0)

            eingabe_var = tk.StringVar(value='0')
            eingabe = tk.Entry(self, textvariable=eingabe_var)
            eingabe.grid(row=i + 1, column=1)
            tk.Label(self, text=einheit_masseinheit).grid(row=i + 1, column=2)

            button_berechnung = tk.Button(self, text='Berechnen!', command=lambda einheit=einheit_text,
                                                                             eingang=eingabe_var: self.berechnungsmethode(
                einheit, float(eingang.get())))
            button_berechnung.grid(row=i + 1, column=3)

root = tk.Tk()
root.title('Winkelumrechner')

fenster_breite = 600
fenster_hoehe = 200
bildschirm_breite = root.winfo_screenwidth()
bildschirm_hoehe = root.winfo_screenheight()
fensterposition_x = (bildschirm_breite - fenster_breite) // 2
fensterposition_y = (bildschirm_hoehe - fenster_hoehe) // 2
root.geometry(f'{fenster_breite}x{fenster_hoehe}+{fensterposition_x}+{fensterposition_y}')

anwendung = Anwendung(root)
anwendung.mainloop()
Sirius3
User
Beiträge: 18225
Registriert: Sonntag 21. Oktober 2012, 17:20

Statt die Umrechnung von jeder Einheit in jede andere explizit per Funktion zu schreiben, würde man einfach eine Umrechnung in eine Standardeinheit und wieder zurück implementieren. In Deinem Fall läßt sich das einfach per Faktor abbilden.
Die GUI sollte von der eigentlichen Berechnung getrennt sein, so dass man auch einfach getrennt testen kann. Die Umrechnung hat nichts mit einem Frame zu tun und kann aus der Klasse raus.
Ein Frame sollte sich nicht selbst im übergeordneten Fenster plazieren, das ist Aufgabe des Aufrufers.
Die __init__ ist so kurz, ein extra erzeuge_elemente ist unnötig.
lambda ist für Deinen Fall die falsche Syntax, benutze functools.partial.
Bei StringVar sollte man auch explizit einen Owner angeben. Da Du die später noch brauchst, mußt Du sie in einer Datenstruktur speichern.
Die fehlt die main-Funktion. Die Größe des Fensters ergibt sich aus dem Inhalt, man gibt ihn nicht explizit vor.

Zwischenstand:

Code: Alles auswählen

import tkinter as tk
import math
from functools import partial

EINHEITEN = {
    "Grad": ("°", 1),
    "Neugrad": ("gon", 9 / 10),
    "Radian": ("", math.pi / 180),
}

def umrechnen(name, eingangswert):
    wert_in_grad = eingangswert / EINHEITEN[name][1]
    werte = {
        name: wert_in_grad * faktor
        for name, (einheit, faktor) in EINHEITEN.items()
    }
    return werte


class Anwendung(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.value_variables = {}
        for row, (name, (einheit, _)) in enumerate(EINHEITEN.items(), 1):
            tk.Label(self, text=name).grid(row=row, column=0)
            variable = tk.StringVar(self, value='0')
            self.value_variables[name] = variable
            eingabe = tk.Entry(self, textvariable=variable)
            eingabe.grid(row=row, column=1)
            tk.Label(self, text=einheit).grid(row=row, column=2)
            button_berechnung = tk.Button(self,
                text='Berechnen!', command=partial(self.berechnen, name))
            button_berechnung.grid(row=row, column=3)

    def berechnen(self, name):
        wert = float(self.value_variables[name].get())
        print(umrechnen(name, wert))


def main():
    root = tk.Tk()
    root.title('Winkelumrechner')

    anwendung = Anwendung(root)
    anwendung.grid()
    root.mainloop()

if __name__ == "__main__":
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13931
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Feedback1000: Die Fenstergrösse ergibt sich aus dem Fensterinhalt, die gibt man nicht vor. Da passiert es leicht, dass man zu viel Platz um den tatsächlichen Inhalt verschenkt, oder gar zu wenig Platz bereit stellt als benötigt wird um alles anzuzeigen, so dass im schlechtesten Fall das ganze unbenutzbar wird.

Auch die Fensterposition gibt man nicht vor. Man ist ja nicht alleine auf dem System und die Fensterverwaltung ist in einer deutlich besseren Position sinnvoll zu entscheiden wo ein neues Fenster Platz hat.

Da sind ”Methoden” in der Klasse, die eigentlich gar keine Methoden sind, weil das einfach nur Funktionen sind, die das Objekt gar nicht benötigen. Das ist hier ja nicht Java wo man alles zwangsweise in eine Klasse stopfen muss.

``self.master = master`` ist sinnfrei, das macht die `__init__()` der Basisklasse bereits.

``self.grid()`` ist falsch. Widgets layouten sich nicht selbst. Das macht keines der vorhandenen Widgets. Das nimmt dem Erzeuger von dem Objekt die Freiheit das zu layouten wie er möchte.

`erzeuge_elemente()` sollte keine Extra-Methode sein. Eine `__init__()` die im Grunde nichts macht ausser eine Methode aufzurufen die dann den Job der `__init__()`-Methode macht, ist sinnfrei.

Das Aufteilen der Bestandteile von den `einheiten` würde man direkt bei der Zuweisung in der Schleife machen statt da drei einzelne Zuweisungen und magische Indexzugriffe zu verwenden.

Statt immer ``i + 1`` zu schreiben, könnte man `enumerate()` auch gleich von 1 starten lassen. *Aaaaber* Spalten und Zeilen im Grid starten mit 0 und nicht mit 1.

`StringVar` für eine Zahleneingabe ist nicht so passend.

Dieser „``lambda`` mit Default-Wert“-Hack ist der Grund warum es `functools.partial()` gibt.

Bei einem ``if``/``elif`` ohne ``else`` sollte man immer mal kurz überlegen ob man da nicht doch ein sinnvolles ``else`` schreiben kann. Hier Beispielsweise eine Ausnahme auslösen, statt implizit in eine andere Ausnahme zu laufen weil Namen verwendet werden die gar nicht definiert wurden.

Zwischenstand (ungetestet):

Code: Alles auswählen

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

RHO_RAD2GON = math.pi / 200
RHO_RAD2DEG = math.pi / 180
RHO_GON2DEG = 9 / 10


def gon2deg(gon):
    return gon * RHO_GON2DEG


def gon2rad(gon):
    return gon * RHO_RAD2GON


def deg2rad(deg):
    return deg * RHO_RAD2DEG


def deg2gon(deg):
    return deg / RHO_GON2DEG


def rad2gon(rad):
    return rad / RHO_RAD2GON


def rad2deg(rad):
    return rad / RHO_RAD2DEG


class Anwendung(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        for i, (einheit_text, label_text, masseinheit) in enumerate(
            [
                ("deg", "Altgrad:", "°"),
                ("gon", "Neugrad:", "gon"),
                ("rad", "Radiant:", ""),
            ]
        ):
            eingabe_var = tk.DoubleVar(value=0)
            tk.Label(self, text=label_text).grid(row=i, column=0)
            tk.Entry(self, textvariable=eingabe_var).grid(row=i, column=1)
            tk.Label(self, text=masseinheit).grid(row=i, column=2)
            tk.Button(
                self,
                text="Berechnen!",
                command=partial(self.berechne, einheit_text, eingabe_var),
            ).grid(row=i, column=3)

    def berechne(self, einheit, eingabe_var):
        eingangswert = eingabe_var.get()
        if einheit == "deg":
            wert1 = deg2gon(eingangswert)
            wert2 = deg2rad(eingangswert)
        elif einheit == "gon":
            wert1 = gon2deg(eingangswert)
            wert2 = gon2rad(eingangswert)
        elif einheit == "rad":
            wert1 = rad2deg(eingangswert)
            wert2 = rad2gon(eingangswert)
        else:
            raise ValueError(f"unknown unit {einheit!r}")

        print(f"Einheit: {einheit}, Wert1: {wert1}, Wert2: {wert2}")


def main():
    root = tk.Tk()
    root.title("Winkelumrechner")

    anwendung = Anwendung(root)
    anwendung.grid()

    root.mainloop()


if __name__ == "__main__":
    main()
Wenn man jetzt die umgerechneten Werte in den entsprechenden Eingabefeldern ausgeben will, muss man sich die entsprechenden `DoubleVar`-Objekte merken und zwar so, dass man geziehlt darauf zugreifen kann. Zum Beispiel in einem Wörterbuch, dass die Einheit auf das `Variable`-Objekt abbildet.

Ich würde auch die Umrechnungsfunktionen in ein Wörterbuch stecken. Die Funktionsnamen deuten ja schon darauf hin das hier auch eine Zuordnung besteht zwischen Quellmasseinheit zu Zielmasseinheit. Das ist momentan hart im Code in den Namen, so dass man da für jede Umrechnung immer wieder die fast gleichen Codezeilen schreiben muss. Das sollte eher eine Datenstruktur sein über die man mit einer Schleife geht.

Aus dem Namen und dem Einheitszeichen würde ich `namedtuple` machen und das dreibuchstabige Kürzel ganz weglassen. Man kann die Tupelwerte als Schlüssel und Vergleichswerte verwenden.

Noch einen Tick besser wäre es eine Klasse zu schreiben mit diesen Informationen und zwei Methoden: Umrechnung in eine Basiseinheit, und Umrechnung von der Basiseinheit in die Einheit, die von dem jeweiligen Objekt repräsentiert wird. Dann braucht man keine Umrechnungstablle von jeder Einheit in jede andere als Datenstruktur oder gar hart in Code gegossen.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Antworten