Previous Page

Up One Level

Next Page

Python Tutorial

Contents

Index

Vorige: 7. Input and Output Omhoog: Python Tutorial Volgende: 9. Classes
Onderdelen
  • 8.1 Syntax errors
  • 8.2 Exceptions
  • 8.3 Het afhandelen van exceptions
  • 8.4 Het genereren van exceptions
  • 8.5 User-defined exceptions
  • 8.6 Het definiëren van opruimacties


8. Fouten en exceptions

Tot nu toe zijn foutmeldingen alleen zijdelings genoemd, maar als je de voorbeelden hebt uitgeprobeerd, heb je er wellicht al een paar gezien. Er zijn (minstens) twee afzonderlijke typen fouten (errors): syntax errors en exceptions.


8.1 Syntax errors

Syntax errors, ook wel parserfouten genoemd, zijn misschien wel de meeste gebruikelijke klacht die je krijgt als je Python nog aan het leren bent:

>>> while True print 'Hallo wereld'
  File "<stdin>", line 1, in ?
    while True print 'Hallo wereld'
                   ^
SyntaxError: invalid syntax

De parser herhaalt de regel die in overtreding is, en zet een klein (pijltje) onder de regel, dat wijst naar het eerste punt binnen de regel waar de fout opgemerkt werd. De fout wordt veroorzaakt door (of in ieder geval opgemerkt bij) het token wat voorafgaat aan het pijltje: in het voorbeeld wordt de fout opgemerkt bij het keyword print, aangezien de dubbele punt (":") die hieraan zou moeten voorafgaan, ontbreekt. De bestandsnaam en het regelnummer worden afgedrukt, zodat je weet waar je moet zoeken, in het geval dat de invoer uit een script werd gelezen.


8.2 Exceptions

Zelfs als een statement of expressie syntactisch correct is, kan het nog steeds een fout veroorzaken als geprobeerd wordt het uit te voeren. Fouten die opgemerkt worden tijdens het uitvoeren van het programma, worden exceptions genoemd, en hoeven niet altijd fataal te zijn: je zult hierna zien hoe je ze kunt opvangen en afhandelen. De meeste exceptions worden echter niet binnen programma's afgehandeld, en resulteren in foutmeldingen zoals hieronder:

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ZeroDivisionError: integer division or modulo by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: cannot concatenate 'str' and 'int' objects

De laatste regel van de foutmelding geeft een indicatie van wat er misging. Er zijn verschillende types exceptions, en het type wordt afgedrukt als onderdeel van de melding: in het voorbeeld zie je de types ZeroDivisionError, NameError en TypeError. De string die afgedrukt wordt voor het exceptiontype is de naam van de ingebouwde exception die is opgetreden. Dit geldt voor alle ingebouwde exceptions, maar niet noodzakelijk voor user-defined exceptions (hoewel het wel een bruikbare conventie is). Namen van standaardexceptions zijn ingebouwde identifiers (en geen gereserveerde keywords).

De rest van de regel is een meer gedetailleerde omschrijving van de opgetreden fout; de interpretatie en betekenis hiervan zijn afhankelijk van het type exception.

Het eerste deel van de foutmelding toont de context waarin de exception is opgetreden, in de vorm van een stack trace. In het algemeen vind je hier een stack backtrace met enkele regels uit de broncode; regels die ingelezen zijn vanaf stdin worden hier echter niet getoond.

In de Python Library Reference vindt je een lijst van ingebouwde exceptions en hun betekenis.


8.3 Het afhandelen van exceptions

Je kunt zelf selecteren welke exceptions je je programma wilt laten afhandelen. In het volgende voorbeeld zie je een programma dat de gebruiker blijft vragen een getal in te voeren, totdat een geldige integer ingevoerd wordt. De gebruiker kan het programma ook onderbreken (met Control-C of wat er maar door het besturingssysteem ondersteund wordt); merk op dat in dat geval een KeyboardInterrupt exception gegenereerd wordt, ten teken dat de interruptie door de gebruiker veroorzaakt is.

>>> while True:
...     try:
...         x = int(raw_input("Voer een getal in: "))
...         break
...     except ValueError:
...         print "O jee! Dat was geen geldig getal.  Probeer het nog eens..."
...

