Dropdown menü deaktivieren

Fragen zu Tkinter.
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Marvin75854 hat geschrieben: Dienstag 24. Juli 2018, 09:03 Die Abkürzungen die ich benutze machen hier schon Sinn, zumindest für mich.
Vielleicht verstehst Du heute noch, was die Abkürzungen bedeuten, in drei Wochen, z.B. nach dem Urlaub, sieht das aber ganz anders aus.

Nach Deiner Beschreibung hast Du die klassische Aufteilung in Geschäftslogik und GUI: Du schreibst von Berechnungen, die Werte bekommen. Woher die Werte kommen, ist für die Berechnung egal. Die Berechnung liefert ein Ergebnis. Was mit dem Ergebnis gemacht wird, ist für die Berechnung egal, ob das nun in eine Datei geschrieben wird, oder in einem Fenster dargestellt wird.

Die Module, die eine Berechnung durchführen sind also unabhängig von einer GUI, sollten so auch entwickelt werden, damit sie auch ohne GUI testbar sind.

Die GUI (falls sie denn überhaupt nötig ist), sammel also nur ein paar Werte aus Eingabefeldern auf, ruft damit die Berechnung auf, nimmt das Ergebnis und stellt es dar.


Wenn Du nur eine Ansammlung von Funktionen hast, dann hast Du keine Klassen. Was ist denn der gemeinsame Zustand der vier Unterklassen und was die gemeinsamen Methoden?
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Marvin75854: Zu den Klassen kann man nicht viel sagen ohne sie zu kennen. Aber wenn Du die vier Klassen sogar von einer Basisklasse abgeleitet hast, dann *kann* man das doch gar nicht nur als Funktionen sehen, es sei denn auch die Vererbung macht eigentlich auch keinen Sinn. Bei dem gezeigten Code waren ja lauter globale Variablen, für die gibt es bei Verwendung von Klassen keinen Grund/keine Ausrede mehr.

Zusätzlich zu dem was Sirius3 bereits zu den Abkürzungen gesagt hat, widersprichst Du Dir ein bisschen selbst, denn selbst wenn die Abkürzungen für Dich Sinn machen, schreibst Du ja auch das eventuell jemand von dem Unternehmen in diesen Quelltext schauen wird oder zumindets könnte. Und der muss dann rätseln was die Abkürzungen bedeuten. Bachelor und Masterarbeiten bauen auch nicht selten auf vorhandenen Arbeiten auf. Kann also gut sein, das nach Dir jemand kommt der diesen Code wiederverwenden, anpassen, und erweitern muss.

Man gewinnt auch nicht wirklich etwas durch Abkürzungen. Es ist ja nicht so, dass Buchstaben knapp wären. :-) Ausschreiben muss man es auch nur einmal, danach taucht das bei jedem vernünftigen Editor in der Autovervollständigung auf.

Kommentare habe ich in meinem Programmen eher wenige. Wofür Module, Funktionen, Klassen, Methoden da sind, steht in Docstrings. Wenn Du in den Kommentaren Überschriften drin hast, dann sieht das für mich entweder nach Docstrings aus, die als Kommentare gesetzt sind, oder nach Stellen an denen man über eine Funktion oder ein Modul nachdenken könnte, wo so ein Kommentar als Docstring geeignet wäre.

Die Notwendigkeit für solche Kommentare mit Überschriften um sich zurecht zu finden, könnte eventuell auch an den ganzen Variablen auf Modulebene liegen, also das der Code selbst keine oder sehr wenig Struktur hat.

Zum starten von externen Programmen ist das `subprocess`-Modul da.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Marvin75854

Ok. Das mit den Abkürzungen sehe ich ein. Muss mir da nochmal was einfallen lassen. Das sind teilweise nämlich nicht nur einfache Abkürzungen, sondern sogar mehrere Abkürzungen aneinander gereit. Ausgeschrieben wären das in einigen Fällen 30-40+ Zeichen. Werde mal gucken wie ich das möglichst übersichtlich und eindeutig hinkriege.

Sirius3 hat geschrieben: Dienstag 24. Juli 2018, 09:24 Nach Deiner Beschreibung hast Du die klassische Aufteilung in Geschäftslogik und GUI: Du schreibst von Berechnungen, die Werte bekommen. Woher die Werte kommen, ist für die Berechnung egal. Die Berechnung liefert ein Ergebnis. Was mit dem Ergebnis gemacht wird, ist für die Berechnung egal, ob das nun in eine Datei geschrieben wird, oder in einem Fenster dargestellt wird.

Die Module, die eine Berechnung durchführen sind also unabhängig von einer GUI, sollten so auch entwickelt werden, damit sie auch ohne GUI testbar sind.

Die GUI (falls sie denn überhaupt nötig ist), sammel also nur ein paar Werte aus Eingabefeldern auf, ruft damit die Berechnung auf, nimmt das Ergebnis und stellt es dar.
OK ja das macht sinn. Das habe ich verstanden. Nötig ist die GUI auf jeden Fall, da schon sehr viel eingegeben werden muss. Ist auch so in der Aufgabenstellung gefordert.

Sirius3 hat geschrieben: Dienstag 24. Juli 2018, 09:24 Wenn Du nur eine Ansammlung von Funktionen hast, dann hast Du keine Klassen. Was ist denn der gemeinsame Zustand der vier Unterklassen und was die gemeinsamen Methoden?
__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 10:07 @Marvin75854: Zu den Klassen kann man nicht viel sagen ohne sie zu kennen. Aber wenn Du die vier Klassen sogar von einer Basisklasse abgeleitet hast, dann *kann* man das doch gar nicht nur als Funktionen sehen, es sei denn auch die Vererbung macht eigentlich auch keinen Sinn. Bei dem gezeigten Code waren ja lauter globale Variablen, für die gibt es bei Verwendung von Klassen keinen Grund/keine Ausrede mehr.
Also in der Basisklasse habe ich 3 Funktionen. Eine um die Ergebnisse in einem bestimmten Format in eine Datei zu schreiben, eine für die Ausgabe auf der GUI und eine die etwas kleines berechnet. Und in den Tochterklassen habe ich zusätzlich in jeder Tocherklasse noch eine Funktion für eine spezifische Berechnung, deren Ergebnisse dann in der Basisklasse ausgegeben werden. Die Klassen werden dann mit einigen Variablen aufgerufen die auf der GUI eingeben werden.

__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 10:07 Kommentare habe ich in meinem Programmen eher wenige. Wofür Module, Funktionen, Klassen, Methoden da sind, steht in Docstrings. Wenn Du in den Kommentaren Überschriften drin hast, dann sieht das für mich entweder nach Docstrings aus, die als Kommentare gesetzt sind, oder nach Stellen an denen man über eine Funktion oder ein Modul nachdenken könnte, wo so ein Kommentar als Docstring geeignet wäre.
Sowohl als auch. Das meiste sind Docstrings die als Kommentare gesetzt sind. Das kann ich ja relativ schnell ändern, wenn es so üblicher ist. Habe aber auch noch ein paar "Platzhalter" bzw. Notizen wo ich zu einem späteren Zeitpunkt noch was einfügen muss. Die werden später ja sowieso gelöscht.

__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 10:07 Die Notwendigkeit für solche Kommentare mit Überschriften um sich zurecht zu finden, könnte eventuell auch an den ganzen Variablen auf Modulebene liegen, also das der Code selbst keine oder sehr wenig Struktur hat.
Naja wie gesagt. Es sind inzwischen fast 3000 Zeilen. Einige davon sind eben die Überschriften, aber auch ohne wäre das schon relativ viel. Ich kenne die Variablen und weiß was passiert, wenn ich die Codeabschnitte sehe. So ist es dann aber doch wesentlich übersichtlicher. Und ja mein Code hat absolut keine Struktur. Bin ja gerade schon dabei daran zu arbeiten. Denke, wenn ich das Programm auf mehrere Module aufteile und generell noch überarbeite, sieht das schon wesentlich besser aus. Ist nur nicht so einfach als Anfänger von anfang an alles sauber aufzubauen. Hatte da noch überhaupt keinen Überblick wie ich das Programm aufbauen soll und hab mich einfach über jede neue funktionierende Funktion gefreut.

__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 10:07 Zum starten von externen Programmen ist das `subprocess`-Modul da.
Werde ich mir mal angucken. Danke
Marvin75854

__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 10:07 @Marvin75854: Zu den Klassen kann man nicht viel sagen ohne sie zu kennen. Aber wenn Du die vier Klassen sogar von einer Basisklasse abgeleitet hast, dann *kann* man das doch gar nicht nur als Funktionen sehen, es sei denn auch die Vererbung macht eigentlich auch keinen Sinn. Bei dem gezeigten Code waren ja lauter globale Variablen, für die gibt es bei Verwendung von Klassen keinen Grund/keine Ausrede mehr.
Aber habe ich nicht sowieso trotzdem noch globale Variablen? Wenn ich einen Wert auf der GUI eingebe und den in mehreren Funktionen verwenden will? Wenn ich das richtig verstehe, habe ich innerhalb einer Klasse lokale Variablen, die eben nur in der Klasse verwendet werden können. Wenn ich jetzt aber einen Wert auf der GUI eingebe, will ich doch, dass der Wert gobal ist, damit ich ihn überall mit .get() aufrufen kann oder habe ich da einen Denkfehler?
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Marvin75854: Zu lang sollten Namen auch nicht werden. Ich hoffe da einfach mal das die so lang sind weil Du Namenspräfixe als ”Namensräume” verwendest. Was man eigentlich nicht machen muss weil Python ja Objekte hat, die Namensräume darstellen.

Deine Klassen klingen immer noch nicht so wirklich überzeugend. Weil Du beispielsweise von Funktionen sprichst und nicht von Methoden. Irgendetwas muss diese Funktionen doch mit dem Objekt verbinden das durch die Klasse modelliert wird. Damit es eben nicht einfach nur Funktionen sind. Wird denn das `self`-Argument für irgend etwas anderes verwendet als andere Funktionen auf dem Objekt aufzurufen? Und haben die Unterklassen wirklich nur eine Methode? Verwendet die `self` sinnvoll? Falls nicht wären das ja eigentlich auch nur drei Funktionen von denen man der Basisklasse eine als Argument hätte mitgeben können.

Wenn man Kommentare die eigentlich Docstring-Material enthalten leicht zu Docstrings machen kann, dann sollte man das machen. Nur noch mal zur Sicherheit: Eine literale Zeichenkette ist nur an bestimmten Stellen ein Docstring. Also normalerweise nur am Modulanfang und direkt nach ``class`` und ``def``. Einige Werkzeuge, wie Sphinx, behandeln Zeichenketten direkt *unter* Zuweisungen auch als Docstring. Irgendwo mitten im Code sind es keine Docstrings.

Docstrings bekommt man in der Python-Shell bei `help()` angezeigt, oder auch in IPython und Jupyter-Notebooks, und IDEs können die in der Regel auch (auf Wunsch) als Tooltips o.ä. anzeigen. Und man kann die mit der `autodoc`-Erweiterung mit Sphinx aus dem Quelltext direkt in die generierte Dokumentation übernehmen.

Von wo willst Du denn überall auf die `get()`-Methode von Eingabeelementen zugreifen? Es ist ja üblicherweise so, dass man eine Schaltfläche drückt oder irgendwie anders eine Berechnung anstösst. Und die Behandlung dafür sammelt dann alle Werte ein und ruft damit dann irgendwas von der Programmlogik auf.

Eventuell kann es sein das man innerhalb der GUI noch einmal Unterteilungen in eigene Widgetklassen hat, aber da braucht man ja auch keine globalen Werte, denn Widgets die solche Widgets enthalten, kennen/merken sich diese enthaltenen Widgets ja in der Klasse die sie beschreibt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Marvin75854

