<jupyter_start><jupyter_text>Skriptsprachen Sommersemester 2021Prof. Dr. <NAME>  AusnahmebehandlungBei der Softwareentwicklung und leider auch beim Produktiveinsatz von Software, kann es zu Fehlern im Programmablauf kommen. Die Ursachen von Fehlern können z.B. unerwartete Benutzereingaben oder ein mangelhafter Programmentwurf sein. Auch die Auswirkungen der Fehler können sehr unterschiedlich sein. Manche Fehler sind unkritisch und können vom Programm ignoriert werden. Andere Fehler sind kritisch für den Fortlauf des Programms und benötigen zwingend eine Fehlerbehandlung, damit das Programm nicht unkontrolliert beendet wird.Die meisten modernen Programmiersprachen ermöglichen eine _Fehler-_, bzw. _Ausnahmebehandlung_ um unerwartete Programmzustände zur Laufzeit des Programms abzufangen und gegebenenfalls aktiv darauf zu reagieren. Besteht die Gefahr, dass ein bestimmter Teil des Programmcodes zu einem Ausnahmefall führen könnte, so kann der entsprechende Code-Abschnitt in einen _try_-Block eingebettet werden. Falls es innerhalb dieses Blockes zu einer Ausnahme kommt, so springt der Kontrollfluss in einen folgenden _except_ (in C++ oder Java, _catch_) Block, in dem auf die Ausnahme reagiert werden kann. Es ist möglich, verschiedene Fehlerarten zu unterscheiden und, je nach Fehler-Typ, zu reagieren.In Python ist es auch möglich, im Programm aktiv Ausnahmen zu erzeugen. Dies können eingebaute _Exceptions_ sein, es lassen sich aber auch eigene Ausnahme-Klassen definieren. Um mit der Ausnahmebehandlung zu experimentieren, stellen wir zunächst die Jupyter Umgebung so ein, dass möglichst umfangreiche Ausgaben im Fehlerfall erzeugt werden. `%xmode` ist ein internes ipython Kommando mit dem der Ausgabemodus gesetzt und abgefragt werden kann.<jupyter_code>%xmode Verbose<jupyter_output><empty_output><jupyter_text>Fragt man im Interpreter eine unbekannte Referenz ab, so erzeugt dies eine _NameError_ Ausnahme.<jupyter_code>a_name<jupyter_output><empty_output><jupyter_text>Ausnahmen abfangenUm diese Ausnahme abzufangen, kann man die Anweisung in einen _try_-Block einbetten und im folgenden _except_-Block auf die Ausnahme reagieren. Im folgenden Beispiel fangen wir die _NameError_ Ausnahme ab und reagieren mit einer print Anweisung. Damit wird der Fehler _behandelt_ und das Programm nach verlassen des Blocks normal fortgesetzt.<jupyter_code>try:
    a_name
except NameError:
    print("Es liegt ein Bezeichnerfehler vor")<jupyter_output><empty_output><jupyter_text>`NameError` ist nichts weiter als eine spezielle Klasse von Ausnahmen. Die Basisklasse aller Ausnahmen ist die Klasse `BaseException`. Das Attribut `_bases__`  liefert für eine Klasse ein Tupel mit allen Basisklassen zurück. Angewendet auf die Klasse `NameError` sehen wir, dass `NameError` von `Exception` erbt, und diese Klasse wiederum von `BaseException`.<jupyter_code>print(NameError.__bases__)
print(NameError.__bases__[0])
print(NameError.__bases__[0].__bases__[0])<jupyter_output><empty_output><jupyter_text>Wenn zur Laufzeit eine bestimmte Ausnahme auftritt, wird ein Objekt der entsprechenden Fehlerklasse erzeugt. Diesem Objekt kann über das Schlüsselwort `as` ein Bezeichner zugewiesen werden, über den im Folgenden auf das Exception-Objekt zugegriffen werden kann.<jupyter_code>try:
    a_name
except NameError as e:
    print(e)<jupyter_output><empty_output><jupyter_text>Bisher haben wir in den try-Blöcken einen _NameError_ erwartet und auch nur Ausnahmen dieses Typs abgefangen. Stimmt der angegebene Typ im _except_-Block nicht mit der konkreten Ausnahme zur Laufzeit überein, wird vom Interpreter ein Fehler erzeugt.<jupyter_code>try:
    open("KeinFile")
