Seite 1 von 1

Neustart

Verfasst: Freitag 13. März 2015, 01:47
von Sophus
Hallo Leute,

ich überlege gerade, wie man seine eigene Anwendung neustarten kann. Zur Zeit löse ich das über eine Batch-Datei. Aber dazu später. Erst einmal meine Ordnerstruktur - siehe Bild.

Bild

Im Ordner proiject_xarphus liegt dann das Modul xarphus.py. Ich behandel dieses Modul als eine Art Index.

Code: Alles auswählen

# -*- coding: utf-8 -*-
from files.modStart import start_main

def main():
    start_main()

if __name__ == "__main__":
    main()
Damit greife ich dann auf die Funktion files\start_main.py im Modul modStart zu. Alles ganz unspektakulär. Von dort aus werden nach unten hin nach und nach Module importiert. Klappt ja auch alles. Ganz weit rechts im Bild seht ihr, dass die Datei ui_pp_mdi.py markiert ist. In diesem Modul wird die UI-Datei geladen und die ganzen Aktionen, Trigger etc festgelegt. Alles super soweit. unter anderem ist dort eine Aktion "Neustart". Meine erste Idee war, rückwärts das Index-Modul xarphus.py zu importieren, aber soweit reicht der Import nicht.

Das wäre der vollständige Pfad zum ui_pp_mdi-Modul
\project_xarphus\files\modules_ui\ui_pp_mdi.py

Also habe ich erst einmal im ui_pp_mdi.py-Modul folgende Funktion hinterlegt:

Code: Alles auswählen

    def run_batch(self):
        from subprocess import Popen
        app.quit()
        self.batch_it = os.path.join(os.path.abspath(".")  + os.sep + "xarphus.bat")
        Popen(self.batch_it)
Ich rufe bei einem bestimmten Trigger die Funktion run_batch, schließe die Anwendung, und die Batch-Datei startet meine Anwendung wieder. Funktioniert auch alles. Das Problem ist aber, man will ja plattformunabhängig agieren, sonst hätte man ja auch auf Python verzichten können, sobald man nur in eine OS-Richtung geht. Nun weiß ich nicht, wie ich ein Neustart bewerkstelligen könnte, und das innerhalb meiner Ordnerstruktur. Wie gesagt, xarphus.py ist sozusagen das Ausgangsmodul. Nur leider kann ich nicht rückwärts das Modul xarphus.py (ausgehend von ui_pp_mdi.py) importieren, da der Weg zu lang ist. Wäre mir das möglich, hätte ich ja getrost auf die main()-Funktion in xarphus.py zugreifen können.

Re: Neustart

Verfasst: Freitag 13. März 2015, 04:09
von snafu
@Sophus: Es gibt unter Python 2.x die `reload()`-Funktion. Dieser übergibt man das neu zu ladende Modul. Unter Python 3.x steckt die gleichnamige Funktion im Modul `imp`. Das mit der Batch-Datei wird so nicht funktionieren.

Über alle bereits geladenen Module kommst du mittels `sys.modules`. Dann musst du auch nicht kompliziert irgendwelche Modul-Hierarchien abarbeiten, um an ein bestimmtest Modul zu kommen.

Generell würde ich aber davon abraten, ein einmal importiertes Modul nochmal zu importieren (im Sinne eines Reloads). Du müsstest dann immer darauf achten, dass an den Orten, wo Bestandteile des Moduls bereits "angefasst" wurden, auch die entsprechende Aktualisierung bekannt gemacht wird, damit nicht mit veralteten Daten gearbeitet wird. Oft macht man sich damit mehr Probleme als man zu lösen glaubt.

Wenn du uns den konkreten Anlass für den gewünschten Reload mitteilst, dann können wir dir möglicherweise Tipps geben, wie du dein Vorhaben anders umsetzen kannst.

Re: Neustart

Verfasst: Freitag 13. März 2015, 09:17
von EyDu
Warum gehst du den Weg über eine batch-Datei und startest Python nicht einfach direkt mittels Popen?
Sophus hat geschrieben:

Code: Alles auswählen

    def run_batch(self):
        from subprocess import Popen
        app.quit()
        self.batch_it = os.path.join(os.path.abspath(".")  + os.sep + "xarphus.bat")
        Popen(self.batch_it)
An der Methode ist wirklich viel kaputt. "run_batch" hört sich sehr allgemein an. Da würde ich jetzt einen Parameter erwarten, bei dem man eine batch-Datei übergeben kann. Eigentlich ist das ganze Ding so allgemein, dass man die Funktion gar nicht braucht.

