Pythonscript kann immer nur 1x ausgeführt werden

  • Hallo,

    ich bastle mir gerade einen Telegrambot mit Python. Der funktioniert auch. Man sendet einen Textbefehl an den Bot und der macht dann irgendwas (eine LED einschalten, ein Foto machen, etc...). Wichtig für meine Frage sind nur die beiden letzten Zeilen und die Funktion handle_text().

    Problem: Ich kann das Script aber immer nur genau 1x aufrufen. Wenn ich versuche, das Script in der gleichen Terminalinstanz ein zweites mal aufzurufen, bekomme ich folgenden Fehler im Terminal angezeigt:


    Der Fehler tritt erst auf, seit ich die picamera in das Skript eingabaut habe. Vorher konnte ich das Skript beliebig oft starten. Wahrscheinlich hängt es damit zusammen, dass die Funktion handle_text() in einem eigenen Thread abläuft und danach signal.pause() aufgerufen wird.


    Ich kann dieses Skript immer nur mit CTRL+Z beenden. Damit hat picamera wohl ein Problem. Um das Skript wieder starten zu können, muss ich ein neues Terminal öffnen.


    Ich hätte gerne folgende Lösung: Ich sende einen Textbefehl (z.B. "Ende") über meinen Telegramclient an den Bot. Der Bot erkennt den Befehl (wie die anderen im Beispiel auch) und veranlasst, dass das Skript sauber beendet wird.


    Ich hatte mir versuchsweise ein einfaches Script geschrieben, das einfach nur mit picamera ein Foto schiesst und sich dann wieder beendet. Das kann ich beliebig oft aufrufen. Nur in der Kombination mit signal.pause() und dem Thread klappt das nicht.


    Ich hatte auch schon versucht, die beiden letzten Zeilen in eine Whileschleife zu stecken (while weiter == 1): und die Variable "weiter" in der Funktion handle_text auf "0"

    zu ändern. Klappt aber nicht, weil die Funktion in einem eigenen Thread läuft und die Variable nicht kennt.


    Hat jemand eine Idee, wie man das Skript sauber beendet oder den picamera-Fehler abstellt?

    Gruß Bernie

  • Wahrscheinlich werden die Ressourcen durch picamera nicht freigegeben, wenn die Methode camera.close() nicht aufgerufen wird.



    Oder mit einem Context-Manager:

    Code
    if __name__ == "__main__":
    
        camera = PiCamera()
        camera.resolution = (512, 384)
    
        with PiCamera() as camera:
            camera.resolution = (512, 384)
            MessageLoop(bot, handle_text).run_as_thread() 
            signal.pause()



    Noch was. Die Funktion spiel ist etwas komisch implementiert.

    Anstelle dem Namen returnwert etwas zuzuweisen, könnte man dort auch direkt ein return "string" verwenden.




    Hier mal ein Beispiel mit nur einer Runde.



    In der Funktion foto ist ein return. Das braucht dort nicht zu sein, da jede Python-Funktion implizit ein None zurückliefert.


    Dann ist mir noch aufgefallen, dass du die alte Formatierungsmethode verwendest. Ist wahrscheinlich vom Quellcode, den du per copy&paste einfach so übernommen hast.


    Modernes Python bietet den format syntax an. Es gibt die format Methode bei str und bytes.


    Code
    fmt = "Hello {}"
    text = fmt.format("World")
    print(text)

    Oder mit format-strings

    Code
    world = "World"
    text = f"Hello {world}"
    print(text)


    Da kann man noch viel mehr machen, aber das wirst du am besten selbst nachlesen: https://docs.python.org/3/libr…ecification-mini-language

    Edited once, last by DeaD_EyE ().

  • Vielen Dank!


    Die gute Nachricht: camera.close() hilft.

    Ich habe das mal in die handle_text()-funktion eingetragen. Wenn camera.close() durchlaufen wird, kann ich anschließend das Skript mit CTRL+Z beenden wieder starten.

    Die schlechte Nachricht: Deine beiden Beispiele (try und der contextmanager) funktionieren bei mir nicht.


    Bei dem Try-beispiel kann ich das Skript starten aber except KeyboardInterrupt: wird anscheinend nicht durchlaufen. Zumindest wird die darin befindliche Funktion camera.close() nicht aufgerufen.


    Wenn ich den Context-Manager, so wie in deinem Beispiel gezeigt, implementiere, startet das Skript erst gar nicht. Da kriege ich schon beim ersten Start die Fehlermeldung:

    So habe ich ihn imlementiert (fast copy&paste ))

    Code
    ...
    if __name__ == "__main__":
    
      camera = PiCamera()
      camera.resolution = (512, 384)
    
      with PiCamera() as camera:
        camera.resolution = (512, 384)
        MessageLoop(bot, handle_text).run_as_thread() 
        signal.pause()


    Ansonsten: Ja richtig, ich lerne gerade Python und das Skript hier ist eine Spielwiese. Ich habe noch ein altes Raspberry/Pythonbuch als erste Anleitung verwendet und habe mir wohl auch nicht die allerneuesten Pythontutorials reingezogen. Das werde ich ändern. Danke für den Hinweis.

  • Einrückung des Codes generell mit 4 Leerzeichen. Alles andere führt zwangsweise zu Problemen beim Bearbeiten des Codes.

    Moderne Entwicklungsumgebungen rücken Python Code immer automatisch mit 4 Leerzeichen ein.

    Welchen Editor nutzt du?


    Du initialisierst PiCamera zweimal.

    Das erste Mal in Zeile 2, was beim ersten Mal funktioniert und danach in Zeile 7 mit dem Kontextmanager, was nicht mehr funktionieren kann.

    Also wird versucht dieselbe Kamera zweimal zu verwenden, was nicht geht.


    Um zu testen, ob die Exception KeyboardInterrupt auch wirklich abgefangen wird, kannst du dort in dem Block ja ein print("STRG+C") mit einfügen. Wenn das nicht ausgegben wird, geht die Exception irgendwo unter, was komisch ist.


    Der Kontextmanager ruft die Methode __enter__ des fertigen Objektes auf. Fast immer wird das Objekt selbst zurückgeliefert. Sobald der Block des Kontextmanagers verlassen wird, wird die Methode __exit__ aufgerufen. Falls es im Block des Kontextmanagers zu einer Exception kam, wird das der __exit__ Methode übergeben. Wenn die Methode ein True zurückliefert, wird die Exception unterdrückt. In den meisten Fällen braucht man das nicht und meistens implementiert man das auch nicht selbst. Zusätzlich schließt die __exit__ Methode meist irgendwas. Bei einem Dateiobjekt wird z.B. auch die close Methode aufgerufen. Moderne Bibliotheken haben den Kontextmanager meist schon sinnvoll implementiert. PiCamera auch.


    Ein großer Vorteil des Kontextmanagers ist die Garantie, dass die __exit__ Methode beim Verlassen des Blocks ausgeführt wird.



    Beim letzten Beispiel wird zuerst aus der Klasse Foo eine Instanz erzeugt: Foo().

    Die __enter__ Methode der Instanz von Foo wird durch den Kontextmanager aufgerufen und das zurückgelieferte Objekt wird dann f zugewiesen. Fast immer ist es die Instnz selbst. Deswegen gehen alle 3 gezeigten Beispiele. Beim ersten Beispiel wird durch den Kontextmanger nichts zugewiesen. Dort fehlt das as f.

  • Servus und danke für die Tipps!


    Ich bin Python-Neuling und komme eigentlich aus der C-Ecke (nicht C++). also aus der prozeduralen Programmiererei. Meine Beschäftigung mit Python ist in erster Linie ein Lockdown-Zeitvertreib. Ich bin Elektronikbastler und wechsle gerade von Wahtsapp zu Telegram. Und weil hier noch ein Raspi rumliegt, habe ich beschlossen, als Übungsaufgabe einen Telegrambot mit Python3 zu bauen, der über GPIO eine Schaltung steuert, die Kamera steuert und anderweitigen Unfug anstellt. Ist also alles nicht sooo wichtig und dient nur der Erkenntnisfindung :)


    Weil ich aus der C-Ecke komme und bisher nur ein altes Pythonbuch zum Lernen hatte, bin ich mit dem Handling von Klassen/Methoden/Vererbung etc. noch nicht vertraut. Da muss ich mich erst mal einlesen.


    Einrückung 4 Zeichen: War mir nicht klar. Ich verwende Geany und da waren 2 Zeichen Einrückung voreingestellt. Das habe ich unverändert übernommen. Kann ich aber gerne ändern.


    Du initialisierst PiCamera zweimal.

    Das erste Mal in Zeile 2, was beim ersten Mal funktioniert und danach in Zeile 7 mit dem Kontextmanager, was nicht mehr funktionieren kann.

    Also wird versucht dieselbe Kamera zweimal zu verwenden, was nicht geht.

    Das habe ich natürlich geändert und die Initialisierung oben auskommentiert. Ich habe das Script mittlerweile soweit, dass es funktioniert und auch mehrfach hintereinander aufgerufen werden kann. Sieht jetzt so aus:

    Die Initialisierung der Kamera, das Schießen des Fotos und das Schließen der Kamera findet also nur noch in der Funktion foto() in einem try-Block statt. Steht die Kamera nicht zu Verfügung, wird der Ausnahmeblock durchlaufen. Das funktioniert. Ist dieses Vorgehen auch sinnvoll?

    Quote


    Um zu testen, ob die Exception KeyboardInterrupt auch wirklich abgefangen wird, kannst du dort in dem Block ja ein print("STRG+C") mit einfügen. Wenn das nicht ausgegben wird, geht die Exception irgendwo unter, was komisch ist.

    Hab ich getestet. Der Text wird nicht ausgegeben. Warum weiß ich noch nicht...

  • Die Initialisierung der Kamera, das Schießen des Fotos und das Schließen der Kamera findet also nur noch in der Funktion foto() in einem try-Block statt. Steht die Kamera nicht zu Verfügung, wird der Ausnahmeblock durchlaufen. Das funktioniert. Ist dieses Vorgehen auch sinnvoll?

    Ja, kann man so machen. Schlecht ist nur, dass du jede Exception abfängst und nichts ausgibst.

    Es könnte ja z.B. auch sein, dass foto.jpg nicht geschrieben werden kann. Es würde dann nichts ausgegeben

    und der Wert 0 zurückgeliefert.


    Als Rückgabewerte würde ich boolean nutzen.

    Kleiner Hinweis, True und False sind von int vererbt.

    D.h. man kann True und False auch als integer behandeln, aber das nur so nebenbei erwähnt.


    Unten habe ich noch ein print mit eingefügt.

    Dann testest du welche Exception z.B. kommt, wenn du die Kamera nicht angeschlossen hast.

    Anstelle dann Exception abzufangen, fängst du z.B. PiCameraMMALError ab.


    Welche Exceptions picamera hat, steht hier: https://picamera.readthedocs.io/en/release-1.13/api_exc.html



    Hab ich getestet. Der Text wird nicht ausgegeben. Warum weiß ich noch nicht...

    Hm, wenn das nicht der Fall ist, wurde zuvor camera.close() auch nicht aufgerufen.

    Da du das jetzt in einer Funktion ausgelagert hast, ist das nun nicht mehr ganz der Fall.

    Eine Möglichkeit gibt es noch. Wenn das Programm beendet wird und die Funktion foto ausgeführt wird,

    dann wird die close Methode ggf. nicht aufgerufen. Wäre es hingegen in einem Kontextmanager,

    so würde dieser garantieren, dass die close Methode aufgerufen wird.


    In der Dokumentation wird Kontextmanager auch explizit erwähnt: https://picamera.readthedocs.i…mera.html#module-picamera


    Code
    with PiCamera() as camera:
        # do something with the camera
        pass



    PS: Deine Einrückung besteht immer noch aus 2 Leerzeichen. Am besten black verwenden, um den Code zu formatieren.