__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 14:33 @Marvin75854: Zu lang sollten Namen auch nicht werden. Ich hoffe da einfach mal das die so lang sind weil Du Namenspräfixe als ”Namensräume” verwendest. Was man eigentlich nicht machen muss weil Python ja Objekte hat, die Namensräume darstellen.
Namensräume sind doch beispielsweise innerhalb einer Klasse. Also die Namen die ich den Variablen innerhalb einer Klasse gebe, haben auf das restliche Programm keinen Einfluss. Also ich kann den Variablen innerhalb der verschiedenen Klassen immer die gleichen Namen geben, obwohl sie unterschiedliche Werte haben. Bei globalen Variablen geht das aber ja nicht. Ich habe z.B. jetzt je 22 Eingabefelder für X und Y - Koordinaten. Also 44 Eingabefelder. Die muss ich ja irgendwie benennen. Also bspw. HP_X_1 für H-Punkt_X-Koordinate_Nummer1. Die GUI habe ich jetzt einfach so runtergeschrieben. Wahrscheinlich wäre es besser, wenn ich die GUI auch in einer Klasse definiere und dann in der main Funktion erstelle? Aber dann habe ich ja das selbe Problem. Oder sollte ich die GUI auf mehrere Klassen aufteilen?
__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 14:33 Deine Klassen klingen immer noch nicht so wirklich überzeugend. Weil Du beispielsweise von Funktionen sprichst und nicht von Methoden. Irgendetwas muss diese Funktionen doch mit dem Objekt verbinden das durch die Klasse modelliert wird. Damit es eben nicht einfach nur Funktionen sind. Wird denn das `self`-Argument für irgend etwas anderes verwendet als andere Funktionen auf dem Objekt aufzurufen? Und haben die Unterklassen wirklich nur eine Methode? Verwendet die `self` sinnvoll? Falls nicht wären das ja eigentlich auch nur drei Funktionen von denen man der Basisklasse eine als Argument hätte mitgeben können.
Richtig Methoden. Ich kenne ehrlich gesagt den Unterschied nicht genau. Ich dachte Methoden wären einfach nur Funktionen einer Klasse.
Also meine Klassen sehen so aus:
class Dummy(object):
def COG_ausgabe(self, i, SR):
if i == 1:
VM_Label = Label (SR, text=round(self.HX_neu,2)).grid(row=20, column=17)
......

def __init__(self, HX, HZ, TW,y,w):
self.HX = float(HX)
self.HZ = float(HZ)
self.TW = float(TW)
self.y = float(y)
self.w = float(w)

def COG_nach_Crash(self):
self.y_neu = (90 - self.w) * ((2*math.pi)/360)
.....

def write(self,i, L):
f = open(GUI.filename,"a")
.........




class EuroSID_2_RE(Dummy):
def calc(self):
dTW = self.TW - 19
.....
pass
Innerhalb einer If-Funktion und an anderen Stellen erstelle ich dann ein Objekt einer bestimmten Klasse und rufe einige Methoden auf.
def COG_1SR_Export(L,D,a,b,c,d,i,y,w):

if a == 1:

if L == "Barriere - FMVSS 214 / CMVSS 214" or L == "Barriere - UR R95" or ...........
try:
....
x = EuroSID_2_RE(HX,HZ,d,y,W)
x.calc()
x.COG_nach_Crash()
x.write(i, L)
....
Und diese Funktion wiederum wird in einer anderen Funktion mehrfach mit unterschiedlichen Koordinaten, die eben auf der GUI eingegeben werden, durch einen Button aufgerufen. Die Namen der Variablen die der Funktion oder den Klassen übergeben werden, habe ich hier noch nicht geändert. Ich probiere viel rum und tipper da einfach immer irgendwas rein.
Der Code ist wahrscheinlich ein noch größerer Pfusch, als sowieso schon erwartet. Aber ich hab halt wirklich keine Ahnung was ich hier eigentlich tue und solange es funktioniert, mache ich einfach immer weiter...
Dumme frage noch: Wie kann ich denn Teile des Codes einrücken? Leerzeichen werden ja nicht übernommen.
__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 14:33 Wenn man Kommentare die eigentlich Docstring-Material enthalten leicht zu Docstrings machen kann, dann sollte man das machen. Nur noch mal zur Sicherheit: Eine literale Zeichenkette ist nur an bestimmten Stellen ein Docstring. Also normalerweise nur am Modulanfang und direkt nach ``class`` und ``def``. Einige Werkzeuge, wie Sphinx, behandeln Zeichenketten direkt *unter* Zuweisungen auch als Docstring. Irgendwo mitten im Code sind es keine Docstrings.

Docstrings bekommt man in der Python-Shell bei `help()` angezeigt, oder auch in IPython und Jupyter-Notebooks, und IDEs können die in der Regel auch (auf Wunsch) als Tooltips o.ä. anzeigen. Und man kann die mit der `autodoc`-Erweiterung mit Sphinx aus dem Quelltext direkt in die generierte Dokumentation übernehmen.
Dann sind das keine Docstings. Habe das Wort vorhin das erste mal gelesen und einfach bei google geguckt was das ist. Habe die Erklärung wohl falsch verstanden. Ich habe mir einfach überall sowas eingefügt:
##################################################
#
#
#
#
# COG Berechnung Funktion
#
#
#
#
##################################################
Wird wahrscheinlich kein Programmierer so machen. Ich finds aber ganz übersichtlich.
__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 14:33 Von wo willst Du denn überall auf die `get()`-Methode von Eingabeelementen zugreifen? Es ist ja üblicherweise so, dass man eine Schaltfläche drückt oder irgendwie anders eine Berechnung anstösst. Und die Behandlung dafür sammelt dann alle Werte ein und ruft damit dann irgendwas von der Programmlogik auf.
Es ist halt teilweise so, dass ein Eingabefeld für mehrere Funktionen verwendet wird und durch verschiedene Buttons aufgerufen wird, also an verschiedenen Stellen des Programms genutzt wird.
__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 14:33 Eventuell kann es sein das man innerhalb der GUI noch einmal Unterteilungen in eigene Widgetklassen hat, aber da braucht man ja auch keine globalen Werte, denn Widgets die solche Widgets enthalten, kennen/merken sich diese enthaltenen Widgets ja in der Klasse die sie beschreibt.
Ich habe die GUI wie gesagt ja nicht in einer Klasse definiert, sondern einfach alles so runtergeschrieben. Ich verstehe teilweise immer noch nicht was genau mir die Klassen für einen Vorteil bringen.Das kommt hoffentlich noch, wenn ich das ein bisschen mehr anwende.

Mein Plan ist da jetzt erstmal das Programm so wie es ist fertig zu schreiben und wenn alles funktioniert, kann ich die einzelnen Funktionen und GUI Elemente ja auch noch in Klassen definieren und auf verschiedene Module aufteilen. Also nochmal ein neues Programm schreiben, nur eben in Klassen und verschiedenen Modulen. Müsste dann ja zum großteil nur copy & paste mit kleinen Anpassungen sein oder ist das das falsche vorgehen?

Ich habe halt diese ganzen Hello World und Taschenrechner Programme irgendwie alle übersprungen und bin direkt mit meinem etwas komplexeren Programm eingestiegen ohne das Programmieren überhaupt richtig verstanden zu haben. Denke ich habe schon viel gelernt und es funktioniert ja auch, sieht aber jetzt halt alles andere als professionell aus.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Marvin75854: Also wenn Du `HP_X_1` bis `HP_X_22` hast, dann sind das eigentlich gar keine 22 Namen sondern nur einer für die Liste wo die Werte alle drin stehen. Und falls die 22 X- und Y-Werte jeweils zusammengehören, dann sollten die auch in jeweils einem Objekt zusammengefasst werden. Aus `HP_X_1` würde dann so etwas wie `h_point_enries[0].get_point().x`. Nur das man das so wahrscheinlich dann gar nicht braucht, sondern statt der Liste ein Widget schreibt das die ganzen Eingabeelemenete zusammenfasst und eine `get_points()`-Methode bietet die eine Liste von `Point`-Objekten liefert, die man dann an die Programmlogik für Berechnungen übergeben kann.

`Dummy` als Basisklassenname ist schon mal ein Warnzeichen. Wenn Dir da kein besserer Name einfällt, stecken da ziemlich sicher Sachen drin die nicht zusammengehören.

In der `__init__()` sollte man *alle* Attribute des Objekts erstellen. Wenn man Attribute in anderen Methoden erstellt, wird es sehr schnell unübersichtlich welche Attribute es insgesamt gibt, und welche es zu welchem Zeitpunkt gibt. `_neu` an Attribute anzuhängen riecht auch komisch. Wenn die Methoden nur solche Attribute erstellen, statt vorhandene zu verändern, dann sind es wahrscheinlich wirklich Funktionen die eigentlich einen Rückgabewert haben sollten, den sie momentan als neue Attribute auf dem Objekt setzen. Falls das auch für die `calc()`-Methoden in den Unterklassen zutrifft, wären das wahrscheinlich auch einfach nur Funktionen. Das sieht im Moment so ein bisschen danach aus, als wäre die Vererbung einfach nur eine ziemlich undurchsichtige Art um eine Funktion so ein `Dummy`-Objekt als Argument zu übergeben.


Was ist das für ein `i`-Argument? Wird da Anhand einer nichtssagenden, magischen Zahl entschieden was die Methode(n) tatsächlich machen? Sind das deutlich unterschiedliche Sachen? Dann sollten es auch verschiedene Methoden sein. Eine Funktion/Methode sollte in der Regel genau *eine* Sache machen.

Kann `COG_ausgabe()` im Laufe des Programms mehr als einmal mit dem Wert 1 für `i` aufgerufen werden? Dann ist es falsch dort ein `Label` zu erzeugen, denn da wird dann jedes mal ein neues Label erzeugt und immer mehr an der Zelle im Grid übereinander gestapelt. Ein eventuell vorhandenes Label verschwindet da nämlich nicht automatisch. Die Grid-Koordinaten 20,17 klingen auch nicht schön.

`VM_Label` ist überflüssig, denn da wird der Rückgabewert vom `grid()`-Aufruf zugewiesen und der ist immer `None`.

Wo ich das ``(90 - self.w) * ((2*math.pi)/360)`` gerade sehe — es gibt eine Umrechnungsfunktion im `math`-Modul: ``math.radians(90 - self.w)``.

Wie viel von Deinen Kommentaren erklären eigenlich solche Argumentnamen: ``def COG_1SR_Export(L,D,a,b,c,d,i,y,w):``? Das sind auch recht viele Argumente, da würde ich wahrscheinlich auch schauen ob sich davon welche sinnvoll zu einem Objekt zusammenfassen lassen. Und auch hier riecht das ``if a == 1:`` wieder nach einer magischen Zahl die bestimmt was tatsächlich passieren soll.

Falls die Texte die da mit `L` verglichen werden, wie "Barriere - FMVSS 214 / CMVSS 214", mehrfach im Quelltext stehen: das ist eine potentielle Fehlerquelle, weil man immer aufpassen muss das die alle exakt gleich geschrieben sind, und wenn man etwas verändert, das alle Kopien exakt gleich verändert werden. Für so etwas definiert man üblicherweise Konstanten. Das `enum`-Modul könnte hier eventuell auch interessant sein wenn sich Gruppen von Konstanten sinnvoll zusammenfassen lassen. Zudem kann man dann auch noch mehr Information zu einer Konstante hinterlegen und sogar Methoden dafür definieren, beziehungsweise sogar Objekte als Werte hinterlegen.

