Neustart

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

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.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@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.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

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?
Das Leben ist wie ein Tennisball.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@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.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

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__).
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@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.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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.
Zuletzt geändert von snafu am Freitag 13. März 2015, 12:38, insgesamt 1-mal geändert.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@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.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Schau mal die exec*-Funktionen im Modul os an, ob die leisten, was Du suchst.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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?
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@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.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@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.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@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_())
Antworten