Python mit Tkinter / circular import

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
Ghostfighter6
User
Beiträge: 13
Registriert: Dienstag 12. September 2017, 07:02

Moin,
nachdem ich mich vor ca. 2 Jahren hier angemeldet und schon mal gesucht habe - aber nicht fündig geworden bin - noch einmal ein Versuch :D

Herausforderung :
Programm in Python mit diversen Modulen
GUI in Tkinter

In der GUI werden diverse Widgets eingebaut die sowohl in der GUI als auch in diversen anderen Modulen verwendet werden.
In der GUI werden über wenige Widgets auch einige der Python-Funktionalitäten in Modulen gestartet, die natürlich weitere Unter-Module aufrufen.
In diversen Modulen müssen zwingend Fehlerausgaben in die GUI eingebaut werden, etc.
Es wurde ein Interface-Module mit den Widget-Funktionalitäten erzeugt die sowohl in der GUI, als auch in Python-Modulen verwendet werden müssen.
Im Interface werden die Widget-Funktionen in GUI aufgerufen.

Ergebnis natürlich (wegen des direkten Aufrufs der eingebundenen Module zur Laufzeit) ---> circular Import.
Ich möchte sauber die Anwendung von der GUI trennen über ein Schnittstellen-Modul wie man das auch in C/C++ machen würde.
Dort habe ich den Vorteil, dass ich die Funktionen nur deklarieren muss damit sie bekannt sind und sie werden nicht sofort inkludiert.
Das wird ja erst in der Link-/Locator-Phase gelöst.

Diese Interface-Funktionalitäten benötigen aber natürlich die Widgets als Objekte, wenn ich nicht auf Funktionen in der GUI zugreifen möchte um den Circular Import zu vermeiden.
Wenn ich sie aber nicht global machen möchte - was ja einer sauberen, gekapselten Programmierung widerspricht, muss ich sie als Übergabewerte dem Modul zur Verfügung stellen.

Hatte darüber nachgedacht alle Widgets in einer Liste zusammenzufassen, damit die Parameterübergabe nicht ausartet und sie an die Module zu übergeben.
Aber eigentlich widerspricht das meiner Vorstellung von einer sauberen Programmierschnittstelle.
Ich möchte dort im Modul lediglich eine abstrakte Funktion haben dieses oder jenes Widget zu enablen, zu disablen, Ausgaben in ein Fenster zu schreiben, Messageboxen aufzumachen, die Progressbar auf x% zu setzen, usw. ... unabhängig von den Widget-Objekten.

Wie würdet ihr das lösen?
HILFREICHE Antworten sind willkommen ;)

LG
Ghostfighter
Ghostfighter6
User
Beiträge: 13
Registriert: Dienstag 12. September 2017, 07:02

Vielleicht als Nachtrag wie ich es augenblicklich gelöst habe :