Het try-statement werkt als volgt.

  • Eerst wordt de try-clause (de statements tussen de keywords try en except) uitgevoerd.

  • Als er geen exception optreedt, wordt de except-clause overgeslagen, en wordt het try-statement verder uitgevoerd.

  • Als er wel een exception optreedt bij de uitvoering van de try-clause, wordt de rest van de clause overgeslagen. Als het type van de exception overeenkomt met de exception die genoemd wordt achter het except-keyword, wordt de rest van de try-clause overgeslagen, en wordt de except-clause uitgevoerd. Vervolgens wordt de draad van de programmarun weer opgepakt na het try-statement.

  • Als er een exception optreedt die niet genoemd wordt in de except-clause, wordt de exception doorgegeven aan een eventueel omvattend try-statement; als er geen handler voor de exception gevonden wordt, spreken we van een niet-afgehandelde exception (“unhandled exception”); de uitvoering van het programma wordt dan afgebroken met een foutmelding zoals hierboven getoond.

Een try-statement kan meer dan één except-clause hebben, zodat verschillende handlers voor verschillende exceptiontypes gespecificeerd kunnen worden. Ongeacht het aantal gespecificeerde handlers, wordt er altijd hooguit één uitgevoerd. Handlers handelen alleen exceptions af die optreden in de corresponderende try-clause, dus geen exceptions die optreden in andere handlers van hetzelfde try-statement. Een except-clause kan meerdere exceptions benoemen, de exceptiontypes worden dan in een lijst met haakjes gezet; bijvoorbeeld:

... except (RuntimeError, TypeError, NameError):
...     pass

In de laatste except-clause mogen de namen van de af te handelen exceptions worden weggelaten, zodat deze als een soort default handler kan dienen. Wees hier zeer voorzichtig mee! Met deze aanpak blijven echte programmeerfouten gemakkelijk verborgen. De default handler kan ook gebruikt worden om een foutmelding af te drukken en de exception vervolgens opnieuw te genereren (re-raise). Op die manier wordt degene die de code heeft aangeroepen, in staat gesteld om de exception ook zelf af te handelen:

import sys

