Try/Except funktioniert nicht

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
pyfon
User
Beiträge: 2
Registriert: Samstag 12. November 2022, 21:13

Moin,
ich habe ein Programm geschrieben, welches zwei URLs als Input nimmt, und sich von diesen URLs jeweils eine .ical-Datei holt. Wenn aus welchem Grund auch immer die angegebene URL fehlerhaft ist, möchte ich dies gerne mit Try/Except abfangen. Trotz Try und Except in der main Methode, wirft mir das Programm bei fehlerhaften URLS, bspw. "1" und "2", einen Fehler, welcher aus dem requests-Modul zu kommen scheint :


raise MissingSchema(
requests.exceptions.MissingSchema: Invalid URL '2': No scheme supplied. Perhaps you meant http://2?
raise MissingSchema(
requests.exceptions.MissingSchema: Invalid URL '1': No scheme supplied. Perhaps you meant http://1?


Eigentlich gehe ich davon aus, dass der gesamte Code innerhalb des Try in der Main-Datei dann dem Try unterliegt, egal ob die dort ausgeführten Methoden bzw. Klassen aus der Imports.py importiert werden. Warum kann ich dann den Fehler mit meinem Try/Except nicht abfangen?
Datei 1: main.py

Code: Alles auswählen

import requests
import Imports
import threading

try:
    url1 = input("Input URL No.1:")
    url2 = input("Input URL No.2:")
    t1 = Imports.myThread(url1)
    t2 = Imports.myThread(url2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    split_events1 = t1.value
    split_events2 = t2.value
    EventListObject = Imports.handleEvents(split_events1)
    EventListObject2 = Imports.handleEvents(split_events2)

    for day in EventListObject.listOfEvents:
        for day2 in EventListObject2.listOfEvents:
            if day.date == day2.date:
                if day.starttime == day2.starttime and day.endtime == day2.endtime:
                    print("Match: " + day.date)

except:
    print("Invalid Input.")
Datei 2: Imports.py

Code: Alles auswählen

import requests
import threading


class Eventday:
    def __init__(self, datee, starttime, endtime):
        self.date = datee
        self.starttime = starttime
        self.endtime = endtime

    def updateEndTime(self, newendtime):
        if newendtime > self.endtime:
            self.endtime = newendtime

    def updateStartTime(self, newstarttime):
        if newstarttime < self.starttime:
            self.starttime = newstarttime


class EventList:
    def __init__(self):
        self.listOfEvents = []

    def handle(self, datee, starttime, endtime):
        binResult = binarySearch(self.listOfEvents, datee)
        if binResult != -1:
            self.listOfEvents[binResult].updateStartTime(starttime)
            self.listOfEvents[binResult].updateEndTime(endtime)

        else:
            self.listOfEvents.append(Eventday(datee, starttime, endtime))


def handleURL(inputurl):
        cal_i = requests.get(inputurl).text
        split_overhead = cal_i.split("END:VTIMEZONE")
        split_events = split_overhead[1].split("BEGIN:VEVENT")
        return split_events


class myThread(threading.Thread):
    def __init__(self, url):
        threading.Thread.__init__(self)
        self.url = url
        self.value = None

    def run(self):
        self.value = handleURL(self.url)


def handleEvents(eventlist):
    eventlistobject = EventList()
    for event in eventlist:
        date = 0
        endt = 0
        stime = 0
        remote = False
        for line in event.splitlines():
            if "DTEND" in line:
                date = line.split(":")[1].split("T")[0]
                endt = line.split(":")[1].split("T")[1]
                # print(date)
            if "DTSTART" in line:
                stime = line.split(":")[1].split("T")[1]
            if "Raum Online" in line:
                remote = True
        if not remote and stime != 0:
            eventlistobject.handle(date, stime, endt)
    return eventlistobject


def binarySearch(list, value):
    low = 0
    high = len(list) - 1

    while low <= high:
        middle = (high + low) // 2
        if list[middle].date < value:
            low = middle + 1
        elif list[middle].date > value:
            high = middle - 1
        else:
            return middle
    return -1

Ich hoffe ihr könnt mir irgendwie weiterhelfen und ich übersehen einfach nur etwas, bin eher ein Anfänger :)
Falls noch weitere Informationen benötigt werden, gerne fragen.
Vielen Dank im Voraus
Benutzeravatar
sparrow
User
Beiträge: 4540
Registriert: Freitag 17. April 2009, 10:28

Konvention zur Namensgebung in Python: Namen werden klein_mit_unterstrich geschrieben. Ausgenommen sind Konstanten (KOMPLETT_GROSS) und Klassen (nicht deren Instanzen) (PascalCase). Das gilt auch für die Namen von Modulen.

Deine Exception tritt in einem Thread auf. Dann musst du sie auch dort behandeln. In deinem Haupthread kommt sie ja nie an.
ABER: Ich sehe überhaupt gar keine Notwendigkeit überhaupt Threads zu benutzen. Das macht die Sache nur unnötig komplex. Überhaupt ist der Code für das, was er tut übertrieben kompliziert.
Sirius3
User
Beiträge: 18276
Registriert: Sonntag 21. Oktober 2012, 17:20

Das except kannst Du sofort wieder löschen. Ein naktes `except´ benutzt man nicht. Fange nur die Fehler ab, die Du auch sinnvoll behandeln kannst.
Module, Variablen und Methoden schreibt man komplett klein, Klassen dagegen mit GroßemAnfangsbuchstaben.
Ein Modul `imports` ist relativ unsinnig. Im jetzigen Zustand des Programms ist eine Aufteilung in Module noch nichts sinnvoll, und dann sollten Module sprechende Namen haben.
`datee` hat einen Schreibfehler.
Der Code in `handleURL` ist falsch eingerückt. `inputurl` trägt das `input` nicht zum Verständnis bei und kann weg.
my ist eigentlich immer ein unsinniges Präfix.
Da in Python alles ein Objekt ist, sollte `eventlistobject` eigentlich nur `events` heißen.
endt oder stime, warum kürz Du beliebige Teile von Variablennamen mit nur einem Buchstaben ab?
Variablen sollten immer nur Werte eines Types haben, hier initialisierst Du aber mit einem Int und überschreibst dann mit einem String. Der Wert für "nicht vorhanden" ist None.

Eine Exception, die in einem Thread geworfen wird, wird natürlich nicht irgendwoanders im Hauptthread aufgefangen. `concurrent.futures` wären hier deutlich besser geeignet, anstatt so eine Funktionalität selbst nachzuprogrammieren. Da Du aber noch Anfänger bist, vergiss das mit den Threads fürs erste komplett, da das in diesem Fall eh nicht so viel bringt.
Benutzeravatar
__blackjack__
User
Beiträge: 14069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ergänzende Anmerkungen: Grunddatentypen sollten nicht in Namen stehen, denn die ändert man während der Entwicklung auch gerne mal und hat dann entweder falsche, irreführende Namen im Programm, oder muss das Programm durchgehen und überall die Namen anpassen. Zudem ist es auch unnötig einschränkend etwas `irgendwas_list` zu nennen wo letztlich nur drüber iteriert wird, wo also jedes passende Objekt verwendet werden könnte das iterierbar ist, und nicht nur Listen.

Bei `handleEvents()` ist es zudem noch mal extra verwirrend, dass es ein `eventlist` gibt und den Typen `EventList`, der Wert aber an `eventlistobject` gebunden wird. UnterstrichezwischenWortenwürdendieNamenauchetwaslesbarermachen.

`EventList` ist als Klasse auch ein bisschen fragwürdig. Das hat nur eine Liste als ”internen” Zustand, auf die aber von aussen zugegriffen wird. Im Grunde würde da also eine Liste und eine `handle()`-Funktion ausreichen. Wobei `handle()` hier auch extrem nichtssagend als Name ist. Was macht die Funktion denn? Das sollte sich irgendwie im Namen widerspiegeln.

Binärsuche gibt es bereits fertig im `bisect`-Modul in der Standardbibliothek, das muss man nicht selbst programmieren.

Ein Datentyp für Zeitspannen wäre eventuell sinnvoll.

Die `split()`-Aufrufe sehen nur mässig robust aus, weil ich mal vermute der Code geht da immer davon aus, dass es nur eine Trennstelle gibt. Das würde man `split()` besser als Argument mitgeben, oder `partition()` stattdessen verwenden.

Es würde vielleicht auch Sinn machen einen ordentlichen Parser zu verwenden. Es gibt fertige Bibliotheken für das iCal-Format. Das ist komplexer als man vielleicht denken könnte, wenn man sich nur ein paar Beispiele anschaut. insbesondere auch was Zeitangaben betrifft.

Man sollte die Antwort auf eine `requests`-Anfrage auf den Status prüfen, denn der `text` könnte auch der Inhalt einer Fehlermeldung sein. Wenn es okay ist eine Ausnahme auszulösen, haben `requests.Response`-Objekte eine praktische `raise_for_status()`-Methode.

Selbst wenn das nackte ``except`` das *alle* Ausnahmen behandelt, auch Tippfehler und ähnliches, funktionieren würde: Es macht wenig Sinn einfach "Invalid input" auszugeben. Erstens muss das ja gar nicht stimmen, weil es auch völlig andere Probleme geben kann die zu Ausnahmen führen, und zweitens ist es so unmöglich die Ursache zu finden, wenn die ganzen Informationen aus dem Traceback einfach so verworfen werden.

Ich persönlich verwende gerne die `loguru`-Bibliothek und dort `catch()` als Dekorator oder Kontextmanager mit ``with`` um Ausnahmen ausführlich zu protokollieren. Entweder in bunt auf der Konsole, oder in eine Datei.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
pyfon
User
Beiträge: 2
Registriert: Samstag 12. November 2022, 21:13

Hallo,
danke für eure Anregungen.
Diese habe ich versucht, zu berücksichtigen. Unter anderem habe ich Namen angepasst sowie die EventList-Klasse gelöscht und durch eine normale Liste ersetzt.

Ich benötige eine parallelisierte Ausführung der requests.get()-Methode, weil das Abrufen der Daten ca. 10 Sekunden dauert. Bei zwei abzurufenden URLs bräuchte das Programm dementsprechend 20s, parallel laufend nur 10s.
@Sirius3 hatte ja vorgeschlagen, concurrent.futures zu verwenden. Das habe ich auch gemacht, scheint mir auf den ersten Blick einfacher als die vorher vorhandenen Threads.

Um das ganze zu testen und halbwegs zu verstehen, habe ich mir ein kleines Programm geschrieben:

Code: Alles auswählen

from concurrent import futures
import requests


def get_data(url):
    i_cal = requests.get(url).text
    split_overhead = i_cal.split("END:VTIMEZONE")
    split_events = split_overhead[1].split("BEGIN:VEVENT")
    return split_events


url1 = "a"
url2 = "b"

with futures.ThreadPoolExecutor(max_workers=2) as e:
    thread1 = e.submit(get_data, url1)
    thread2 = e.submit(get_data, url2)

print("URL1", len(thread1.result()))
print("URL2", len(thread2.result()))


Das Programm funktioniert an sich auch. Jedoch wird mir immernoch nicht klar, wie ich mit den Exceptions umgehen soll. Ich habe nach euren Antworten verstanden, dass geworfene Exceptions in Threads im "Hauptprogramm" keine Wirkung haben. Wie kann ich das denn ändern?
Mein Ziel ist es konkret, dass sich das ganze Programm beendet, falls in einem der Threads eine Exception auftritt. Dann sollte noch kurz ausgegeben werden, dass das Programm aufgrund eines Fehlers mit der URL beendet wurde.
Kann mir dazu evtl. jemand beispielhaften Code zeigen oder die Exceptions oben einbauen?
Tut mir leid, dass ich so offensichtlich ahnungslos bin, aber ich versuche ja zu lernen :P

Vielen Danke euch schonmal
Sirius3
User
Beiträge: 18276
Registriert: Sonntag 21. Oktober 2012, 17:20

Deshalb ja der Hinweis auf concurrent.futures. Denn das propagiert ja auch Exceptions, welchen Rückgabewert sollte denn result() sonst liefern?
`e` ist für einen ThreadPool ein besonders bescheuerter Name. submit liefert ein Future zurück, keinen Thread, also auch ein schlechter Variablenname.
`map` ist noch einfacher:

Code: Alles auswählen

with futures.ThreadPoolExecutor(max_workers=2) as pool:
    events1, events2 = pool.map(get_data, [url1, url2])
Antworten