Ich habe die Funktionen abstrakt in ein Interface-Modul eingeführt - ohne jede Implementierung - quasi nur als Verweis ... und habe sie dann im eigentlichen GUI-Modul implementiert.
So kann ich sie in jedem Modul verwenden, ohne das ich von einem anderen Modul direkt auf das GUI-Modul zugreifen muss.
Ich finde das unschön und glaube auch nicht, dass das gute Python-Manier ist, aber mir fiel einfach nichts Besseres ein :(
Sirius3
User
Beiträge: 18078
Registriert: Sonntag 21. Oktober 2012, 17:20

Du brauchst eine klare Trennung von GUI und der Geschäftslogik. Deine diversen Module dürfen nichts von der GUI wissen. Wenn Du glaubst, dass das nicht geht, dann stelle hier Dein konkretes Problem vor.
Für das Problem, dass Funktionen log-Meldungen haben, die in der GUI dargestellt werden sollen, hier eine Idee: https://beenje.github.io/blog/posts/log ... xt-widget/
Benutzeravatar
grubenfox
User
Beiträge: 548
Registriert: Freitag 2. Dezember 2022, 15:49

Ich habe zwar ein paar Probleme mir den aktuellen Ist-Zustand vorzustellen, aber dieses hier:
Ghostfighter6 hat geschrieben: Dienstag 17. September 2024, 09:13 In der GUI werden diverse Widgets eingebaut die sowohl in der GUI als auch in diversen anderen Modulen verwendet werden.
klingt irgendwie falsch. (GUI-)Widgets sollten nur in der bzw. von der GUI verwendet werden und nicht von anderen Modulen. Wie auch Sirus3 hier drüber schreibt.

An dieser Stelle muss ich an das Beobachter-Muster denken
https://refactoring.guru/design-patterns/observer
https://refactoring.guru/design-pattern ... g-features
https://www.geeksforgeeks.org/observer- ... -patterns/

Ansonsten habe ich als Interface/Schnittstellen-Modul gerade ein Modul im Kopf das die öffentlichen Daten (und die Möglichkeit sich als Beobachter für diese und jene Daten anzumelden) zur Verfügung stellt. Die GUI importiert das Interface-Modul um von dort die Daten (die die GUI anzeigen möchte) zu erhalten und über Änderungen an den Daten informiert zu werden.

Die "Anwendung" (bzw. Busines-Logic, also alles das was keine GUI ist) importiert das Interface-Modul um dort die öffentlichen Daten (die irgendwer irgendwie und irgendwo darstellen kann) abzulegen.
Ghostfighter6
User
Beiträge: 13
Registriert: Dienstag 12. September 2017, 07:02

Danke erstmal für die Antworten ...

@Sirius :
Habe das Beispiel nicht komplett durchgearbeitet, aber so wie ich das sehe geht es dabei lediglich darum, Ausgaben in der GUI darzustellen ...

@grubenfox :
Das scheint mir auch, als ob es sich nur um die Übernahme von Daten außerhalb der GUI in diese handelt.

Okay, vielleicht sollte ich das Problem noch mal erläutern an einem sehr simplen Beispiel :
Ich habe ein GUI-Modul mit einem einem Textfeld und einem Button.
Ich habe ein weiteres Modul mit - wie es grubenfox nennt - Geschäftslogik.

Das Textfeld soll über eine Funktion im GUI-Modul Daten aus dem Logik-Modul annehmen und ausgeben ... das ist simple.
Dann wird der Button geklickt und ein command ausgeführt, welches einen Ablauf in dem Logik-Modul startet.
Ich denke mal so eine Anwendung kommt oft vor.

Da ich aber dazu in dem GUI-Modul die zu startende Funktion im Logik-Modul über einen Import bekannt machen muss, im Logik-Modul die Funktion das Textfeld zu befüllen, bekomme ich schnell einen Circular Import.
Ich hoffe das ist nun verständlicher.

Die eigentliche Schnittstelle ist nicht mein Problem ... aber das Einbinden der Funktion des anderen Moduls.

Wie macht man so etwas in Python grundsätzlich ... ich habe bisher kein Beispiel dafür gefunden bzw. es handelte sich lediglich um Module die quasi aufeinander aufbauen, also rudimentäre Dinge einbauen die von nichts anderem abhängen.
Ich habe darum das Interface-Modul eingebaut, das zwischen den beiden 'Layern' liegt ... ähnlich wie die RTE beim AUTOSAR, um beide komplett voneinander zu trennen.

Vielen Dank im voraus für Infos :)
Benutzeravatar
grubenfox
User
Beiträge: 548
Registriert: Freitag 2. Dezember 2022, 15:49

Ghostfighter6 hat geschrieben: Dienstag 17. September 2024, 12:09 im Logik-Modul die Funktion das Textfeld zu befüllen
Also Daten von außerhalb der GUI (nämlich vom Logik-Modul) in die GUI übernehmen ---> siehe oben

Wobei das bei dem simplen Beispiel noch einfacher ist (scheint mir so):
Ghostfighter6 hat geschrieben: Dienstag 17. September 2024, 12:09 Das Textfeld soll über eine Funktion im GUI-Modul Daten aus dem Logik-Modul annehmen und ausgeben ... das ist simple.
Dann wird der Button geklickt und ein command ausgeführt, welches einen Ablauf in dem Logik-Modul startet.
... und nach dem starten des Ablaufs im Logik-Modul wird wieder die "Funktion im GUI-Modul Daten aus dem Logik-Modul annehmen und ausgeben" aufgerufen.
Benutzeravatar
grubenfox
User
Beiträge: 548
Registriert: Freitag 2. Dezember 2022, 15:49

Womit das "simple Beispiel" doch eigentlich die Lösung für das Problem ist, oder nicht?
In der GUI zuerst einen Ablauf im Logik-Modul aufrufen und danach in der GUI die GUI updaten ("über eine Funktion im GUI-Modul Daten aus dem Logik-Modul annehmen und ausgeben")...
Ghostfighter6
User
Beiträge: 13
Registriert: Dienstag 12. September 2017, 07:02

@grubenfox :
Sorry ... ich verstehe nicht wie das mein Problem löst.
Ich muss wechselseitig eine Funktion bekanntmachen von einem Modul in einem anderen ... das ist mein Problem.
Zur Laufzeit wird dann interativ das jeweils andere Modul importiert ... --> Circular Import

Also mir fehlt das Wissen wie sowas grundsätzlich gehandelt wird.

Wenn ich nur eine Anwendung - also nur Python habe - dann ist das vermutlich relativ einfach ... dann habe ich ein Hauptmodul das ich auf mehrere funktionale Einheiten aufteile.
Hier handelt es sich um ein Python Programm und ein event-gesteuertes zweites Programm die Daten und/oder Funktionalitäten austauschen oder aufrufen.
Benutzeravatar
grubenfox
User
Beiträge: 548
Registriert: Freitag 2. Dezember 2022, 15:49