Um den Quelltext hier mit Einrückung anzeigen zu lassen muss er nicht in [ quote ]-Tags sondern in [ Python ]- oder [ code ]-Tags stehen. Im vollständigen Editor gibt da auch Schaltflächen für.

Wenn dieser fette Kommentar über einer Funktion steht die `calculate_cog():` heisst, dann wäre er überflüssig. :-) Wenn er über einer Gruppe von Funktionen steht die COGs (?) berechnen, dann würde ich die im Modul-Docstring unter einer Überschrift zusammengefasst aufzählen oder in ein eigenes Modul auslagern.

Ein Eingabefeld für verschiedene Funktionen die von verschiedenen Schaltflächen ausgelöst werden zu verwenden ist ja kein Problem. Dafür braucht man immer noch keine globalen Variablen, denn die Eingabefelder und die Methoden die von den Schaltflächen aktiviert werden, sind ja im selben Objekt. Oder zumindest über Attribute dieses Objekts erreichbar.

Klassen bringen mehr Übersicht und Struktur. Weil nicht alle Variablen in einem globalen Namensraum zusammengeworfen werden, mit Namen, die aus einer Aneinanderreihung von kryptischen Abkürzungen bestehen. Und man hat weniger Einzelattribute und Argumente bei Aufrufen wenn Informationen die zusammegehören in Objekten zusammengefasst werden.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Marvin75854

__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 18:38 @Marvin75854: Also wenn Du `HP_X_1` bis `HP_X_22` hast, dann sind das eigentlich gar keine 22 Namen sondern nur einer für die Liste wo die Werte alle drin stehen. Und falls die 22 X- und Y-Werte jeweils zusammengehören, dann sollten die auch in jeweils einem Objekt zusammengefasst werden. Aus `HP_X_1` würde dann so etwas wie `h_point_enries[0].get_point().x`. Nur das man das so wahrscheinlich dann gar nicht braucht, sondern statt der Liste ein Widget schreibt das die ganzen Eingabeelemenete zusammenfasst und eine `get_points()`-Methode bietet die eine Liste von `Point`-Objekten liefert, die man dann an die Programmlogik für Berechnungen übergeben kann.
__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 18:38 Was ist das für ein `i`-Argument? Wird da Anhand einer nichtssagenden, magischen Zahl entschieden was die Methode(n) tatsächlich machen? Sind das deutlich unterschiedliche Sachen? Dann sollten es auch verschiedene Methoden sein. Eine Funktion/Methode sollte in der Regel genau *eine* Sache machen.
__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 18:38 Kann `COG_ausgabe()` im Laufe des Programms mehr als einmal mit dem Wert 1 für `i` aufgerufen werden? Dann ist es falsch dort ein `Label` zu erzeugen, denn da wird dann jedes mal ein neues Label erzeugt und immer mehr an der Zelle im Grid übereinander gestapelt. Ein eventuell vorhandenes Label verschwindet da nämlich nicht automatisch. Die Grid-Koordinaten 20,17 klingen auch nicht schön.
__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 18:38 `Dummy` als Basisklassenname ist schon mal ein Warnzeichen. Wenn Dir da kein besserer Name einfällt, stecken da ziemlich sicher Sachen drin die nicht zusammengehören.
Es handelt sich hierbei um tatsächliche Dummys die im Crashtest verwendet werden. Deswegen die Klasse Dummy. Die Unterklassen sind dann bestimmte Arten von Dummys. Auf meiner GUI kann man auf dem ersten Reiter 6 Dummys auswählen, und zusammen mit ein paar anderen Informationen die man eingibt werden dann ein paar Berechnungen durchgeführt. i ist also die Stelle an der ein Dummy ausgewählt wurde. Also wenn ich von den 6 Dummys nur 3 auswähle wird die Funktion für i = 1-3 ausgeführt. Das heißt ich wähle mit einem Dropdownmenü einen Dummy aus und je nachdem welche Version das ist, wird eine unterschiedliche Berechnung durchgeführt, bestimmte Informationen auf der GUI ausgegeben und als text datei rausgeschrieben. Die Grid-Koordinaten sind dabei immer unterschiedlich, sodass die Labels untereinander erstellt werden. Sieht auf der GUI dann am Ende wie eine Tabelle aus. Die Funktion erstellt also immer nur labels auf der GUI und das i entscheidet an welcher Stelle und mit welchen Werten, also HX_i quasi.

Also eine Liste macht natürlich Sinn. Bzw. mehrere Listen, da nicht alle Koordinaten zusammengehören. Ist ein Widget nicht ein Textfeld o.ä. auf der GUI? Die Werte sollen ja auf der GUI eingegeben und dann nur für die Berechnung verwendet werden. Ich verstehe den Grundsätzlichen Aufbau irgendwie überhaupt nicht. Wenn ich jetzt ein Klasse schreibe für die GUI oder nur für die Eingabefelder? Muss ich dann nicht noch überall wo ich diese Werte der Eingabefelder verwenden will ein Objekt der Klasse erstellen? Oder erstelle ich dann in der main Funktion einmal ein Objekt der Klasse GUI bspw. und rufe dann die anderen Funktionen für die Berechnungen damit auf?
__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 18:38 In der `__init__()` sollte man *alle* Attribute des Objekts erstellen. Wenn man Attribute in anderen Methoden erstellt, wird es sehr schnell unübersichtlich welche Attribute es insgesamt gibt, und welche es zu welchem Zeitpunkt gibt. `_neu` an Attribute anzuhängen riecht auch komisch. Wenn die Methoden nur solche Attribute erstellen, statt vorhandene zu verändern, dann sind es wahrscheinlich wirklich Funktionen die eigentlich einen Rückgabewert haben sollten, den sie momentan als neue Attribute auf dem Objekt setzen. Falls das auch für die `calc()`-Methoden in den Unterklassen zutrifft, wären das wahrscheinlich auch einfach nur Funktionen. Das sieht im Moment so ein bisschen danach aus, als wäre die Vererbung einfach nur eine ziemlich undurchsichtige Art um eine Funktion so ein `Dummy`-Objekt als Argument zu übergeben.
Ich habe nicht alle Attribute hier definiert, das werde ich gleich ändern. Ja bei dem _neu sind wir wahrscheinlich wieder bei den Namen angekommen. Es wird z.b. ein HX Wert bei der Erstellung des Objektes einer Tochterklasse übergeben. Mit dem wird dann eine Berechnung durchgeführt und das Ergebnis HX_neu wird dann in der Basisklasse auf der GUI, bzw. in einer Textdatei ausgegeben.

__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 18:38 Wo ich das ``(90 - self.w) * ((2*math.pi)/360)`` gerade sehe — es gibt eine Umrechnungsfunktion im `math`-Modul: ``math.radians(90 - self.w)``.
Ah perfekt. Das ist praktisch.
__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 18:38 Wie viel von Deinen Kommentaren erklären eigenlich solche Argumentnamen: ``def COG_1SR_Export(L,D,a,b,c,d,i,y,w):``? Das sind auch recht viele Argumente, da würde ich wahrscheinlich auch schauen ob sich davon welche sinnvoll zu einem Objekt zusammenfassen lassen. Und auch hier riecht das ``if a == 1:`` wieder nach einer magischen Zahl die bestimmt was tatsächlich passieren soll.
__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 18:38 Falls die Texte die da mit `L` verglichen werden, wie "Barriere - FMVSS 214 / CMVSS 214", mehrfach im Quelltext stehen: das ist eine potentielle Fehlerquelle, weil man immer aufpassen muss das die alle exakt gleich geschrieben sind, und wenn man etwas verändert, das alle Kopien exakt gleich verändert werden. Für so etwas definiert man üblicherweise Konstanten. Das `enum`-Modul könnte hier eventuell auch interessant sein wenn sich Gruppen von Konstanten sinnvoll zusammenfassen lassen. Zudem kann man dann auch noch mehr Information zu einer Konstante hinterlegen und sogar Methoden dafür definieren, beziehungsweise sogar Objekte als Werte hinterlegen.
a ist hier, wie oben das i einfach die Nummer des Dummys der ausgewählt wurde. Also 6 Dropdown menüs untereinander, im 2. Dropdown menü wurde ein Dummy ausgewählt, also a = 2 == true. Und Innerhalb der if Funktion wird dann mit L == "Barriere - FMVSS 214 / CMVSS 214" getestet was genau gemacht werden soll. Barriere ist ein Lastfall in der Crashberechnung und hier auch eine Auswahlmöglichkeit in einem Dropdown menü und erstellt dann ein Objekt eines bestimmten Dummys mit bestimmten X und Y Coordinaten, die wiederrum schon viel frühere berechnet wurden, durch eine ganz andere Funktion, in einem ganz anderen Bereich der GUI. Es besteht also die Möglichkeit entweder einen Dummy + verschiedener Koordinaten und anderer Informationen oder einen Lastfall auszuwählen. Und je nachdem was ausgewählt wird, wird das entsprechende Objekt einer Dummyklasse erstellt und damit eben auch andere Berechnungen durchgeführt. Die Ausgaben die in der Basisklasse definiert wurden, sind immer die gleichen nur eben mit anderen Werten.

__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 18:38 Wenn dieser fette Kommentar über einer Funktion steht die `calculate_cog():` heisst, dann wäre er überflüssig. :-) Wenn er über einer Gruppe von Funktionen steht die COGs (?) berechnen, dann würde ich die im Modul-Docstring unter einer Überschrift zusammengefasst aufzählen oder in ein eigenes Modul auslagern.
Ja das ist definitiv überflüssig. Ging mir hier ja nur darum einfach eine optische Trennung zwischen den Funktionen zu haben. Einfach etwas das beim schnellen Durchscrollen sofort auffällt und man immer weiß wo im Programm man gerade ist.
__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 18:38 Ein Eingabefeld für verschiedene Funktionen die von verschiedenen Schaltflächen ausgelöst werden zu verwenden ist ja kein Problem. Dafür braucht man immer noch keine globalen Variablen, denn die Eingabefelder und die Methoden die von den Schaltflächen aktiviert werden, sind ja im selben Objekt. Oder zumindest über Attribute dieses Objekts erreichbar.
Es ist ja alles miteinander Verknüpft auf irgendeine Art und Weise. Ich kann sicher noch mehr Klassen verwenden, aber weiß gerade bei Elementen der GUI nicht wie ich das umsetzen soll. Nach meiner Vorstellung ist dann einfach alles was ich jetzt Global gemacht habe in einer Klasse. Dann wären die Variablen zwar nicht mehr global definiert, ich bräuchte die Namen so aber immer noch, weil ich dann ja immer noch die selbe Anzahl an Variablen habe, nur eben nicht mehr global, sondern innerhalb der Klasse. Wenn ich das jetzt unterteile und an die Werte will, dann muss ich ja jedes mal ein Objekt der Klasse erstellen oder nicht? Und, wenn ich jetzt Eingabefelder oder die GUI in einer Klasse erstelle, dann aber kein Objekt dieser Klasse erstelle, wird doch auch keine GUI angezeigt oder irre ich mich da?
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Marvin75854 hat geschrieben: Mittwoch 25. Juli 2018, 08:52 Es ist ja alles miteinander Verknüpft auf irgendeine Art und Weise.
Das will man beim Programmieren ja gerade dadurch vermeiden, indem man klare Schnittstellen definiert. Die Dummies wissen nichts von einer GUI, sondern bekommen nur einen festen Satz an Werten, die, wenn es zu viele sind, wieder in einer Klasse zusammenfassen kann. Dadurch ist nicht alles mit allem verknüpft. Das Ergebnis einer Rechnung wird dann wieder an die GUI zurückgegeben, wo es dargestellt (z.B. indem Labeltexte geändert werden) wird, oder über eine Funktion in eine Datei geschrieben wird. Eine übergeordnete Dummy-Klasse sehe ich dann nicht mehr, höchstens noch einen Dummy-Werte-Container, der an die jeweilige Berechnungsfunktion (wieder keine Klasse) übergeben wird.