Module innerhalb von Funktionen oder Methoden zu importieren ist kein guter Stil. Das kann man machen, falls das nur einmal im Programmablauf passiert, man Ladezeiten verkleinern will oder ein Modul nicht unbedingt verfügbar ist. Ansonsten gehören Importe immer nach oben ins Programm.

Woher kommt der Name "app"? Der taucht da einfach so auf. Und noch viel, viel schlimmer: Warum wird da überhaupt quit drauf aufgerufen? Das ist einfach nur ein riesiger Seiteneffekt in einer Methode, welchen man so ganz sicher nicht haben möchte. Schon gar nicht, wenn eine Methode "run_batch" heißt und der quit-Aufruf vollkommen unerwartet ist.

Du führst den join-Aufruf hier ad absurdum. Der macht hier gar nichts. Warum fügst du das Trennzeichen per Hand ein? Das ist nicht nur unnötig, sondern unter Umständen auch noch falsch. Und dann wird das ganze noch an ein Attribut gebunden. Ein ganz normaler Name tut es hier auch. Und ob abspath hier wirklich notwendig ist, darüber solltest du dir auch mal Gedanken machen.

Der Popen-Aufruf wartet auch nicht bis dass aufgerufene Programm durchgelaufen ist. Ist das so gewollt?

Re: Neustart

Verfasst: Freitag 13. März 2015, 11:34
von snafu
Ach so, es geht ja um das Neustarten des kompletten Programms. Dann bitte meinen oben gemachten Kommentar ganz schnell wieder vergessen. Ich war wohl gestern Nacht nicht mehr so aufnahmefähig.

Re: Neustart

Verfasst: Freitag 13. März 2015, 11:44
von Sophus
@snafu: Ich arbeite mit Python 2.x. Nun, wozu ich die Reload-Funktion brauche? Ich dneke hierbei einen Schritt weiter. Später möchte ich mit einer INI-Datei arbeiten, um Einstellung dort abzulegen, und sie auch von dort aus auszulesen. Und sobald man eine Einstellung vorgenommen hat, möchte ich, dass sich mein Programm komplett neustartet, damit die Einstellungen wirksam werden. Das war meine ursprüngliche Idee. Deswegen beschäftige ich mich gerade mit dem restart-Thema. Kein Thema, dass du gestern Nacht etwas müde warst. Ich war es auch :-)

@EyDu: Ich habe das Modul subprocess mit Absicht in einer Funktion geladen, eben weil ich dieses Modul in diesem Fall nur ein einziges bräuchte, und den Namensraum nicht unnötig vollstopfen will. Deine Frage zu der "app". Die Funktion wird in einer Klasse kreiert. Dort ist app mit QApplication geschmückt. Zu deiner Anmerkung des Join-Aufrufes. Du meinst mit manuelle Trennstriche dieses (".") richtig? Wie wäre denn der Join-Aufruf in meinem Fall richtig? Ich wollte damit nur erreichen, dass ich mit einem sogenannten universellen Pfad arbeiten kann, also was die Backslash anbelangt. Linux und Windows verfolgen da unterschiedliche Ziele, was die Pfad-Angabe anbelangt. Wenn ich self.batch_it mit print ausgeben lasse, wird der Pfad korrekt angezeigt.

Re: Neustart

Verfasst: Freitag 13. März 2015, 11:52
von Sirius3
Wie man join verwendet, steht in der Dokumentation. Du brauchst da aber gar kein join, weil abspath schon das macht, was Du so kompliziert geschrieben hast:

Code: Alles auswählen

batch_it = os.path.abspath("xarphus.bat")
Aber eigentlich willst Du gar nicht vom aktuellen Verzeichnis aus gehen, sondern vom Verzeichnis, in dem Dein Modul liegt (__file__).

Re: Neustart

Verfasst: Freitag 13. März 2015, 12:03
von Sophus
@Sirius3: In meinem Falle wollte ich an meine Batch-Datei (vorerst, weil ich keine andere Möglichkeit fand). Daher habe ich diesen Pfad genommen. Wenn wir schon bei Pfaden sind. Da habe ich gleich eine weitere Frage:

Code: Alles auswählen

self.getPath_addBook_icon = os.path.join(os.path.abspath("."), 'files', 'images', "img_32x32", 'addBook.png')
Ich habe diese Pfad-Angabe mal aus dem Kontext gerissen. Man sieht hier, dass ich auf eine *.png-Datei zugreifen will. Ich hoffe mal, dass ich hier diesen Join-Aufruf richtig verwende? Ich meine, die Pfad-Angaben habe ich überprüfen lassen und sind korrekt. Aber ich wollte mal wissen, ob ich sie trotzdem korrekt verwende? files, images, und img_32x32 sind hier die Namen der Ordner.