Nun bin ich verwirrt...
Ghostfighter6 hat geschrieben: Dienstag 17. September 2024, 12:56 Wenn ich nur eine Anwendung - also nur Python habe - dann ist das vermutlich relativ einfach ... dann habe ich ein Hauptmodul das ich auf mehrere funktionale Einheiten aufteile.
Bis eben war das auch noch so (dachte ich.... )
Aber nun ...
Ghostfighter6 hat geschrieben: Dienstag 17. September 2024, 12:56 Hier handelt es sich um ein Python Programm und ein event-gesteuertes zweites Programm die Daten und/oder Funktionalitäten austauschen oder aufrufen.
ist da noch ein zweites Programm (nicht Python)? Dann gibt es da ja auch nichts zu importieren... die Kommunikation zwischen zwei unabhängigen Programmen ist nun eine ganz andere Baustelle.
Ghostfighter6
User
Beiträge: 13
Registriert: Dienstag 12. September 2017, 07:02

Tkinter ist eine GUI ... das ist per se nicht Python
Das wäre ähnlich, wenn ich das mit Qt verheiratet ... Qt ist auch nicht Python ... die kommunizieren über eine Schnittstelle ;)

von Haus aus verfügt Python über keinerlei graphische Funktionalität
Sirius3
User
Beiträge: 18078
Registriert: Sonntag 21. Oktober 2012, 17:20

@Ghostfighter6: es bleibt bei meiner ursprünglichen Aussage, das Logik-Modul darf nichts von der GUI wissen, dann gibt es auch keine circulären Importe.
Der Ablauf ist, wie bei jeder Funktion gleich, eine Funktion bekommt Parameter und gibt einen Rückgabewert zurück, also:
1. GUI: Knopf wird gedrückt
2. GUI: Ruft Logik-Funktion mit Inhalt des Textfeldes auf.
3. Logik: rechnet und gibt Ergebnis zurück
4. GUI: Nimmt Rückgabewert des Funktionsaufrufs und stellt ihn in der GUI dar.

Punkt 3 ist die reine Rechnung, und weiß nichts von der GUI.

Ich glaube zu ahnen, was Dir vorschwebt, aber das wird dann gleich deutlich komplizierter, und ist in dem Artikel, den ich verlinkt habe erklärt.
1. GUI: Knopf wird gedrückt
2. GUI: Ein eigenständiger Thread wird gestartet, der eine länger dauernde Rechnung durchführt.
3a. GUI: es wird in regelmäßigen Abständen geschaut, ob Meldungen in einer Kommunikations-Queue liegen und diese werden dargestellt.
3b. Logik: immer wenn Meldungen gemacht werden sollen, werden diese in die Kommunikations-Queue gelegt.
4. Logik: über die Queue wird gemeldet, dass die Rechnung fertig ist
5. GUI: aus der Queue wird die Fertig-Meldung gelesen, und in der GUI "fertig" ausgegeben.
Punkt drei findet gleichzeitig in getrennten Threads statt, und das macht die Sache so schwierig zu verstehen und zu programmieren.
Ghostfighter6
User
Beiträge: 13
Registriert: Dienstag 12. September 2017, 07:02

@Sirius :
Exactely ... that's it :)

Die Oberfläche ist nur für die User um die Bedingungen zu wählen (Konfigurationen, Dateien, Testpläne in Jama) ... dann wird die Ausführung einer Testautomatisierung gestartet und evtl. Fehlermeldungen in die Oberfläche geschrieben ...
Es wird auch mit Sharepoint, Azure DevOps und wie gesagt Jama gearbeitet ... mit Test-HW und all so einem Zeug ...
Bin halt von Haus aus E-Ing und komme eigentlich aus der Embedded Welt mit früher mal Treiber und OS-Programmierung .. aber für Embedded halt.
Windows mache ich nun für die Automatisierung und mit Threads habe ich bisher nur als Lesestoff Erfahrung.

Ich habe es halt über stinknormale Funktionen implementiert und über 'update.idletask' versucht die Oberfläche nachzuziehen.
Nach dem Testvorgang (es wurde eine Test-Applikation gestartet) geht die Kontrolle an Python zurück.
Darum halt der Circular Import ... und über die 'normale Programmierung' sehe ich nicht wie ich den umgehe.
Thread ist ein Stichwort ... werde mich mal mit beschäftigen ... kann ich damit den Circular Import wirklich vermeiden?
Nicht, dass ich einen Haufen Zeit investiere um dann zu sehen, dass ich wieder gegen die Wand laufe.
Leider muss man das alles in der privaten Zeit machen ... die Manager wollen nach wenigen Storypoints (agile SW) ein Ergebnis sehen.

Vielen Dank für den Tip :)
Und danke ... hatte den Artikel nur überflogen ... dann lese ich ihn wohl mal ausführlicher :D

Apropos : Mir schwebt es nicht vor, sondern es läuft bereits ... nach der Build-Pipeline wird die Test-Pipeline über einen Agent automatisch gestartet.
Aber mir gefiel die 'Lösung' mit dieser Schnittstelle nicht ... die Firma will natürlich keine Zeit mehr investieren ...
Antworten