Bei GUI-Klassen ist es tatsächlich so, dass meist nur eine Instanz davon erzeugt wird.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Marvin75854: Cool, ich glaube das ist der erste Code den ich sehe, bei dem ein ”ernst gemeintes” `Dummy` tatsächlich ein passender Name ist. Das ist echt selten. :-)

Das mit dem `i` sieht nach einer Aufteilung an einer falschen ”Achse” aus. Du hast da ja mindestens zwei Methoden die von diesem `i` abhängen und in beiden steht ein ``if i == ?:`` pro Dummy-Version. Wenn man jetzt also etwas an einer Dummy-Version ändert (hinzufügen/verändern/entfernen) muss man jede Methode mit diesem `i` ändern. Eine objektorientierte Aufteilung wäre ein Objekt pro Dummy-Version wo die Methoden kein `i` brauchen weil das Objekt schon die Entscheidung enthält/darstellt um welche Dummy-Version es sich handelt. Wie viele Klassen man dafür braucht, hängt davon ab wie unterschiedlich die Dummy-Versionen sind. Die beiden ”Extreme” wären eine Klasse wenn die Unterschiede der Dummy-Versionen leicht durch entsprechende Parametrisierung ausgedrückt werden können, oder eine das andere ”Extrem” eine Klasse pro Dummy-Version, plus eventuell eine Basisklasse wenn sich gemeinsamkeiten herausziehen lassen. Es kann auch etwas dazwischen sein.

Mein ”Problem” mit den Grid-Koordinaten ist, dass 17 und 20 ziemlich grosse Zahlen sind und ich da *ein* Grid vor dem inneren Auge habe in dem *alles* angeordnet ist. Also das grafische Äquivalent zu den ganzen Namen in einem Namensraum. Da braucht man ja eine extra Zeichnung um sich den Aufbau vorstellen zu können, und Änderungen an der GUI können schnell sehr aufwändig werden.

Ein Widget ist allgemein ein Anzeigeelement und konkret bei Tkinter auch die Basisklasse für alle Anzeigeelemente innerhalb von Fenstern (`Frame`, `Label`, `Entry`, `Button`, …).

Ich würde sowohl für die GUI als ganzes, als auch für eigene Eingabeelemente Klassen schreiben, soweit das Sinn macht. Ob und was da Sinn macht, hängt von den konkreten Zusammenhängen zwischen den Anzeige-/Eingabeelementen ab und wie oft bestimmte Kombinationen benötigt werden. Wie schon gesagt könnte es Sinn machen ein Widget zur Eingabe eines Punktes oder einer Liste von Punkten zu schreiben, wenn in der GUI mehr als ein Punkt oder eine Liste von Punkten eingegeben werden können, denn dann kann man dieses Widget mehrfach verwenden und spart Code und braucht das nur einmal Dokumentieren, beziehungsweise ist das durch passende Namen und die Zusammenfassung in einem Objekt schon etwas übersichtlicher. Letzeres könnte auch ein Grund sein auch bei nur einer Verwendung eine Klasse zu schreiben.

Im ersten Schritt könnte man in der Tat die ganzen globalen Variablen die GUI-Elemente betreffen in eine GUI-Klasse verfrachten. Dann wären formal zumindest schon mal keine globalen Variablen mehr im Modul. Und man sieht Stellen im Programm wo sich der Code auf die globalen Variablen bezogen hat und kann/muss das ändern. Andere Alternative wäre erst einmal alles auf Modulebene was da nicht hingehört in eine `main()`-Funktion zu verschieben. Hat im grossen und ganzen den gleichen Effekt und man sieht was man mindestens in eine Klasse stecken muss, damit es korrekt funktioniert. Was am Ende dann wahrscheinlich fast alles ist.

In beiden Fällen könnte man danach anfangen die GUI sinnvoll zu unterteilen.

Meistens lässt man das `Tk`-Objekt in der Hauptfunktion und leitet die Haupt-GUI-Klasse von `Frame` ab und kann sie dann in der Hauptfunktion erzeugen und zum Beispiel mit `pack()` im Fenster anordnen.

Wenn Du an die Werte willst, musst Du ein Objekt erstellen, aber das machst Du ja sowieso, denn Du willst die GUI ja anzeigen. Genau wie Du von den vorhandenen Widget-Klassen Exemplare erstellen musst, damit Du sie in die Anzeige einbauen und bei Eingabeelementen an die Eingaben kommen kannst.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Marvin75854

__blackjack__ hat geschrieben: Mittwoch 25. Juli 2018, 10:05 @Marvin75854: Cool, ich glaube das ist der erste Code den ich sehe, bei dem ein ”ernst gemeintes” `Dummy` tatsächlich ein passender Name ist. Das ist echt selten. :-)
Ja ist wirklich ein interessantes Projekt. Aber fordert mich schon sehr, muss ich sagen. Aber so soll es ja auch sein :D

__blackjack__ hat geschrieben: Mittwoch 25. Juli 2018, 10:05 Das mit dem `i` sieht nach einer Aufteilung an einer falschen ”Achse” aus. Du hast da ja mindestens zwei Methoden die von diesem `i` abhängen und in beiden steht ein ``if i == ?:`` pro Dummy-Version. Wenn man jetzt also etwas an einer Dummy-Version ändert (hinzufügen/verändern/entfernen) muss man jede Methode mit diesem `i` ändern. Eine objektorientierte Aufteilung wäre ein Objekt pro Dummy-Version wo die Methoden kein `i` brauchen weil das Objekt schon die Entscheidung enthält/darstellt um welche Dummy-Version es sich handelt. Wie viele Klassen man dafür braucht, hängt davon ab wie unterschiedlich die Dummy-Versionen sind. Die beiden ”Extreme” wären eine Klasse wenn die Unterschiede der Dummy-Versionen leicht durch entsprechende Parametrisierung ausgedrückt werden können, oder eine das andere ”Extrem” eine Klasse pro Dummy-Version, plus eventuell eine Basisklasse wenn sich gemeinsamkeiten herausziehen lassen. Es kann auch etwas dazwischen sein.
Also ich habe eine Funtkion geschrieben die nur daraus besteht 6 mal die gleiche Funktion aufzurufen, nur mit anderen Werten. Also z.b. HX_1, HX_2 ... und diese aufgerufene Funktion hat die if i abfrage um zusammen mit der if L abfrage zu ermitteln welcher Dummy/Lastfall angegeben wurde. Und dann habe ich noch einmal eine if i abfrage in einer Methode innerhalb der Dummyklasse für die Ausgabe auf der GUI. Ich hatte Anfangs versucht das mit einer for schleife zu realisieren. Das hat aber irgendwie nicht richtig funktioniert, deswegen habe ich da eben diese if abfrage eingebaut.
Wenn ich die Methoden in den Klassen änder, dann müsste das weiterhin funktionieren, wenn ich allerdings eine neue Methode benötige, dann muss ich einiges ändern.
Ob ich die verschiedenen Dummy-Versionen brauche, kann ich noch gar nicht zu 100% sagen, weil ich die Berechnungen noch nicht durchgeführt habe. Bisher ist da überall die selbe Rechnung als Platzhalter drin. Sie werden aber mit Sicherheit unterschiedlich sein.

__blackjack__ hat geschrieben: Mittwoch 25. Juli 2018, 10:05 Mein ”Problem” mit den Grid-Koordinaten ist, dass 17 und 20 ziemlich grosse Zahlen sind und ich da *ein* Grid vor dem inneren Auge habe in dem *alles* angeordnet ist. Also das grafische Äquivalent zu den ganzen Namen in einem Namensraum. Da braucht man ja eine extra Zeichnung um sich den Aufbau vorstellen zu können, und Änderungen an der GUI können schnell sehr aufwändig werden.
Es ist tatsächlich alles in einem Grid dargestellt. Ich habe gelesen, dass es noch eine andere Methode gibt um Elemente auf einer GUI zu platzieren, habe mich damit Anfangs aber nicht weiter beschäftigt, weil ich auch noch nicht genau wusste was ich alles auf der GUI darstellen muss. Ich habe einen kleinen Entwurf bekommen, musste aber jetzt schon einiges ergänzen. Also im Moment ordne ich die Elemente einfach irgendwo an und gucke am Ende wie genau ich alles platziere.
__blackjack__ hat geschrieben: Mittwoch 25. Juli 2018, 10:05 Ich würde sowohl für die GUI als ganzes, als auch für eigene Eingabeelemente Klassen schreiben, soweit das Sinn macht. Ob und was da Sinn macht, hängt von den konkreten Zusammenhängen zwischen den Anzeige-/Eingabeelementen ab und wie oft bestimmte Kombinationen benötigt werden. Wie schon gesagt könnte es Sinn machen ein Widget zur Eingabe eines Punktes oder einer Liste von Punkten zu schreiben, wenn in der GUI mehr als ein Punkt oder eine Liste von Punkten eingegeben werden können, denn dann kann man dieses Widget mehrfach verwenden und spart Code und braucht das nur einmal Dokumentieren, beziehungsweise ist das durch passende Namen und die Zusammenfassung in einem Objekt schon etwas übersichtlicher. Letzeres könnte auch ein Grund sein auch bei nur einer Verwendung eine Klasse zu schreiben.
Wenn ich das richtig verstehe, dann würde ich einmal eine Klasse mit einem Eingabefeld schreiben und von dieser Klasse dann verschiedene Objekte erstellen. Dann müsste ich dem Objekt ja quasi auch mitgeben wo es auf der GUI angezeigt werden soll oder nicht?
Also ich habe eine Klasse mit einem Eingabefeld. Und dann eine mit einer Methode zur Berechnung. Ich verstehe nicht ganz wie ich das dann am Ende zusammenbringen soll. Erstelle ich in der main Methode dann einfach lauter Objekte? Wie gebe ich denn überhaupt Zahlenwerte o.ä. aus einer Klasse zurück? Bisher habe ich ja alles entweder global oder schreibe die Informationen direkt in der Klasse oder Funktion in eine Textdatei ohne sie noch weiter zu übergeben.
__blackjack__ hat geschrieben: Mittwoch 25. Juli 2018, 10:05 Im ersten Schritt könnte man in der Tat die ganzen globalen Variablen die GUI-Elemente betreffen in eine GUI-Klasse verfrachten. Dann wären formal zumindest schon mal keine globalen Variablen mehr im Modul. Und man sieht Stellen im Programm wo sich der Code auf die globalen Variablen bezogen hat und kann/muss das ändern. Andere Alternative wäre erst einmal alles auf Modulebene was da nicht hingehört in eine `main()`-Funktion zu verschieben. Hat im grossen und ganzen den gleichen Effekt und man sieht was man mindestens in eine Klasse stecken muss, damit es korrekt funktioniert. Was am Ende dann wahrscheinlich fast alles ist.

In beiden Fällen könnte man danach anfangen die GUI sinnvoll zu unterteilen.

Meistens lässt man das `Tk`-Objekt in der Hauptfunktion und leitet die Haupt-GUI-Klasse von `Frame` ab und kann sie dann in der Hauptfunktion erzeugen und zum Beispiel mit `pack()` im Fenster anordnen.