Re: Neustart

Verfasst: Freitag 13. März 2015, 12:21
von snafu
Ich denke, es werden zwei Python-Prozesse benötigt: Der erste Prozess startet den zweiten Prozess, welcher die App enthält. Wenn ein Neustart erforderlich wird, muss der zweite Prozess dies dem ersten Prozess mitteilen (Stichwort: Interprozesskommunikation). Der erste Prozess würde dann den zweiten Prozess zum sauberen Beenden auffordern und danach einen neuen zweiten Prozess anstoßen.

Das ganze Gemurkse kann man übrigens umgehen, wenn man die App (oder relevante Teilbereiche) an geeigneter Stelle die Konfiguration neu einlesen lässt. Dies könnte dann einfach ein Neuzeichnen des Fensters und alle weiteren Schritte zur Reinitialisierung auslösen. Das erfordert sicherlich einen größeren Programmieraufwand (Refactoring), aber ist IMHO am Ende sauberer.

Re: Neustart

Verfasst: Freitag 13. März 2015, 12:33
von snafu
Das Starten eines zweiten Python-Prozesses würde ich im Code des ersten Prozesses übrigens so machen:

Code: Alles auswählen

app = subprocess.Popen([sys.executable, '-m', 'app_modul'])
Erstes Argument: Pfad zum Python-Interpreter
Zweites Argument: Anweisung, dass ein Modul geladen werden soll
Drittes Argument: Name des zu startenden Moduls (ohne ".py")

Letzteres ist halt das Modul, wo die `main()`-Funktion für die App enthalten ist. Die Benennung ist natürlich nur ein Beispiel.

Re: Neustart

Verfasst: Freitag 13. März 2015, 12:38
von Sophus
@snafu: Deine Ausführung klingt für einen Anfänger verdammt kryptisch und umfangreich. Nach einer kurzen Recherche fand ich diese Seite: https://www.daniweb.com/software-develo ... n-program-. Dort wird dieser Code präsentiert:

Code: Alles auswählen

import sys
import os
def restart_program():
    """Restarts the current program.
    Note: this function does not return. Any cleanup action (like
    saving data) must be done before calling this function."""
    python = sys.executable
    os.execl(python, python, * sys.argv)
if __name__ == "__main__":
    answer = raw_input("Do you want to restart this program ? ")
    if answer.lower().strip() in "y yes".split():
        restart_program()
Probiert habe ich dies noch nicht. Meine Idee war ja, an meine xarphus.py Datei zu kommen, was ja sehr umständlich ist. Und diesen Skript dort zu hinterlegen.

Re: Neustart

Verfasst: Freitag 13. März 2015, 12:43
von snafu
Sophus hat geschrieben:@snafu: Deine Ausführung klingt für einen Anfänger verdammt kryptisch und umfangreich.
Ein Anfänger sollte sich einfach damit zufrieden geben, dass ein Reload des Programms nicht ohne größere Verrenkungen möglich ist. Nichts für ungut. Ich wüsste jetzt auf Anhieb auch kein Programm, welches sich so verhält. Das könnte durchaus seine Gründe haben...
Sophus hat geschrieben:Nach einer kurzen Recherche fand ich diese Seite: https://www.daniweb.com/software-develo ... n-program-.
Dann doch bitte lieber `Popen()`. Einen Ansatz dafür habe ich ja bereits beschrieben.

Re: Neustart

Verfasst: Freitag 13. März 2015, 12:48
von jerch
@Sophus:
Schau mal die exec*-Funktionen im Modul os an, ob die leisten, was Du suchst.

Re: Neustart

Verfasst: Freitag 13. März 2015, 12:55
von snafu
Mittels `execl()` (und seinen Verwandten) ersetzt man halt den kompletten Python-Prozess durch einen neuen Prozess. Damit spart man sich die Unterteilung in zwei verschiedene Prozesse, wie ich sie vorgeschlagen hatte, indem man einfach alt durch neu überschreibt. Man muss sich dann ggf innerhalb des selben Prozesses um das saubere Herunterfahren der App kümmern, damit es nicht irrtümlich zur Darstellung von 2 Apps bzw zu anderen Fehlern bei der Darstellung der GUI kommt. Ich kenne das Verhalten in so einem Fall nicht.

Zudem stellt sich die Frage: Käme der Python-Interpreter aus dem älteren Prozess damit eigentlich ohne weiteres klar? Können alle Ressourcen abgeräumt werden usw? Stellt `execl()` ein sauberes oder ein abruptes Beenden des alten Prozesses dar?

Re: Neustart