except NameError as e:
    print(e)<jupyter_output><empty_output><jupyter_text>Um dies zu umgehen, kann man über generellere Basisklassen Ausnahmen verschiedenen Typs abfangen. Wird kein Exception-Typ angegeben, werden alle Ausnahmen (`BaseException`) abgefangen.<jupyter_code>try:
    #a_name
    open("KeinFile")
except Exception as e:
    print(e)
    print(e.__class__.__bases__)
    print(e.__class__.__bases__[0].__bases__)<jupyter_output><empty_output><jupyter_text>Man kann auf mehrere Ausnahme-Typen getrennt reagieren indem man mehrere `except ` Blöcke definiert. Tritt keine Ausnahme auf, so wird in einen optionalen `else`-Block gesprungen. Verwendet man zusätzlich einen ebenfalls optionalen `finally`-Block, so wird der eingeschlossene Code in jedem Fall am Ende des kompletten try-Konstrukts ausgeführt.<jupyter_code>try:
    a
    #open("KeinFile")
    print("OK")
except NameError:
    print("Es liegt ein Bezeichnerfehler vor")
except:
    print("Es liegt irgend ein Fehler vor")
else:
    print("Es liegt kein Fehler vor")
finally:
    print("Das war's")<jupyter_output><empty_output><jupyter_text>Callstack Wird eine Ausnahme in einer Funktion (unterhalb des _main_ Moduls) ausgelöst, so gibt der Interpreter die komplette Funktionshierarchie zur Laufzeit (den sogenannten _Callstack_) vom _main_ Modul bis hin zur Funktion, in der die Ausnahme ausgelöst wird, aus. Dies ist besonders hilfreich, da eine Ausnahme an jeder Stelle des Callstacks abgefangen und behandelt werden kann.<jupyter_code>def a(): b()
def b(): c()
def c(): d()
def d(): x
a()<jupyter_output><empty_output><jupyter_text>Tritt zur Laufzeit eine Ausnahme auf, so wird sie zuerst durch den Callstack "nach oben" hoch gereicht. Erst, wenn die exception beim _main_ Modul ankommt, wird der eigentliche Fehler erzeugt. Wird der _NameError_ im obigen Beispiel z.B. in der Funktion `a()` abgefangen, so wird keine Fehlerausgabe erzeugt, obwohl der Fehler seine Ursache in einer tiefer geschachtelten Funktion hat.<jupyter_code>def a():
    try:
        b()
    except NameError:
        print("NameError in a() abgefangen")
def b(): c()
def c(): d()
def d(): x
a()<jupyter_output><empty_output><jupyter_text>Werfen einer ExceptionNeben den eingebauten Ausnahmefällen ist es auch möglich, Ausnahmen im eigenen Programmcode auszulösen. Mit der `raise` Anweisung kann eine neue Instanz eines Exception-Typs erzeugt und ausgelöst werden (das Auslösen von Ausnahmefällen nennt man auch "werfen").<jupyter_code>def myprint(s):
    if (s.lower()=="hello world"):
        raise ValueError("Nicht schon wieder!")
    else:
        print(s)