Wenn Du an die Werte willst, musst Du ein Objekt erstellen, aber das machst Du ja sowieso, denn Du willst die GUI ja anzeigen. Genau wie Du von den vorhandenen Widget-Klassen Exemplare erstellen musst, damit Du sie in die Anzeige einbauen und bei Eingabeelementen an die Eingaben kommen kannst.

Ich verstehe das irgendwie überhaupt nicht. Habe jetzt ein bisschen rumprobiert und es funktioniert so gut wie gar nichts. Ich habe keine Ahnung wie ich Werte aus einer Klasse einer anderen Klasse übergeben kann usw. Wüsste auch nicht wie ich die GUI jetzt noch auf mehrere Klassen aufteilen sollte. Das würde eventuell mit abgeleiteten Klassen gehen? Bei meinem Beispiel funktioniert die GUI immerhin. Ich verstehe aber nicht wie ich dann die die Berechnungen noch mit einfüge. Ich habe jetzt auch das Gefühl, dass selbst wenn ich das jetzt so zum Laufen kriege der Code nicht unbedingt übersichtlicher wird dadurch, sondern einfach noch komplizierter. Liegt vielleicht daran, dass ich mich nicht damit auskenne, aber im Moment ist einfach alles von oben bis unten runtergeschrieben. Wenn ich am ende alles in etlichen Klassen definiere, steige ich doch gar nicht mehr durch.

Code: Alles auswählen

class GUI:

    def __init__(self):
        self.GUI = Tk() 
        self.GUI.title ("GUI")
        self.GUI.geometry("100x100")
        self.label = Label(self.GUI, text="Hallo").grid(row=1,column=1)
        self.HX = Entry(self.GUI).grid(row=1, column=2)
        self.GUI.mainloop()
        
        
        
class Berechnung():
    def __init__(self):
        pass
        
    def Berechnung1(self,HX):
        self.HXX = HX
        print(self.HXX) 
        
    def Berechnung2(self,HX):
        self.HXXX = HX
        print(self.HXXX)
        
        


def main():
    x = GUI() 
    y = Berechnung()
    y.Berechnung1(x)
    y.Berechnung2(x)

    

if __name__ == "__main__": 
    main()
Marvin75854

Sirius3 hat geschrieben: Mittwoch 25. Juli 2018, 09:02
Marvin75854 hat geschrieben: Mittwoch 25. Juli 2018, 08:52 Es ist ja alles miteinander Verknüpft auf irgendeine Art und Weise.
Das will man beim Programmieren ja gerade dadurch vermeiden, indem man klare Schnittstellen definiert. Die Dummies wissen nichts von einer GUI, sondern bekommen nur einen festen Satz an Werten, die, wenn es zu viele sind, wieder in einer Klasse zusammenfassen kann. Dadurch ist nicht alles mit allem verknüpft. Das Ergebnis einer Rechnung wird dann wieder an die GUI zurückgegeben, wo es dargestellt (z.B. indem Labeltexte geändert werden) wird, oder über eine Funktion in eine Datei geschrieben wird. Eine übergeordnete Dummy-Klasse sehe ich dann nicht mehr, höchstens noch einen Dummy-Werte-Container, der an die jeweilige Berechnungsfunktion (wieder keine Klasse) übergeben wird.

Bei GUI-Klassen ist es tatsächlich so, dass meist nur eine Instanz davon erzeugt wird.

Ich denke in der Theorie verstehe ich das einigermaßen, nur habe ich absolut keine Ahnung wie ich das umsetzten soll. Also für mich ist der Hauptzweck der übergeordneten Dummy Klasse die 3 Methoden darin. Also die Ausgabe auf der GUI, die Ausgabe in einer text datei und eine kleine Berechnung. Die 3 Methoden wären für alle Unterklassen ja die gleichen. Zusätzlich sollen noch an anderen Stellen des Programms, Dinge auf der GUI und in Textdatein ausgeben werden. Vermutlich könnte ich das irgendwie zusammenführen und mir damit die Dummy - Klasse sparen. Aber hier habe ich absolut keine Ansatz wie ich das machen soll.
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@Marvin75854: der gezeigte Code kann nicht funktionieren, weil GUI-Programme außerhalb des mainloops (außer der Initialisierung) nichts machen können.
In Deinem Beispiel kehrt GUI.__init__ quasi nie zurück (außer man beendet das Programm und dann sollte es auch keine Berechnungen mehr geben).
self.label und self.HX haben jeweils den Wert None, weil das der Rückgabewert von grid ist.

Die Berechnung-Klasse macht so keinen Sinn, da sie keinen Zustand hat.

Als Grundgerüst könnte das Programm so aussehen:

Code: Alles auswählen

import tkinter as tk

class Berechnung():
    def __init__(self, hx):
        self.hx = hx
        
    def run1(self):
        print("Run1", self.hx)
        
    def run2(self):
        print("Run2", self.hx)

class GUI(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("GUI")
        self.label = tk.Label(self, text="Hallo")
        self.label.grid(row=1,column=1)
        self.hx = tk.Entry(self)
        self.hx.grid(row=1, column=2)
        self.run_button = tk.Button(self, text="Run", command=self.run)
        self.run_button.grid(row=2, column=1)
        
    def run(self):
        berechnung = Berechnung(self.hx.get())
        berechnung.run1()
        berechnung.run2()

def main():
    gui = GUI() 
    gui.mainloop()

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

@Marvin75854: Also wie gesagt ist das mit den magischen Zahlen anhand denen entschieden wird was getan werden soll ziemlich unübersichtlich. Da würde ich mindestens mal `enum.Enum`-Objekte für verwenden, denen kann man einen beschreibenden Namen geben, den man dann im Code verwenden kann. Das kann auch helfen Fehler zu vermeiden oder zu finden, weil man bei Ausgaben dann keine nichtssagende Zahl mehr bekommt, und auch nicht aus versehen gleich Konstanten mit unterschiedlichen Bedeutungen aus versehen vergleichen kann. Und wenn man dann anhand von `Enum`-Werten unterschiedliches Verhalten über ``if``/``elif``-Kaskaden realisiert, könnte man an der Stelle schon mal schauen *wie* sich das Unterscheidet und ob man diese Unterscheidungen nicht als Daten ausdrücken kann, oder eventuell sogar als Datentypen mit Verhalten, also Methoden.

Grid als Layout ist ja okay, es ging mehr mehr darum dass das alles in *einem* Grid steckt. Wenn man da jetzt Änderungen vornehmen will, muss man sehr wahrscheinlich ziemlich viele `grid()`-Aufrufe anpassen.

Eine Klasse mit *einem* Eingabefeld ist wahrscheinlich nur in wenigen Fällen sinnvoll. Ein Widget in dem man beispielsweise einen Punkt eingeben kann der aus X- und Y-Koordinate besteht, hat ja schon mal mindestens zwei Eingabefelder, eins für jeden Bestandteil. Und dann noch zwei Label 'X' und 'Y', damit man weiss welches Eingabefeld für welche Koordinate gedacht ist. Und vielleicht noch ein Label das beschreibt was dieser Punkt bedeutet. Da sind dann schon fünf Widgets, plus ein `Frame` von dem man das ableiten kann, wo die Dinge drin stecken, und den man dann in der GUI platziert. Und wenn man das eine Zelle weiter rechts oder tiefer haben möchte, kann man diesen gesamten Verbund von fünf Widgets mit einem `grid()`-Aufruf wo anders hin setzen und muss nicht jedes Label und jedes Eingabefeld einzeln verschieben. Und man kann aussehalb der Klasse diesem Verbund einen Namen geben, statt mindestens den zweien die man sonst für die Eingabefelder benötigt um später an die Werte zu kommen.

In der `main()`-Funktion steht in der Regel nur so etwas, hier jetzt mal mit super-generischen Namen und unter der Annahme das die Programmlogik auch einen Zustand hat, und deshalb in einer Klasse zusammengefasst ist, was nicht zwingen der Fall sein muss, wenn man die Programmlogik auch zustandslos von der GUI aus verwenden kann:

Code: Alles auswählen

def main():
    root = tk.Tk()
    logic = ProgramLogic()
    frame = MainFrame(root, logic)
    frame.pack()
    root.mainloop()
In der `MainFrame.__init__()` wird die GUI in dem Frame aufgebaut. Das heisst `MainFrame` hat die Widgets die man später noch braucht, um Werte abzufragen oder Ausgaben/Anzeigen zu machen, als Attribute. Wobei das nicht direkt die Grundwidgets sein müssen, also das was aus dem Tkinter-Modul kommt, sondern eben auch selbst geschriebene Widgets sein können, die aus mehreren Teilen bestehen.

Um an Zahlenwerte aus solchen selbst geschriebenen Widgets zu kommen, bietet man den gleichen Weg wie die vorhandenen Einzelwidgets: Man schreibt Methoden die diese Werte abfragen, und gegebenenfalls auch prüfen und umwandeln. Man möchte zum Beispiel nicht erst in der Programmlogik Zeichenketten in Zahlen umwandeln wenn man Zahlen benötigt. Das sollte schon im GUI-Teil passieren. Der ist auch für eine Rückmeldung an den Benutzer zuständig. Wenn der beispielsweise bei einem Punkt nicht beide Koordinaten eingibt, oder in eines der Felder einfach 'Hallo' eintippt, dann sollten diese Werte gar nicht erst in die Berechnungsfunktion kommen, sondern schon im GUI-Code zu einer Fehlermeldung führen, damit der Benutzer das korrigieren kann. Wenn man nett ist, setzt man dann auch gleich den Fokus auf das Eingabefeld mit dem ungültigen Wert und wählt den Inhalt aus. :-)

Mal (völlig ungetestet) ein Beispiel für eine GUI die einen Start- und einen Endpunkt, und eine Anzahl von Schritten erfragt und dann eine Funktion damit aufruft:

Code: Alles auswählen

class MainFrame(tk.Frame):

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.start_point_entry = PointEntry(self, 'Start')
        self.start_point.grid(row=0, column=0)
        self.end_point_entry = PointEntry(self, 'End')
        self.end_point_entry.grid(row=0, column=1)
        
        frame = tk.Frame(self)
        tk.Label(frame, text='Steps:').pack(side=tk.LEFT)
        self.steps_entry = tk.Entry(frame, textvariable=self.steps_var)
        self.steps_entry.pack(side=tk.LEFT)
        frame.grid(row=1, column=0)
        
        tk.Button(self, text='Go', command=self.on_go).grid(row=1, column=1)
    
    def on_go(self):
        try:
            steps = int(self.steps_entry.get())
        except ValueError:
            # TODO Hier ein Fehlerfenster öffnen.
            self.steps_entry.set_focus()
            self.steps_entry.select_range(0, tk.END)
        else:
            do_something_with_two_points_and_a_number_of_steps(
              self.start_point_entry.get_point(),
              self.end_point_entry.get_point(),
              steps,
            )
Wobei wie oben schon beschrieben `PointEntry` selbst aus bis zu fünf weiteren Widgets besteht, die hier aber gar nicht mehr auftauchen müssen, weil nur die Schnittstelle nach aussen, also hier die Klasse selbst und eine `get_point()`-Methode von Interesse sind. Ohne diese Klasse gäbe es in der `MainFrame.__init__()` 8 bis 10 mehr GUI-Objekte.

Wieso solltest Du bei etlichen Klassen nicht mehr durchsteigen? Der Witz ist doch gerade das man Details in Klassen kapselt die man dann gar nicht mehr wissen muss um das Programm an sich zu verstehen, die jetzt alle global im Modul rumliegen und die in Rückruffunktionen alle auf einen Schlag stehen. Zum Beispiel brauchst Du die Interna von einem `PointEntry` gar nicht kennen um die gezeigte `MainFrame`-Klasse zu verstehen. Ohne das `PointEntry` wäre aber die `__init__()` und die `on_go()` deutlich umfangreicher. Und das bei nur zwei Punkten. Du hast ja was von 22 Punkten in der GUI geschrieben.