Verfasst: Freitag 13. März 2015, 13:02
von jerch
@snafu:
Die exec-Funktionen sind sehr low level, heisst man sollte genau wissen, was die tun. Ein exec ist wie "Steckerziehen" des Programmes, d.h. man sollte den laufenden Prozess vorher soweit aufgeräumt haben, dass das ok ist. Gleichwohl bleiben alle file descriptors offen (!), welche nicht ein bestimmtes Flag haben. Offene Dateien sollten also vorher mit aufgräumt werden.

Re: Neustart

Verfasst: Freitag 13. März 2015, 13:06
von Sophus
@snafu: Ich habe deinen Ansatz wie folgt umgesetzt:

Code: Alles auswählen

        from subprocess import Popen
        re_start = Popen([sys.executable, '-m', 'xarphus'])
        app.quit()
ich hoffe, dass Python hierbei auf xarphus.py, also auf mein Ausgangs-Modul zugreift. Mus ich hierbei nicht die Ordner-Hierarchie angeben, damit Python weißt, weilche Datei gemeint ist?
Ich weiß, dass es unsauber und kein guter Stil ist, Module innerhalb einer Funktion/Methode zu laden. Aber in diesem Fall brauche ich dieses Modul ja nur für den Neustart.

Re: Neustart

Verfasst: Freitag 13. März 2015, 13:08
von snafu
Was mir noch einfällt: Man könnte auch ganz simpel über Exit-Codes kommunizieren. Der Prozess mit der eigentlichen App beendet sich immer selbst und gibt mittels speziellem Exit-Code an, ob er neugestartet werden möchte. Dies wird vom "äußeren" Prozess ausgewertet und dann ggf der Neustart angewiesen. Dies sollte auch für einen Anfänger recht leicht zu realisieren sein. Also das wäre wahrscheinlich mein bevorzugter Weg, wenn nichts gravierendes dagegen spricht. Es beruht in dem Fall wieder auf der Idee mit den zwei Prozessen.

Re: Neustart

Verfasst: Freitag 13. März 2015, 13:16
von snafu
Sophus hat geschrieben:@snafu: Ich habe deinen Ansatz wie folgt umgesetzt:

Code: Alles auswählen

        from subprocess import Popen
        re_start = Popen([sys.executable, '-m', 'xarphus'])
        app.quit()
Und der Code steckt im `xarphus`-Modul oder wie? Man sollte halt darauf achten, dass bei mehreren Neustarts nicht am Ende zig ineinander verschachtelte Python-Prozesse bestehen. Ich habe das Gefühl, genau dies würde bei dir passieren. Denn ein `app.quit()` beendet zwar die GUI, aber eben nicht den Python-Interpreter an sich.
Sophus hat geschrieben:ich hoffe, dass Python hierbei auf xarphus.py, also auf mein Ausgangs-Modul zugreift. Mus ich hierbei nicht die Ordner-Hierarchie angeben, damit Python weißt, weilche Datei gemeint ist?
Durch das "-m" ist es wie ein normaler Import. Wenn Python mittels `import xarphus` das richtige Modul benutzt, dann sollte es auch über die Kommandozeilen-Variante mit `Popen()` funktionieren. Oder hast du zwei verschiedene Versionen des Moduls und möchtest eine ganz bestimmte Version ausführen?

Re: Neustart

Verfasst: Freitag 13. März 2015, 15:11
von Sophus
@snafu: Nein, der Code steckt nicht in xarphus.py. Wenn du dir mein Screenshot noch einmal anschaust. Der Code steckt in ui_pp_mdi.py. In dieser Datei wird die UI-Datei der MainWindow geladen. Die ui_pp_mdi.py dient außerdem dazu, dass dort die ganzen Trigger und Aktionen der MainWindow verwaltet werden. Dort steckt dann auch der besagte Code in einer Funktion. xarphus.py rühre ich weitestgehend nicht an. Es soll eine Art Index-Datei bleiben. Ich denke hier eher wie in VB6 oder auch PHP. Und deine Bedenken hatte ich auch. Also habe ich meinen Task-Manager geöffnet, und mein Programm mehrmals neugestartet. Die vorherigen Prozesse werden beendet und der neue Prozess entsteht. Also, ich habe am Ende nicht zig Mal das gleiche Programm. Die quit()-Methode verwende ich, weil ich in ui_pp_mdi.py setQuitOnLastWindowClosed (siehe Code) verwende. Ich muss mich also um das Schließen kümmern. Close() würde hier nicht reichen - soll ja auch nicht.

Code: Alles auswählen

app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
window = Mdi_Main()
sys.exit(app.exec_())