myprint("Ein Test")
myprint("Hello World")<jupyter_output><empty_output><jupyter_text>**Aufgabe 1****Implementieren Sie die folgende Klasse `myarray` so aus, dass die Listen `oeffentlich` und `privat` zusammengefasst in einem privaten Attribut von Typ `list` gespeichert werden.**- **Schreibzugriffe auf den "privaten Bereich" dieser Liste sollen einen `IndexError` auslösen.**- **Lesezugriffe sollen auf den gesamten Bereich möglich sein.**- **Implementieren Sie die `__str__()` Methode um die Liste geeignet auszugeben.** - **Testen Sie Ihre Implementierung.**- **Um einen komfortablen Zugriff auf die Listen-Elemente zu ermöglichen, können Sie die [__getitem__()](https://docs.python.org/3/reference/datamodel.htmlobject.__getitem__) und [__setitem__()](https://docs.python.org/3/reference/datamodel.htmlobject.__setitem__) Magic-Methods implementieren.**<jupyter_code>class myarray():
    # YOUR CODE HERE
    raise NotImplementedError()
            
#a = myarray()
#print(a)
#a[1] = '?'
#print(a)
#a[8] = '?'
assert '__len__' in dir(myarray()), 'implement __len__() method for array length.'
assert '__getitem__' in dir(myarray()), ' implement the getitem () magic methods.'
assert '__setitem__' in dir(myarray()), 'implement the setitem () magic methods.'

test = myarray()
try:
    for i in range(len(test)): test[i] = '*'
except IndexError:
    assert i>2, 'IndexError should only be raised for elements of the privat list!'
else:
    assert False, 'IndexError should be raised for elements of the privat list!'
    
assert test.__str__().count('*') > 2, 'implement the __str__() magic methods.'<jupyter_output><empty_output><jupyter_text>Eigene Exceptions definieren Es können auch eigene Ausnahmen definiert werden<jupyter_code>class MyException(Exception):
    def __init__(self,x):
        self.X = x
    def __str__(self):
        return ("Dies ist eine selbst-definierte exception mit dem Attribut: %s." % self.X)
    
raise MyException("Test")<jupyter_output><empty_output><jupyter_text>**Aufgabe 2****Entwickeln Sie eine Klasse die einen positiven Integer Wert als Attribut speichert. Überprüfen Sie im Konstruktor, ob der Wert der übergebenen Zahl negativ ist. Ist dies der Fall, so werfen Sie die von Ihnen zu definierende _NegativeValueException_. Testen Sie Ihre Implementierung.**<jupyter_code>class PosInt():
    # YOUR CODE HERE
    raise NotImplementedError()
NegativeValueException.__base__
try:
    assert NegativeValueException.__base__ == Exception, 'the error defined must inheret Exception'
    raise NegativeValueException(1)
except NameError:
    print('define the NegativeValueException class!')
    raise
except NegativeValueException:
    pass
    
test = PosInt(1)
try:
    test = PosInt(-1)
except NegativeValueException:
    pass
else:
    print('Error should be thrown for negative values!')
a = PosInt(1)
b = PosInt(-2)<jupyter_output><empty_output><jupyter_text>Erneutes Werfen einer AusnahmeIn einigen Fällen ist es sinnvoll, auf eine Ausnahme zu reagieren, sie dann aber trotzdem weiterzureichen. Dazu kann im `except`-Block eine `raise` benutzt werden, die<jupyter_code>class VariableGibtsNichtError(NameError):
    def __str__(self):
        return ("Diese Variable gibt es nicht!")
    
def a():
    try:
        b()
    except NameError:
        print("NameError in a() aufgetreten")
        ### Option 1: Ausnahme weiterleiten
        raise
        ### Option 2: Neue Ausnahme erzeugen
        #raise NameError
        ### Option 3: Neue Ausnahme in einem leeren 
        #raise NameError from None
        ### Option 3: Neue Ausnahme in bestehenden Kontext erzeugen 
        #raise VariableGibtsNichtError from NameError
def b(): c()
def c(): d()
def d(): x
a()<jupyter_output><empty_output><jupyter_text>AssertionsAssertions sind hilfreich, wenn man sicherstellen möchte, dass bestimmte Bedingungen an bestimmten Stellen innerhalb des Programms. Über das Schlüselwort `assert` kann eine Bedingung angegeben werden, die zur Laufzeit überprüft wird. Ist die Bedingung nicht erfüllt, so wird eine Ausnahme ausgelöst.Assertions bieten eine kompakte Möglichkeit, um im Programmcode (Sonder-)Zustände zu überprüfen, die zwar semantisch korrekt sind, aber im Verlauf des Programms möglicherweise zu einem Fehler führen könnten. Tut man dies nicht, muss im Fehlerfall ggf. sehr langwierig nach der Fehlerursache gesucht werden. Daher ist es sinnvoll, diese "problematischen Zustände" von vornherein auszuschließen.<jupyter_code>x = int(input())
assert x>0
assert x<=365, "Achtung, diese Zahl könnte falsch sein"
print("%d Tage sind ein %d-tel des Jahres" % (x, 356//x))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07681040007323996, 'fraction_numerical': 0.0029295980957612378, 'mean_word_length': 5.418331374853114, 'pattern_counts': {'":': 0, '<': 67, '<?xml version=': 0, '>': 69, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '16', 'dir': 'jupyter-structured-clean-dedup', 'id': '602295', 'license': 'MIT', 'n_tokens_mistral': 3565, 'n_tokens_neox': 3630, 'n_words': 1198, 'path': 'u6/ex13_Ausnahmebehandlung.ipynb', 'repo_name': 'fh-swf-hgi/skriptsprachen-ex', 'size': '10931'}