Die Übersicht kommt in der GUI, genau wie sonst auch beim Programmieren dadurch, dass man ein Problem in Teilprobleme zerlegt, und die wieder in Teilprobleme, bis man Teillösungen hat, die sich leicht schreiben und verstehen lassen und zu grösseren Teillösungen zusammensetzen. Solange bis man das Gesamtproblem gelöst hat. Und auf höheren Ebenen braucht man dann die ganzen Variablen und Details auf tieferen Ebenen nicht sehen/verstehen.

Dein Beispiel kann so nicht funktionieren, denn man müsste das `Berechnung`-Objekt ja der GUI übergeben, damit darauf zugreifen kann und Methoden darauf aufrufen kann.

Und die leere `__init__()` und das Einführen neuer Attribute in den Methoden, und dann auch noch jeweils andere, deutet sehr darauf hin das da Funktionen die gar nicht auf diese Weise zusammengehören, in einer Klasse stecken.

Ich hoffe es ist nicht zu verwirrend, aber man sollte nicht so viele Klassen schreiben. ;-) Wenn es eine Funktion ist, dann ist es eine Funktion. Es bringt keinen Vorteil die in eine Klasse zu zwängen. Was man vielleicht zu einem Objekt zusammenfassen kann sind Argumente die Du an Deine Funktion(en) in der Programmlogik übergibst und was die zurückgeben.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Marvin75854

Sirius3 hat geschrieben: Mittwoch 25. Juli 2018, 14:53 @Marvin75854: der gezeigte Code kann nicht funktionieren, weil GUI-Programme außerhalb des mainloops (außer der Initialisierung) nichts machen können.
In Deinem Beispiel kehrt GUI.__init__ quasi nie zurück (außer man beendet das Programm und dann sollte es auch keine Berechnungen mehr geben).
self.label und self.HX haben jeweils den Wert None, weil das der Rückgabewert von grid ist.
Ok das verstehe ich. Macht Sinn. Also die GUI an sich in der main Methode erzeugen und eben nur den Inhalt der GUI in einzelnen Klassen.

__blackjack__ hat geschrieben: Mittwoch 25. Juli 2018, 15:33 @Marvin75854: Also wie gesagt ist das mit den magischen Zahlen anhand denen entschieden wird was getan werden soll ziemlich unübersichtlich. Da würde ich mindestens mal `enum.Enum`-Objekte für verwenden, denen kann man einen beschreibenden Namen geben, den man dann im Code verwenden kann. Das kann auch helfen Fehler zu vermeiden oder zu finden, weil man bei Ausgaben dann keine nichtssagende Zahl mehr bekommt, und auch nicht aus versehen gleich Konstanten mit unterschiedlichen Bedeutungen aus versehen vergleichen kann. Und wenn man dann anhand von `Enum`-Werten unterschiedliches Verhalten über ``if``/``elif``-Kaskaden realisiert, könnte man an der Stelle schon mal schauen *wie* sich das Unterscheidet und ob man diese Unterscheidungen nicht als Daten ausdrücken kann, oder eventuell sogar als Datentypen mit Verhalten, also Methoden.
Das sind immer diese Funktionen von Python die ich einfach nicht kenne. Habe gerade mal gegooglet und das ist und denke das könnte sinnvoll sein. Würde es hier nicht sogar Sinn machen das ganze Global zu defininieren?
__blackjack__ hat geschrieben: Mittwoch 25. Juli 2018, 15:33 Grid als Layout ist ja okay, es ging mehr mehr darum dass das alles in *einem* Grid steckt. Wenn man da jetzt Änderungen vornehmen will, muss man sehr wahrscheinlich ziemlich viele `grid()`-Aufrufe anpassen.

Eine Klasse mit *einem* Eingabefeld ist wahrscheinlich nur in wenigen Fällen sinnvoll. Ein Widget in dem man beispielsweise einen Punkt eingeben kann der aus X- und Y-Koordinate besteht, hat ja schon mal mindestens zwei Eingabefelder, eins für jeden Bestandteil. Und dann noch zwei Label 'X' und 'Y', damit man weiss welches Eingabefeld für welche Koordinate gedacht ist. Und vielleicht noch ein Label das beschreibt was dieser Punkt bedeutet. Da sind dann schon fünf Widgets, plus ein `Frame` von dem man das ableiten kann, wo die Dinge drin stecken, und den man dann in der GUI platziert. Und wenn man das eine Zelle weiter rechts oder tiefer haben möchte, kann man diesen gesamten Verbund von fünf Widgets mit einem `grid()`-Aufruf wo anders hin setzen und muss nicht jedes Label und jedes Eingabefeld einzeln verschieben. Und man kann aussehalb der Klasse diesem Verbund einen Namen geben, statt mindestens den zweien die man sonst für die Eingabefelder benötigt um später an die Werte zu kommen.
Ja das stimmt. Auf die Aufgabe habe ich mich auch nicht unbedingt gefreut. So macht es das schon wesentlich einfacher muss ich sagen.


Also ich habe beide eure Beispiele nicht zum Laufen bekommen :D Waren irgendwelche Fehlermeldungen mit denen ich nichts anfangen konnte und die ich durch google auch nicht gelöst bekommen habe. Hat mir aber trotzdem gut geholfen. Ich habe nochmal ein etwas rumprobiert. Habe jetzt hier eine kleine GUI mit Klassen erstellt und die auf 2 Datein aufgeteilt. Allerdings funktioniert immer noch nicht alles wie geplant.

Code: Alles auswählen

#!/usr/bin/python
from __future__ import print_function
import subprocess
from Tkinter import *
import Tkinter, Tkconstants, tkFileDialog
import os
from tkMessageBox import *
import math
import sys
import time
import Tix as tx



import Modul_2



class MainFrame(Frame):

    def __init__(self, parent):
        Frame.__init__(self, parent)
        Label(self, text="X").grid(row=0,column=0)
        Label(self, text="Y").grid(row=0,column=1)
        self.HX = Entry(self).grid(row=1, column=0)
        self.HY = Entry(self).grid(row=1, column=1)
       
        Berechnung_button = Button(self, text="Berechnung", command=lambda:self.Zahlenwerte()).grid(row=2, column = 0)

             
    def Zahlenwerte(self):
        print(self.HX)
        print(self.HY)
    
    def get_Zahlen(self)
        return self.HX
        return self.HY
    
        
class Berechnung():
    def __init__(self, HX, HXX, HY, HYY):
        self.HX = HX
        self.HY = HY
        self.HXX = HXX
        self.HYY = HYY
    
    def calc():
        self.Ergebnis_X = self.HX + self.HXX
        self.Ergebnis_Y = self.HY + self.HYY
        print(self.Ergebnis_X)
        print(self.Ergebnis_Y)
        
        

def main():
    GUI = tx.Tk()
    
    swr=tx.ScrolledWindow(GUI)
    swr.pack(fill=tx.BOTH, expand=1)
    
    nb=tx.NoteBook(swr.window)
    nb.pack(fill=tx.BOTH, expand=1)

    nb.add("reiter"+str(1),label="1. Reiter")
    nb.add("reiter"+str(2),label="2. Reiter")
    
    frame = MainFrame(nb.reiter1)
    frame.grid(row=0, column=0)
    frame.Zahlenwerte
    
    frame_2 = Modul_2.Frame_2(nb.reiter1)
    frame_2.grid(row=1, column=0)
    
    frame_3 = Modul_2.Frame_3(nb.reiter2)
    frame_3.grid(row=1, column=0)
    
    frame_4 = MainFrame(nb.reiter2)
    frame_4.grid(row=0, column=0)
    
#    x = Berechnung()
#    x.calc()
    GUI.mainloop()


if __name__ == "__main__": 
    main()
Hier das importierte Modul "Modul_2"

Code: Alles auswählen

#!/usr/bin/python
from __future__ import print_function
import subprocess
from Tkinter import *
import Tkinter, Tkconstants, tkFileDialog
import os
from tkMessageBox import *
import math
import sys
import time
import Tix as tx

class Frame_2(Frame):
    def __init__(self, sibling):
        Frame.__init__(self, parent)
        Label(self, text="XX").grid(row=0,column=0)
        Label(self, text="YY").grid(row=0,column=1)
        self.HX = Entry(self).grid(row=1, column=0)
        self.HY = Entry(self).grid(row=1, column=1)  
        
        
class Frame_3(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)
        Label(self, text="X").grid(row=0,column=0)
        Label(self, text="Y").grid(row=0,column=1)
        self.HX = Entry(self).grid(row=1, column=0)
        self.HY = Entry(self).grid(row=1, column=1) 
Ich verstehe aber immer noch nicht wie ich da jetzt die Klasse Berechnung mit ihren Funktionen einbinde, bzw. wie ich die Werte aus den anderen Klassen übergeben kann. Habe da schon mit irgendwelchen Funktionen "get_Zahlen" rumexperimentiert aber entweder läuft das Programm nicht oder es passiert einfach gar nichts.

Was genau meinst du mit dem PointEntry? Wenn ich das verwende sagt er mir immer :"NameError: global name 'PointEntry' is not defined"

Mein Button gibt immer "None" aus. Woran kann das liegen? Müsste er die eingegebenen Werte innerhalb der Klasse nicht bekommen?

Jedes mal, wenn ich mein Programm ausführe wird automatisch eine Datei "Modul_2.pyc" erstellt. Woran liegt das und ist es möglich das abzustellen?
Sirius3 hat geschrieben: Mittwoch 25. Juli 2018, 14:53

Code: Alles auswählen

class GUI(tk.Tk):
    def __init__(self):
        super().__init__()
Was genau bewirkt das super().__init__ hier? Das hat bei mir ebenfalls nicht funktioniert.

"parent" habe ich hier auch das erste mal gesehen und das hatte bei mir gefühlt keinen Einfluss. Was genau bewirkt das oder wann brauche ich es?

Mein Plan ist meine drei Reiter auf 3 Module aufzuteilen. Und dann halt alles irgendwie in Klassen oder Funktionen zu definieren. Ich denke ich habe dann am Ende eine extrem lange main Funktion, aber das ist ja nicht weiter schlimm oder?
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@Marvin75854: ich bin nicht auf die Idee gekommen, dass Du noch Python2 benutzt. Da funktionieren dann einige Dinge doch anders.
Mein Code also nochmal in der Python2-Version:

Code: Alles auswählen

import Tkinter as tk

class Berechnung():
    def __init__(self, hx):
        self.hx = hx
        
    def run1(self):
        print("Run1", self.hx)
        
    def run2(self):
        print("Run2", self.hx)

