try: except: else: liefert Variable nicht zurück

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
ste87
User
Beiträge: 4
Registriert: Donnerstag 13. Juli 2023, 19:21

Liebe Python-Gemeinde,

ich habe hier ein Python-Problem, für das ich keine Lösung und auch kein work-around finde. Das Programm wird auf einem RPi 4, Model B Rev1.4 ausgeführt, Python Version ist 3.11.4.

Hintergrund: In dieser Anwendung schaltet das RPi Ausgänge über ein externes MCP23017, Verbindung über I²C, verwendete Bibliothek adafruit_mcp230xx.mcp23017.
Einer der Ausgänge ist ein Leistungschütz. Sporadisch gibt es Fehler in der Kommunikation über I²C, offenbar durch elektromagnetische Störungen. Entstörmaßnahmen (Varistor pp) sind durchgeführt.
Eine einfache und zuverlässige Behebung des Problems bietet Pythons try: except: else:

def PA1_ein():

try:
PA1.value = True # setze Ausgang PA1
except:
time.sleep(0.3) # im Störungsfall warte 300ms und versuche es erneut
PA1_ein()
else:
return # im Erfolgsfall zurück zur aufrufenden Routine


Nun möchte man vielleicht wissen, wie oft solche Störungen aufgetaucht sind und wie lange sie gedauert haben. Ich habe daher einen Fehlerzähler k eingeführt, der sich aber leider nicht so verhält, wie ich es erwarte: Der Zähler wird nämlich nicht an das aufrufende Programm zurückgeliefert, stattdessen ein None.

Zur Erläuterung ein Schnipsel des aufrufenden Programms:

k = 55

print("vor PA1_ein", k) # Zähler ausgeben
k = Bibl.PA1_ein(k) # PA1 einschalten
print("nach PA1_ein", k, type(k)) # Zähler ausgeben


und ein Schnipsel des ausführenden Programms:
def Störung(k):
time.sleep(0.3) # Warte
print(bcolors.RT + "Störung" + bcolors.RESET, k) # Ausgabe in rot
k += 1 # Zähler inkrementieren
print("in Störung", k)
return k

def PA1_ein(k):

print("vor try", k)
try: # setze Ausgang auf True
PA1.value = True
print("in try", k)
except:
k = Störung(k) # Störung erkannt, Modul Störung aufrufen
print("in except", k) # erneut versuchen
PA1_ein(k)
else:
print("in else", k, type(k))
return k # im Erfolgsfall zurück mit Zählerstand

Das Verhalten des Fehlerzählers k bei Störung ist hier dargestellt:

vor PA1_ein 55 # vor Aufruf des Moduls ist der Zähler 55 (voreingestellt)
vor try 55 # im Unterprogramm, vor try: ist der Zähler 55
Störung 55 # Ausgabe im Modul Störung, Zähler 55
in Störung 56 # Ausgabe im Modul Störung, nach Inkrementierung ist der Zähler 56
in except 56 # zurück im Unterprogramm, except: Zähler 56
vor try 56 # erneuter Aufruf des Unterprogramms: Zähler 56
Störung 56 # Störung liegt noch an: Ausgabe im Modul Störung, Zähler 56
in Störung 57 # Ausgabe im Modul Störung, nach Inkrementierung ist der Zähler nun 57
vor try 57 # im Unterprogramm, vor try: ist der Zähler 57
Störung 57 # Störung liegt noch an: Ausgabe im Modul Störung, Zähler 57
in Störung 58 # Ausgabe im Modul Störung, nach Inkrementierung ist der Zähler 58
in except 58 # zurück im Unterprogramm, except: Zähler 58
vor try 58 # erneuter Aufruf des Unterprogramms: Zähler 58
in try 58 # Störung liegt nicht mehr an, try ist erfolgreich, Zähler 58
in else 58 <class 'int'> # in else wird das Unterprogramm verlassen, Zähler 58
nach PA1_ein None <class 'NoneType'> # nach Rückkehr ins aufrufende Programm ist der Zähler None

Für eine Erläuterung des Fehlverhaltens bin ich sehr dankbar.

Beste Grüße
Peter
Benutzeravatar
__blackjack__
User
Beiträge: 14056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ste87: Man sieht hier ja die wichtige Einrückung nicht, also ungeprüft die naheliegende Vermutung: Nicht alle Zweige in der Funktion geben den Wert zurück.

Letztlich aber auch völlig egal, denn für eine einfache Wiederholung verwendet man keinen rekursiven Aufruf sondern eine Schleife. Und man verwendet kein nacktes ``except`` ohne konkrete Ausnahme(n) die man dort erwartet und sinnvoll so behandeln kann. Mach mal im ``try``-Block einen Tippfehler und das wird in einer Ausnahme enden weil man nicht beliebig oft verschachtelte Aufrufe machen kann.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
ste87
User
Beiträge: 4
Registriert: Donnerstag 13. Juli 2023, 19:21

