51. Loose Ends

Exceptions selbst definieren

Häufig will man “eigene Fehler” werfen können, um z.B. zu sagen: “Für dieses Wort existiert keine Ableitung”. Das geht so:

class NoDerivation(Exception):
  pass

def getDerivation(word):
  ...
  raise NoDerivation("Sigh")
  ...

Man beerbt also die eingebaute Klasse Exception.

Übrigens war es früher üblich, die Konstruktion der Exception-Instanz dem raise zu überlassen, was so aussah:

raise NoDerivation, "Sigh"

Damit konnte man auch allen möglichen anderen Krempel raisen (also Dinge, die nicht von Exception abgeleitet waren). Python unterstützt das noch heute, aber man sollte es nicht mehr tun.

Es ist oft sinnvoll, sich für ein eigenes Modul einen Satz von Exceptions zu definieren, die von einer gemeinsamen Exception erben:

class GenerException(Exception):
  pass

class NoDerivation(GenerException):
  pass

class EndlessRecursion(GenerException):
  pass

Vorteil dieses Verfahrens ist, dass Programme, die auf das Modul aufbauen, wahlweise alle Exceptions fangen können, die das Modul erzeugt (except GenerException: reagiert auf alle von GenerException abgeleiteten Exceptions) oder eben nur ganz bestimmte Fehler.

Short Circuit Evaluation

In Python (und in den meisten anderen Sprachen) werden Boole’sche Ausdrücke per Short Circuit Evaluation (“Kurzschlussauswertung”) berechnet. Das bedeutet, dass mit and oder or verknüpfte Ausdrücke nur berechnet werden, bis das Ergebnis feststeht. Ein Beispiel:

>>> if 1 and sys.stdout.write("Ha!\n"):
...     sys.stdout.write("Hu!\n")
...
Ha!
>>> if 1 or sys.stdout.write("Ha!\n"):
...     sys.stdout.write("Hu!\n")
...
Hu!

Im ersten Fall weiß Python nach der Eins noch nicht, ob der Ausdruck wahr ist – der zweite Teil der Konjunktion könnte ja falsch sein, und 1 and 0 ist eben falsch. Demnach wird das write ausgewertet, es gibt None zurück, die Gesamtbedingung ist falsch, also wird kein “Hu!” ausgegeben. Hätte es umgekehrt if 0 and geheißen, wäre bereits bei der Null klar gewesen, dass der Ausdruck falsch ist (“falsch und irgendwas” ist immer falsch) und das write wäre nicht ausgewertet und damit auch nicht ausgeführt worden.

Im zweiten Fall ist schon bei der 1 klar, dass die Bedingung erfüllt ist: “wahr oder irgendwas” ist immer wahr.

(Weiterführend:) In vielen Programmiersprachen basieren endlos viele Tricks auf diesem Verhalten, vor allem in perl und in der Bourne-Shell. Programme in diesen Sprachen strotzen vor Konstruktionen wie

mkdir("stuff") or die("Couldn't do something");

(Weiterführend:) Hintergrund ist, dass viele dieser Sprachen kaum über vernünftige Exception-Mechanismen verfügen und Fehlerbedingungen über den Rückgabewert einer Funktion mitgeteilt werden. Wenn nun die Funktion 0 zurückgegeben hat, muss der die zweite Hälfte des Ausdrucks ausgewertet werden, und das ist hier die Funktion die, die das Programm mit einer Fehlermeldung abbricht. Hat sie hingegen etwas ungleich Null zurückgegeben, muss der zweite Teil nicht ausgewertet werden, das Programm läuft weiter.

(Weiterführend:) Diese Sorte Krankheit ist in Python in der Regel überflüssig, weil wir Exceptions haben. Auch sonst sollte man wissen, dass es die Short Circuit Evaluation gibt, normalerweise aber nicht zu intensiv darüber nachdenken. Es gibt in Python nicht viele Fälle, in denen man sie aktiv verwenden will (wer unbedingt in funktionalen Programmen Selektion machen möchte, wird das brauchen).

Präzedenz

Die Präzedenztabelle gibt für alle Operatoren die Stärke ihrer Bindung, wobei ganz oben die am schwächsten bindenden Operatoren stehen.

So ist beispielsweise 5*6+b als (5*6)+b zu lesen, weil * in der Tabelle niedriger steht als +, also stärker bindet.

Weitere Beispiele:

4+5 and 4>x[2] ist (4+5) and (4>(x[2]))

7*+x.bla**2 ist 7*(+((x.bla)**2))

Normalerweise liest Python von links nach rechts.

Eigentlich kann man sich die Tabelle bei Python fast schenken, weil alles so ist “wie man es erwartet”. In anderen Sprachen ist das nicht immer so. In der Regel sollten Klammern gesetzt und nicht die Tabelle befragt werden, wenn irgendeine Unsicherheit besteht. Diese Fälle sind meist so bizarr, dass explizite Klammern ohnehin schlicht hilfreich sind.

Übungen zu diesem Abschnitt

Ihr solltet euch wenigstens an den rötlich unterlegten Aufgaben versuchen

(1)

Macht euch klar, wie das, was in der except-Klausel steht mit der Klassenhierarchie z.B. der oben angegebenen Exceptions interagiert. Schreibt also ein Programm, das mal eine Exception der Oberklasse wirft und eine Unterklasse fängt und umgekehrt.

(2)

Gibt es einen Unterschied zwischen

try:
  ...
except Exception:
  ...

und

try:
  ...
except:
  ...

(3)

Was gibt das folgende Programm für verschiedene Kommandozeilenargumente (für welche unterscheidet sich die Ausgabe?) aus – und vor allem: warum?

import sys

def printAndReturnInt(s):
  print s
  return int(s)

if (len(sys.argv)>1 and printAndReturnInt(sys.argv[1])
  ) and (len(sys.argv)>2 or printAndReturnInt(0)
  ) and (printAndReturnInt(sys.argv[2])):
  print not int(sys.argv[2]) or 0

(4)

Klammert die folgenden Ausdrücke vollständig (d.h. sie sollen nach der Klammerung das gleiche Ergebnis liefern wie nach der Präzendenztabelle ohne Klammern).

x+y**2
a.attr+-5
f(a+b) is not a<b
lambda a, b: not a or b.attr[4]


Markus Demleitner

Copyright Notice