class GUI(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.title("GUI")
        self.label = tk.Label(self, text="Hallo")
        self.label.grid(row=1,column=1)
        self.hx = tk.Entry(self)
        self.hx.grid(row=1, column=2)
        self.run_button = tk.Button(self, text="Run", command=self.run)
        self.run_button.grid(row=2, column=1)
        
    def run(self):
        berechnung = Berechnung(self.hx.get())
        berechnung.run1()
        berechnung.run2()

def main():
    gui = GUI() 
    gui.mainloop()

if __name__ == "__main__": 
    main()

Modul_2 ist ein schlechter Name für ein Modul. Wenn Dir kein besserer einfällt, also einen der beschreibt, was der Nutzen dieses Moduls ist, dann ist das wahscheinlich keine gute Aufteilung in Module. Warum self.HX und self.HY die Werte None haben, habe ich schon weiter oben erklärt. Attribute sollten generell klein geschrieben werden, komplett GROSSBUCHSTABEN ist für Konstanten reserviert. Berechnung_button ist auch None, zudem eine lokale Variable, die sonst nicht benutzt wird, kann also weg. Lambda ist bei command= auch unnötig, weil man direkt die Funktion als Wert übergeben kann.

Vieles was importiert wird, wird nicht benutzt. Sternchenimporte vermeiden, da man nicht kontrollieren kann, was da alles in den Modulnamensraum importiert wird. Wie man's richtig macht, zeigt mein Beispiel.

get_Zahlen hat zwei Returns, von denen aber nur das erste ausgeführt wird. Auf die Entry-Objekte an sich, will man aber sowieso nicht direkt zugreifen, wenn dann nur auf die Werte, die drinstehen.
Frame_2 und Frame_3 sind bis auf den Schreibfehler in Frame_2.__init__ identisch, da braucht es also nur eine Klasse, die man zweimal verwenden kann.

Wie Du die Klasse Berechnung verwendest, habe ich Dir in meinem Beispiel doch auch schon gezeigt. Beim Drücken eines Knopfes werden alle nötigen Informationen aus den Entry-Feldern gelesen, ein Berechnenobjekt erstellt und die Berechnungen durchgeführt. Was da noch fehlt, ist die Ergebnisse wieder darzustellen.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Marvin75854: Mit `Enum` definiert man ja eine Klasse beziehungsweise Singleton-Objekte die man nicht mehr verändert, also Konstanten. Also ja, die Klasse definiert man normalerweise auf Modulebene als Namensraum für die Konstanten.

Mein Beispiel war ja komplett ungetestet und auch unvollständig, denn die `PointEntry`-Klasse und die Funktion die am Ende aufgerufen werden sollte, fehlten ja.

Zu Deinem Code: Da wird `None` ausgegeben weil das der Rückgabewert von der `grid()`-Methode ist. Und den bindest Du ja an `self.HX` und `self.HY`. Und auch an `Berechnung_button`.

Bei ``command=lambda:self.Zahlenwerte()`` ist das ``lambda`` überflüssig, denn `self.Zahlenwerte` ist ja bereits ein passendes aufrufbares Objekt: ``command=self.Zahlenwerte``.

In `Zahlenwerte()` müsstest Du die Berechnung machen. Da kommst Du an die eingegebenen Daten heran, und da kannst Du ein Exemplar von `Berechnung` erstellen und dann dessen `calc()`-Methode aufrufen.

In `get_Zahlen()` wird das zweite ``return`` nie erreicht, denn das erste beendet ja schon die Ausführung der Methode. Und die Methode suggeriert auch das da Zahlen zurückgegeben werden und keine `Entry`-Objekte (wenn das nicht `None` wäre, siehe oben).

Die `Berechnung`-Klasse sieht jetzt wieder nach einer sehr umständlich geschriebenen Funktion aus, und es werden auch wieder Attribute ausserhalb der `__init__()` eingeführt.

Code: Alles auswählen

class Berechnung():
    def __init__(self, HX, HXX, HY, HYY):
        self.HX = HX
        self.HY = HY
        self.HXX = HXX
        self.HYY = HYY
    
    def calc():
        self.Ergebnis_X = self.HX + self.HXX
        self.Ergebnis_Y = self.HY + self.HYY
        print(self.Ergebnis_X)
        print(self.Ergebnis_Y)

# Warum nicht:

def berechne(hx, hy, hxx, hyy):
    return (hx + hxx, hy + hyy)

# Oder:

def berechne(point_a, point_b):
    return point_a + point_b

# Mit:

class Point(object):
    
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)
Die *.pyc-Dateien enthalten das Modul zu Bytecode compiliert. Damit Python die nicht jedes mal neu compilieren muss. Das passiert dann nur noch wenn die dazugehörige *.py-Datei neuer ist, man also (wahrscheinlich) etwas am Quelltext geändert hat. Man kann die Dateien verhindern, aber dazu gibt es eigentlich keinen Grund.

Das `super()` ohne Argumente funktioniert nur in Python 3.x und mit Argumenten würde ich das auch in Python 2.7 nicht für `Tkinter`-Unterklassen verwenden, denn die sind in Python 2 noch „old style“-Klassen. Ich würde auch in Python 3 kein `super()` verwenden.

Bei `Tk` brauchst Du kein `parent` das ist ja schon an oberster Stelle. Bei den inneren Widgets brauchst Du das um anzugeben in welchem Eltern-Widget das Widget angeordnet werden soll. Die Aufrufseite davon kennst Du ja: Du übergibst ja allen `Label`, `Entry`, usw. als erstes Argument den Frame oder das Tk-Objekt in dem es dann dargestellt wird.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Marvin75854

Sirius3 hat geschrieben: Donnerstag 26. Juli 2018, 10:25 @Marvin75854: ich bin nicht auf die Idee gekommen, dass Du noch Python2 benutzt. Da funktionieren dann einige Dinge doch anders.
Mein Code also nochmal in der Python2-Version:
Wie groß ist da der Unterschied eigentlich? Also, wenn mein Programm jetzt von Python 2.6 auf Python 3.x umgestellt werden müsste.

Sirius3 hat geschrieben: Donnerstag 26. Juli 2018, 10:25 Modul_2 ist ein schlechter Name für ein Modul. Wenn Dir kein besserer einfällt, also einen der beschreibt, was der Nutzen dieses Moduls ist, dann ist das wahscheinlich keine gute Aufteilung in Module. Warum self.HX und self.HY die Werte None haben, habe ich schon weiter oben erklärt. Attribute sollten generell klein geschrieben werden, komplett GROSSBUCHSTABEN ist für Konstanten reserviert. Berechnung_button ist auch None, zudem eine lokale Variable, die sonst nicht benutzt wird, kann also weg. Lambda ist bei command= auch unnötig, weil man direkt die Funktion als Wert übergeben kann.

Ja die Namen werde ich für mein richtiges Programm anpassen. Hier habe ich nur ein wenig rumprobiert. Mein Button wurde immer von alleine ausgelöst und ich habe irgendwo bei google ein Beispiel mit Lambda gesehen. Ich weiß gar nicht genau was das Lambda bewirkt, jetzt funktioniert es aber auch ohne.

Sirius3 hat geschrieben: Donnerstag 26. Juli 2018, 10:25 Vieles was importiert wird, wird nicht benutzt. Sternchenimporte vermeiden, da man nicht kontrollieren kann, was da alles in den Modulnamensraum importiert wird. Wie man's richtig macht, zeigt mein Beispiel.
Ja da muss ich nochmal durchgucken was ich alles wirklich brauche. Habe immer einfach alles reinkopiert, wenn irgendeine neue Funktion dazugekommen ist.




__blackjack__ hat geschrieben: Donnerstag 26. Juli 2018, 11:30 @Marvin75854: Mit `Enum` definiert man ja eine Klasse beziehungsweise Singleton-Objekte die man nicht mehr verändert, also Konstanten. Also ja, die Klasse definiert man normalerweise auf Modulebene als Namensraum für die Konstanten.
Das kann sehr hilfreich sein denke ich.

__blackjack__ hat geschrieben: Donnerstag 26. Juli 2018, 11:30 Die *.pyc-Dateien enthalten das Modul zu Bytecode compiliert. Damit Python die nicht jedes mal neu compilieren muss. Das passiert dann nur noch wenn die dazugehörige *.py-Datei neuer ist, man also (wahrscheinlich) etwas am Quelltext geändert hat. Man kann die Dateien verhindern, aber dazu gibt es eigentlich keinen Grund.
Ja ich weiß nicht ob das schlimm ist, aber ich arbeite nicht am privaten Rechner und das Programm soll später von meinem Arbeitgeber und einem Kunden benutzt werden. Gefällt mir einfach nicht so, dass der einfach automatisch eine Datei erstellt, zumal das compilieren ja keine Sekunde dauert. Ich weiß allerdings nicht wie das mit anderen Programmen gehandhabt wird. Wahrscheinlich ist das völlig egal und es gibt, wie du schon sagst, keinen Grund das zu unterdrücken.

__blackjack__ hat geschrieben: Donnerstag 26. Juli 2018, 11:30 Bei `Tk` brauchst Du kein `parent` das ist ja schon an oberster Stelle. Bei den inneren Widgets brauchst Du das um anzugeben in welchem Eltern-Widget das Widget angeordnet werden soll. Die Aufrufseite davon kennst Du ja: Du übergibst ja allen `Label`, `Entry`, usw. als erstes Argument den Frame oder das Tk-Objekt in dem es dann dargestellt wird.
Ok das verstehe ich jetzt. Habe da noch ein bisschen rumprobiert.


Also, wenn ich die Struktur richtig verstehe, dann erstelle ich in der main Methode eine GUI und füge dann durch eine oder mehrere Klassen verschiedene Widgets ein. In diesen Klassen schreibe ich auch eine Funktion die von einem Button z.b. aufgerufen werden. Diese Funktionen erstellen dann ein oder mehrere Objekte anderer Klassen oder rufen externe Funktionen auf um bspw. eine Berechnung durchzuführen oder eine Textdatei rauszuschreiben oder ähnliches.

Also es wird nach if __name__ == "__main__": gesucht und dann die main Methode aufgerufen. Die main Methode erstellt erst die GUI mit Namen, Reitern usw. und danach objekte der Klassen mit den Widgets auf der GUI. Also hier ist die GUI schon fertig modeliert. In den Klassen mit den Widgets sind dann gleichzeitig auch die Buttons definiert, die andere Funktionen aufrufen für die Berechnung usw., die ich in meinem Fall in ein anderes Modul schreiben will. Also wenn ich das richtig verstehe, gebe ich aus den Klassen und Funktionen für die Berechnung so direkt gar keine Werte zurück an die GUI, sondern rufe die innerhalb der GUI Klassen auf. Es wird quasi innerhalb der GUI Klassen nach Werten gefragt. Die Durchführung der Berechnung findet innerhalb der GUI Klasse statt ist nur woanders definiert. Genauso wie die Erstellung der GUI komplett in der main Funktion statt findet.
Meine Beschreibung ist vielleicht nicht ganz korrekt, aber so stelle ich mir den Ablauf des Programms im Moment vor.

Vielen Dank. Bin jetzt auf jeden Fall schon einen ganzen Schritt weiter. Ich hoffe das wird am Ende auf wertgeschätzt, dass ich mir die Mühe mit der OOP mache und nicht doch einfach alles so runterschreibe :D Mit den Klassen habe ich bisher nur wild rumprobiert, werde jetzt aber mal anfangen meine 3000 Zeilen umzuschreiben und das nochmal von vorne aufzubauen, bevor ich weiterprogrammiere.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Marvin75854: Sooo unterschiedlich sind Python 2 und 3 nicht. Es gibt mehrere Projekte die beim Umstieg helfen, beispielsweise `future` und `six`. Es gibt ein HOWTO in der Python-Dokumentation, wo auch das frei verfügbare „Porting to Python 3“ verlinkt ist. Und die Dokumentation zu den Hilfsbibliotheken für den Umstieg haben natürlich auch Informationen.

Ich werde, wenn die Zeit dafür gekommen ist, mit `future` arbeiten und die Codebasis erst einmal auf beiden Versionen zum laufen bekommen, und wenn es auf der 3 dann erfolgreich läuft, den Python 2 Support langsam einstellen.

Deine Schaltfläche wurde nicht von alleine ausgelöst, *Du* hast die Funktion/Methode ziemlich wahrscheinlich selbst aufgerufen und deren Rückgabewert als `command` übergeben statt die Funktion/Methode selbst. Ersteres: ``command=self.Zahlenwerte()``. Richtig: ``command=self.Zahlenwerte``.