Danke für die rasche Antwort, blackjack!
Die auftretenden Fehler sind beliebig (elektromagnetische Störung der I²C-Kommunikation), Ausnahmen können daher nicht vorab definiert werden. Die Störung tritt vielleicht einmal je Stunde auf.
Nach kurzer Zeit ist die Störung verschwunden (im Beispiel 3 * 300ms), das System arbeitet normal weiter - wie auch ohne die Verwendung des Fehlerzählers.
Mich interessiert der Grund, aus dem die Variable k im else:-Block einen vernünftigen Wert hat, beim aufrufenden Programm aber Null ankommt.
Bester Grüße
Peter

Schnipsel mit Einrückungen:

def Störung(k):
time.sleep(0.3)
print(bcolors.RT + "Störung" + bcolors.RESET, k)
k += 1
print("in Störung", k)
return k

def PA1_ein(k):

print("vor try", k)
try:
PA1.value = True
print("in try", k)
except:
k = Störung(k)
print("in except", k)
PA1_ein(k)
else:
print("in else", k, type(k))
return k
ste87
User
Beiträge: 4
Registriert: Donnerstag 13. Juli 2023, 19:21

Der Editor schluckt die Einrückungen. Welche Lösung gibt's? Screenshot?
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

Bitte setzte deinen Code in Code-Tags. Wie du sicher bemerkst, geht sonst die Einrückung verloren.
Die Code-Tags werden eingefügt, wenn du den </>-Button im vollständigen Editor dückst.

Die Fehler, die auftreten, sind hoffentlich nicht beliebig sondern von einem bestimmten Typ. Und eben nur dieser Typ darf abgefangen werden. Jeglichen auftretenden Fehler abzufangen verhindert vernünftiges debuggen.

Ansosnten hat BlackJack dir die Antwort bereits gegeben: Die Lösung ist, keine Rekursion sondern eine Schleife zu verwenden.
Der Aufruf von PA1_ein() (schlechter Name btw.) innerhalb von PA1_ein ist falsch und gehört da nicht hin.
Benutzeravatar
__blackjack__
User
Beiträge: 14056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ste87: ``except:`` behandelt *jede* Ausnahme. Auch wenn man sich im ``try``-Block bei einem Namen vertippt hat, oder nicht mehr genug Speicher vorhanden ist weil irgendein anderer Prozess Amok läuft, und sogar die Ausnahme die Auftritt wenn man zu viele rekursive Aufrufe gemacht hat. *Den* dann mit einem rekursiven Aufruf behandeln zu wollen ist fast schon ein wenig ironisch. Und so eine Verbindung kann auch mal komplett abbrechen. Wenn irgend etwas mit dem Kabel ist, oder mit dem Anschluss oder dem Sensor oder…

Und der Fehler ist tatsächlich das der Code `k` nicht zurück gibt. Das passiert ja nur im ``else``, aber der Zweig muss ja nicht genommen werden. Wenn der nicht genommen wird, dann greift das implizite ``return None`` am Ende jeder Funktion/Methode. Aber wie schon gesagt: man verwendet keine Rekursion als Ersatz für eine simple Schleife. Das kann man in Programmiersprachen machen die „tail call optimisation“ garantieren. Ansonsten ist das ein Programmierfehler.

Sonstige Anmerkungen: Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Namen sollten weder kryptische Abkürzungen enthalten noch nummeriert werden. Bei `PA1*` will der Leser doch eigentlich wissen was das bedeutet, und nicht raten müssen. Und `k` sagt wohl kaum jemanden so auf Anhieb, dass das ein Störungszähler ist.

Funktions- und Methodennamen beschreiben üblicherweise die Tätigkeit die sie durchführen, damit der Leser weiss was da gemacht wird, und um die Funktion/Methode leicht(er) von eher passiven Werten unterscheiden zu können. `Störung()` beschreibt keine Tätigkeit.

Funktionen und Methoden bekommen alles was sie ausser Konstanten benötigen als Argument(e) übergeben. `PA1_ein()` greift einfach ”magisch” auf `PA1` zu, was offensichtlich keine Konstante ist.

Edit: Ungetestet:

Code: Alles auswählen

def was_auch_immer_einschalten(was_auch_immer, stoerungszaehler):
    print("vor try", stoerungszaehler)
    while True:
        try:
            was_auch_immer.value = True
            return stoerungszaehler
        except IOError:
            time.sleep(0.3)
            stoerungszaehler+= 1
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
ste87
User
Beiträge: 4
Registriert: Donnerstag 13. Juli 2023, 19:21

Das ist eine wirklich gute Idee, blackjack. Vielen Dank für Ihre Unterstützung!
Beste Grüße
Peter
Antworten