try:
    f = open('mijnfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError, (errno, strerror):
    print "I/O fout(%s): %s" % (errno, strerror)
except ValueError:
    print "Kon data niet naar integer converteren."
except:
    print "Onverwachte fout:", sys.exc_info()[0]
    raise

Het try ... except-statement heeft een optionele else-clause, die, indien aanwezig, na alle except-clauses moet komen. De else-clause is nuttig om code in op te nemen die moet worden uitgevoerd als de try-clause geen exception oplevert. Bijvoorbeeld:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print 'Kan', arg, 'niet openen'
    else:
        print arg, 'bevat', len(f.readlines()), 'regels'
        f.close()

Het is beter om de else-clause te gebruiken, dan om de betreffende code toe te voegen aan de try-clause; je voorkomt daarmee dat je per ongeluk een exception afvangt in de toegevoegde code, in plaats van de code die door het try ... except-statement werd bewaakt.

Exceptions kunnen met een waarde geassocieerd worden, we noemen dat het argument van de exception. De aanwezigheid en het type van het argument zijn afhankelijk van het type exception.

In de except-clause kan achter de naam van de exception (of de lijst met namen van exceptions) een variabele gespecificeerd worden. Deze variabele wordt verbonden aan een exception instance, waarbij de argumenten opgeslagen zijn in instance.args. Voor het gemak definieert de exception instance __getitem__ en __str__ zodat de argumenten rechtstreeks benaderd of afgedrukt kunnen worden zonder dat je moet refereren aan .args.

>>> try:
...    raise Exception('spam', 'eggs')
... except Exception, inst:
...    print type(inst)     # de exception instance
...    print inst.args      # argumenten in .args
...    print inst           # via __str__ kunnen args rechtstreeks afgedrukt worden
...    x, y = inst          # via __getitem__ kunnen args rechtstreeks uitgepakt worden
...    print 'x =', x
...    print 'y =', y
...
<type 'instance'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

Als een exception een argument heeft, wordt dat als het laatste onderdeel van de foutmelding voor niet-afgehandelde exceptions afgedrukt.

Exception handlers handelen exceptions af die rechtstreeks binnen de try-clause optreden, maar ook exceptions die (indirect) aangeroepen worden in de try-clause. Bijvoorbeeld:

>>> def dit_loopt_stuk():
...     x = 1/0
... 
>>> try:
...     dit_loopt_stuk()
... except ZeroDivisionError, detail:
...     print 'Afhandeling van run-time error:', detail
... 
Afhandeling van run-time error: integer division or modulo


8.4 Het genereren van exceptions

Met het raise-statement kan de programmeur een vooraf gespecificeerde exception veroorzaken. Bijvoorbeeld:

>>> raise NameError, 'HalloDaar'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: HalloDaar

Als eerste argument voor raise wordt de exception genoemd die gegenereerd (raised) moet worden. Optioneel kun je een exception argument als tweede argument meegeven.

Als het nodig is om te bepalen of er een exception is gegenereerd, maar je de exception zelf niet hoeft af te handelen, kun je de volgende, simpelere vorm van het raise-statement gebruiken om de exception opnieuw te genereren:

>>> try:
...     raise NameError, 'HalloDaar'
... except NameError:
...     print 'Exception gesignaleerd!'
...     raise
...
Exception gesignaleerd!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HalloDaar


8.5 User-defined exceptions

Programma's kunnen ook zelf nieuwe exception classes definiëren. Normaal gesproken worden exceptions afgeleid (al dan niet rechtstreeks) van de Exception class. Bijvoorbeeld:

>>> class MijnError(Exception):
...     def __init__(self, waarde):
...         self.waarde = waarde
...     def __str__(self):
...         return repr(self.waarde)
... 
>>> try:
...     raise MijnError(2*2)
... except MijnError, e:
...     print 'Mijn exception is opgetreden, waarde:', e.waarde
... 
Mijn exception is opgetreden, waarde: 4
>>> raise MijnError, 'oeps!'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.MijnError: 'oeps!'

User-defined exception classes verschillen niet wezenlijk van andere classes. Meestal worden exception classes echter simpel gehouden; normaal gesproken beschikken ze alleen over een aantal attributen waarmee informatie over de exception kan worden doorgegeven aan de handlers die de exception afhandelen. Bij het schrijven van een module waarin verschillende soorten fouten kunnen ontstaan, is het een goed gebruik om een base exception class te creëren voor de in die module gedefinieerde exceptions, en deze te subclassen om de specifieke exception classes voor de verschillende foutcondities te creëren:

class Error(Exception):
    """Base class voor exceptions in deze module."""
    pass

class InputError(Error):
    """Exception voor fouten in de invoer.

    Attributen:
        expressie – ingevoerde expressie waarin de fout is opgetreden
        melding – toelichting op de fout
    """

    def __init__(self, expressie, melding):
        self.expressie = expressie
        self.melding = melding

class TransitionError(Error):
    """Treedt op als een operatie probeert een niet-toegestane toestandsovergang tot stand te brengen.

    Attributes:
        vorige – toestand aan het begin van de overgang
        volgende – beoogde nieuwe toestand
        melding – uitleg waarom de specifieke overgang niet is toegestaan
    """

    def __init__(self, vorige, volgende, melding):
        self.vorige= vorige
        self.volgende = volgende
        self.melding = melding

De meeste exceptions krijgen namen die eindigen op “Error”, analoog aan de naamgeving van de standaardexceptions.

Veel standaardmodules definiëren hun eigen exceptions om fouten te rapporteren die kunnen optreden in de functies die ze definiëren. Meer informatie over classes vind je in hoofdstuk 9, “Classes”.


8.6 Het definiëren van opruimacties

Het try-statement heeft nog een optionele clause die bedoeld is voor het definiëren van opruimacties die onder alle omstandigheden uitgevoerd moeten worden. Bijvoorbeeld:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print 'Vaarwel, wereld!'
... 
Vaarwel, wereld!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
KeyboardInterrupt

Een finally-clause wordt altijd uitgevoerd, ongeacht of er een exception is opgetreden in de try-clause. Mocht er een exception opgetreden zijn, dan wordt die opnieuw gegenereerd na uitvoering van de finally-clause. De finally clause wordt ook uitgevoerd “op weg naar de uitgang” als je uit het try-statement springt via een break- of return-statement.

De finally-clause is nuttig als je externe resources (zoals bestanden of netwerkconnecties) moet vrijgeven, ongeacht of het gebruik van de resources geslaagd is.

Een try-statement heeft hetzij één of meer except-clauses, hetzij één finally-clause, maar niet allebei.


Previous Page

Up One Level

Next Page

Python Tutorial

Contents

Index

Vorige: 7. Input and Output Omhoog: Python Tutorial Volgende: 9. Classes
Release 2.3.4, documentation updated on May 20, 2004.
See About this document... for information on suggesting changes.