``lambda`` definiert einfach eine Funktion ohne Namen, a.k.a. eine anonyme Funktion:

Code: Alles auswählen

result = map(lambda x: x * 2, [1, 2, 3])

# <=>

def double(x):
    return x * 2

result = map(double, [1, 2, 3])
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Marvin75854

__blackjack__ hat geschrieben: Donnerstag 26. Juli 2018, 15:33 @Marvin75854: Sooo unterschiedlich sind Python 2 und 3 nicht. Es gibt mehrere Projekte die beim Umstieg helfen, beispielsweise `future` und `six`. Es gibt ein HOWTO in der Python-Dokumentation, wo auch das frei verfügbare „Porting to Python 3“ verlinkt ist. Und die Dokumentation zu den Hilfsbibliotheken für den Umstieg haben natürlich auch Informationen.

Ich werde, wenn die Zeit dafür gekommen ist, mit `future` arbeiten und die Codebasis erst einmal auf beiden Versionen zum laufen bekommen, und wenn es auf der 3 dann erfolgreich läuft, den Python 2 Support langsam einstellen.
OK. Ich habe nämlich keine Ahnung was beim Kunden zur Verfügung steht. Die Chance besteht, dass ich das noch umstellen muss. Habe ich am Anfang gar nicht drüber nachgedacht.

__blackjack__ hat geschrieben: Donnerstag 26. Juli 2018, 15:33 ``lambda`` definiert einfach eine Funktion ohne Namen, a.k.a. eine anonyme Funktion:

Code: Alles auswählen

result = map(lambda x: x * 2, [1, 2, 3])

# <=>

def double(x):
    return x * 2

result = map(double, [1, 2, 3])
Ah ok, das könnte vielleicht auch noch praktisch werden.


Ich brauche schon wieder eure Hilfe. Und zwar habe ich jetzt den ersten kleinen Bereich mit der Funktion auf meiner GUI erstellt. Ich erstelle 3 Klassen mit GUI elementen auf 3 verschiedenen Reitern. Nach der Eingabe der Koordinaten auf dem ersten Reiter wird durch einen Export Button über eine andere Klasse "HDPA_Berechnung in einem anderen Modul eine Berechnung durchgeführt und etwas in eine textdatei geschrieben. Mein Problem ist jetzt, dass ich nicht weiß wie ich auch die Klasse "HDPA_Sitzreihe_2" und damit nochmal die Klasse "HDPA_Berechnung" und im späteren Verlauf noch einige andere Klassen einbinde. Also, dass ich am Ende nur einen Export Button habe. Meine Lösung wäre die gesamte GUI in eine Klasse zu schreiben oder evtl. eine Extra Klasse für den Button zu schreiben der dann eine Funktion aufruft in der die Klasse "HDPA_Sitzreihe_1", "HPDA_Sitzreihe_2" und damit eben öfters die Klasse "HDPA_Berechnung" und alle möglichen anderen Klassen die noch hinzukommen, aufruft. Ich weiß allerdings nicht wie genau das umsetzbar ist. Habt ihr einen Tipp, wie ich das Problem lösen kann?
Hier mein Code:

Code: Alles auswählen

#!/usr/bin/python
from __future__ import print_function
import subprocess
from Tkinter import *
import Tkinter, Tkconstants, tkFileDialog
import os
from tkMessageBox import *
import math
import sys
import time
import Tix as tx



import Programmlogik



class HDPA_Sitzreihe_1(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)
        
        Label(self, text="X").grid(row=13)
	Label(self, text="Z").grid(row=13, column=1)

	self.hx = Entry(self)
	self.hz = Entry(self)
	self.hx.grid(row=14, column=0)
	self.hz.grid(row=14, column=1)

	Label(self, text="Y-Abstand: COG/H-Punkt zu Airbag").grid(row=15,pady=(20,0))
	self.dy = Entry(self)
	self.dy.grid(row=16, column=0) 
        
        
        self.HDPA = Label(self, text="H-Punkt WS50%")
	self.HDPA.grid(row=12, column=0, padx=20)
        
	self.box = IntVar()
	Checkbutton(self, text="HDPA Feld", variable=self.box).grid(row=11)        
        
        self.logo = PhotoImage(file="Sitzreferenzfeld.gif")
	w1 = Label(self, image=self.logo).grid(row=0, column=0, pady=20,padx=20, rowspan=10, columnspan=5)
        
        self.sitzreihe = 1
        self.i = 1
        
        
        self.Export_button = Button(self, text="Export", command=self.Export)
        self.Export_button.grid(row=18, column=0)
        
        
        
    def Export(self):
    
        if self.box.get() == 1:
        
            filename = tkFileDialog.asksaveasfilename(initialdir = os.getcwd(),title = "Speichern der Include-Datei",filetypes = (("all files","*.*"),("inc files","*.inc")))
            f = open(filename,"a")
            f.write("$Erstellt durch COG-Tool am: "+time.strftime("%d.%m.%Y %H:%M:%S")+"\n")
            time.sleep(1)
            
            HDPA = Programmlogik.HDPA_Berechnung(self.hx, self.hz, self.dy, filename, self.sitzreihe, self.i)
            HDPA.HDPA_Berechnen() 
            HDPA.HDPA_Ausgabe()
            
            f = open(filename,"a")
            f.write("ENDDATA\n") 
            f.close()
        else:
            print("Kein HDPA-Feld")
            print(self.hx)
                
        
class HDPA_Sitzreihe_2(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)
    
	Label(self, text="X").grid(row=13)
	Label(self, text="Z").grid(row=13, column=1)
        
	self.HX = Entry(self)
	self.HZ = Entry(self)
	self.HX.grid(row=14, column=0)
	self.HZ.grid(row=14, column=1)
        
	Label(self, text="Y-Abstand: COG/H-Punkt zu Airbag").grid(row=15,pady=(20,0))
	self.HY = Entry(self)
	self.HY.grid(row=16, column=0)

	self.HDPA = Label(self, text="H-Punkt WS50%")
	self.HDPA.grid(row=12, column=0, padx=20)

	box = IntVar()
	Checkbutton(self, text="HDPA Feld", variable=box).grid(row=11)
        
        self.logo = PhotoImage(file="Sitzreferenzfeld_2.Reihe.gif")
	w2 = Label(self, image=self.logo).grid(row=0, column=0, pady=20,padx=20, rowspan=10, columnspan=5)
        
        
class HDPA_Sitzreihe_3(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)        
        
        self.logo = PhotoImage(file="Sitzreferenzfeld_3.Reihe.gif")
        w = Label(self, image=self.logo).grid(row=0, column=0, pady=20,padx=20, rowspan=10, columnspan=5)
        
        
        

def main():
    GUI = tx.Tk()
    GUI.title ("COG Tool") 
    GUI.geometry("1800x800")
    
    swr=tx.ScrolledWindow(GUI)
    swr.pack(fill=tx.BOTH, expand=1)
    
    nb=tx.NoteBook(swr.window)
    nb.pack(fill=tx.BOTH, expand=1)

    nb.add("sitzreihe"+str(1),label="1. Sitzreihe")
    nb.add("sitzreihe"+str(2),label="2. Sitzreihe")
    nb.add("sitzreihe"+str(3),label="3. Sitzreihe")
     
    
    hdpa_sitzreihe_1 = HDPA_Sitzreihe_1(nb.sitzreihe1)
    hdpa_sitzreihe_1.grid(row=0, column=0)
    
    
    hdpa_sitzreihe_2 = HDPA_Sitzreihe_2(nb.sitzreihe2)
    hdpa_sitzreihe_2.grid(row=0, column=0)
 
    hdpa_sitzreihe_3 = HDPA_Sitzreihe_3(nb.sitzreihe3)
    hdpa_sitzreihe_3.grid(row=0, column=0) 
 
    
    GUI.mainloop()


if __name__ == "__main__": 
    main()

und das 2. Modul:

Code: Alles auswählen

#!/usr/bin/python
from __future__ import print_function
import subprocess
from Tkinter import *
import Tkinter, Tkconstants, tkFileDialog
import os
from tkMessageBox import *
import math
import sys
import time
import Tix as tx


class HDPA_Berechnung(object):

    def __init__(self, hx, hz, dy, filename, sitzreihe, i):
        self.hx = float(hx.get())
        self.hz = float(hz.get())
        self.dy = float(dy.get())
        self.sitzreihe = int(sitzreihe)
        self.filename = filename
        self.i = i
        
    def HDPA_Berechnen(self):
        self.links_unten_x = self.hx + 126 - 82   # ACHTUNG: Hier noch + SITZLAENGSVERSTELLWEG
        self.links_unten_z = self.hz + 594 -58    # ACHTUNG: Hier noch + SITZLAENGSVERSTELLWEG
        self.rechts_unten_x = self.hx + 147 +82   # ACHTUNG: Hier noch + SITZLAENGSVERSTELLWEG
        self.rechte_unten_z = self.hz + 594 -52   # ACHTUNG: Hier noch + SITZLAENGSVERSTELLWEG
        self.links_oben_x = self.hx + 126 - 82    # ACHTUNG: Hier noch + SITZLAENGSVERSTELLWEG
        self.links_oben_z = self.hz + 693 + 82    # ACHTUNG: Hier noch + SITZLAENGSVERSTELLWEG
        self.rechts_oben_x = self.hx + 147 + 82   # ACHTUNG: Hier noch + SITZLAENGSVERSTELLWEG
        self.rechts_oben_z = self.hz + 693 + 82   # ACHTUNG: Hier noch + SITZLAENGSVERSTELLWEG
            
        self.delta_y = self.dy - 365
        
    def HDPA_Ausgabe(self):
        
        f = open(self.filename,"a")
        f.write("$H-Punkt: ("+str(self.hx)+", -365, "+str(self.hz)+")\n")
        f.write("$HDPA-Feld fuer die "+str(self.sitzreihe)+". Sitzreihe:\n")
        f.write("NODE  / 1000000"+str(self.i)+"%16.6f%16.6f%16.6f\n"%(self.links_unten_x, self.delta_y, self.links_unten_z))
        f.write("NODE  / 1000000"+str(self.i+1)+"%16.6f%16.6f%16.6f\n"%(self.rechts_unten_x, self.delta_y, self.rechte_unten_z))
        f.write("NODE  / 1000000"+str(self.i+2)+"%16.6f%16.6f%16.6f\n"%(self.links_oben_x, self.delta_y, self.links_oben_z))
        f.write("NODE  / 1000000"+str(self.i+3)+"%16.6f%16.6f%16.6f\n"%(self.rechts_oben_x, self.delta_y, self.rechts_oben_z))
        f.write("SHELL / 1000000"+str(self.sitzreihe)+"       11000000"+str(self.i)+"1000000"+str(self.i+1)+"1000000"+str(self.i+3)+"1000000"+str(self.i+2)+"        \n")
        f.write("PART  / 1000000"+str(self.sitzreihe)+"SHELL          1                        \n")
        f.write("NAMEDefault HDPA_Feld_"+str(self.sitzreihe)+".Sitzreihe\n")
        f.write("                    \n")
        f.write("                              \n")
        f.write("        1.    3               \n\n")
        f.write("END_PART\n")
        f.close()        
        

             
Was mir auch nicht gefällt ist die Lösung wie ich in der Funktion "HDPA_Ausgabe" die Ausgabe mit der Variablen "i" und "sitzreihe" versuche zu automatisieren. Habe da aber noch keine wirkliche Lösung gefunden.
Antworten