3. Het debuggen van je programma#

Programmeren omvat het schrijven van computer code. En daarbij maak je wel eens fouten. Het is nog een heel karwei om die fouten te vinden. Het vinden van fouten in je programma heet debuggen en is een essentieel onderdeel van programmeren. Iedereen maakt dus fouten en bugs zijn onvermijdelijk, zelfs voor de beste programmeurs. In dit hoofdstuk gaan we het hebben over wat debuggen is, waar het woord vandaan komt, en hoe je verschillende technieken kunt gebruiken om je Python-programma’s foutvrij te maken. We richten ons op drie belangrijke technieken: debuggen met print-statements, het gebruik van breakpoints en stepping in PyCharm, en het analyseren van je programma met een Python Profiler.

3.1. De herkomst van het woord#

Het woord “debuggen” komt oorspronkelijk uit de vroege dagen van de informatica. In de jaren ‘40, toen computers nog enorm groot en complex waren, vond een beroemd incident plaats. Een team van ingenieurs ontdekte een echte mot (bug in het Engels) die vastzat in een van de relais van de Mark II-computer. Deze mot veroorzaakte een storing in de machine, en toen ze de mot verwijderden, noemden ze dit “debuggen”. Sindsdien is de term blijven hangen en verwijst het naar het opsporen en verhelpen van fouten in computerprogramma’s. De juistheid van het verhaal staat nog ter discussie, maar het verhaal geeft wel mooi weer dat een bug op een rare of onverwachte plaats kan zitten.

3.2. Debuggen door print-statements#

Een van de eenvoudigste manieren om je programma te debuggen is door gebruik te maken van print-statements. Je voegt aan je code print-statements toe, die informatie over de status van je programma naar de console printen. Je kunt zo zien in wat voor toestand het programma zich bevindt. Laten we een voorbeeld bekijken:

def voeg_getallen_toe(a, b):
    print(f"voeg_getallen_toe: a = {a}, b = {b}")
    resultaat = a + b
    print(f"voeg_getallen_toe: resultaat = {resultaat}")
    return resultaat

som = voeg_getallen_toe(5, 10)
print(f"Hoofdprogramma: som = {som}")

In dit voorbeeld gebruiken we print-statements om te zien wat de waarden van a en b zijn, en wat het resultaat is na de optelling. Deze methode is vooral handig als je snel wilt controleren of variabelen de juiste waarden hebben of om te begrijpen hoe je code zich gedraagt op bepaalde punten.

Hoewel print-statements nuttig zijn, kunnen ze je programma ook rommelig maken en zijn ze niet altijd de beste manier om complexe problemen op te lossen. Wanneer je de bug gevonden hebt, kan het veel werk zijn om de print-statements weer te verwijderen. Om het nog ingewikkelder te maken, is de waarschijnlijkheid dat je met het verwijderen van deze statements weer nieuwe bugs introduceert hoger dan je lief is. Gelukkig is er een meer geavanceerdere vorm van debuggen. Het gebruik van Breakpoints en Stepping.

3.3. Breakpoints in Visual Studio Code#

Visual Studio Code is niet alleen geschikt om al je Python-bestanden overzichtelijk te beheren, maar ook erg handig om te debuggen. Een van de belangrijkste functies is de ingebouwde debugger. Hiermee kun je breakpoints instellen en door je code stappen om te zien wat er gebeurt. Wat breakpoints zijn en hoe je hiermee omgaat, lees je hieronder.

Breakpoints instellen#

Een breakpoint is een marker die je op een regel code plaatst waar je wilt dat je programma stopt met uitvoeren. Zie het als een rood stoplicht: op dat punt pauzeert je code en kun je precies inspecteren hoe de variabelen en functies zich gedragen.

Om een breakpoint in Visual Studio Code in te stellen: 1. Open het bestand waarin je de code wilt debuggen. 2. Klik in de linkermarge naast de regel waar je wilt stoppen. Er verschijnt dan een rode stip, wat aangeeft dat je daar een breakpoint hebt gezet.

def vermenigvuldig_getallen(a, b):
    resultaat = a * b
    return resultaat

product = vermenigvuldig_getallen(6, 7)
print(f"Hoofdprogramma: product = {product}")

Stel dat je een breakpoint wilt instellen op de regel resultaat = a * b. Klik in de linkermarge naast deze regel. Als je nu je programma uitvoert in de debug-modus van Visual Studio Code, zal het uitvoeren stoppen bij dit breakpoint.

Debug-modus starten#

Om te debuggen in Visual Studio Code:

  1. Ga naar de Run and Debug-sidebar (links in de editor, het play-icoontje met een kevertje).

  2. Klik op de Play-knop (Start Debugging) of kies de juiste configuratie als je meerdere debug-instellingen hebt.

  3. Je programma start, en zodra het breakpoint wordt bereikt, pauzeert de uitvoer automatisch.

Stepping#

Wanneer je programma stopt bij een breakpoint, kun je gebruik maken van stepping om je code regel voor regel uit te voeren. Zo kun je precies zien welke stappen je code doorloopt en waar het eventueel misgaat.

  • Step Over: Voert de huidige regel uit en gaat vervolgens naar de volgende regel in dezelfde functie. (F10 op Windows/Linux, Fn+F10 op macOS)

  • Step Into: Gaat de functie binnen als de huidige regel een functieaanroep is. Zo kun je de binnenkant van een functie stap voor stap volgen. (F11 op Windows/Linux, Fn+F11 op macOS)

  • Step Out: Voert de resterende regels van de huidige functie in één keer uit en springt terug naar de regel waar de functie werd aangeroepen. (Shift+F11 op Windows/Linux, Shift+Fn+F11 op macOS)

In het Variables-venster (rechts of links in beeld, afhankelijk van je layout) kun je zien welke variabelen op dat moment in je code aanwezig zijn en welke waarden ze hebben. Dit is onmisbaar bij het opsporen en oplossen van bugs.

Meer weten? Voor meer informatie over debugging in Visual Studio Code en alle beschikbare opties (zoals conditional breakpoints en exception breakpoints), bekijk de officiële documentatie van Visual Studio Code.

3.4. Analyse van het gedrag van je programma#

Naast het debuggen met print-statements en breakpoints, is het soms nodig om dieper te graven in de prestaties van je programma. Een profiler helpt je hierbij door te meten hoeveel tijd je code in beslag neemt en hoeveel geheugen het gebruikt.

Een populaire profiler voor Python is cProfile. Deze profiler wordt meegeleverd met de standaard Python-bibliotheek en is eenvoudig te gebruiken.

Gebruik van cProfile#

Om cProfile te gebruiken, kun je het eenvoudigweg importeren en je code erdoorheen laten draaien. Hier is een voorbeeld:

import cProfile

def langzame_functie():
    totaal = 0
    for i in range(1000000):
        totaal += i
    return totaal

def snelle_functie():
    return sum(range(1000000))

def hoofdprogramma():
    langzame_functie()
    snelle_functie()

cProfile.run('hoofdprogramma()')

Wanneer je dit programma uitvoert, geeft cProfile je het volgende rapport:

        7 function calls in 0.062 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.062    0.062 <string>:1(<module>)
        1    0.000    0.000    0.009    0.009 main.py:13(snelle_functie)
        1    0.000    0.000    0.062    0.062 main.py:16(hoofdprogramma)
        1    0.053    0.053    0.053    0.053 main.py:7(langzame_functie)
        1    0.000    0.000    0.062    0.062 {built-in method builtins.exec}
        1    0.009    0.009    0.009    0.009 {built-in method builtins.sum}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

Je ziet hier gedetailleerd hoeveel tijd elke functie in beslag neemt en hoe vaak elke functie is aangeroepen. Hiermee kun je bijvoorbeeld stukken code vinden, die onnodig vaak aangeroepen worden, zodat je vervolgens met de debugger weer kan kijken hoe je je code kan optimaliseren.

Geheugengebruik analyseren#

Naast tijdsprofielen is het soms ook belangrijk om te weten hoeveel geheugen je programma gebruikt. Zeker wanneer je met grote datasets werkt, bijvoorbeeld bij een AI-toepassing, dan kan inzicht in het geheugengebruik van je programma ervoor zorgen of je je programma kan draaien of niet. Een handige tool hiervoor is memory_profiler. Deze tool geeft je inzicht in het geheugengebruik van je programma, wat vooral nuttig is bij het werken met grote datasets of geheugenintensieve taken.

Om memory_profiler te gebruiken, moet je het eerst installeren:

pip install memory_profiler

Je kunt de memory_profiler ook in PyCharm installeren in het tabblad Python Packages onderin je PyCharm scherm.

Vervolgens kun je de tool gebruiken door je code te decoreren met @profile:

from memory_profiler import profile

@profile
def geheugen_intensieve_functie():
    grote_lijst = [i for i in range(1000000)]
    return sum(grote_lijst)

geheugen_intensieve_functie()

Wanneer je dit programma uitvoert, geeft memory_profiler je het volgende rapport:

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
     3     19.7 MiB     19.7 MiB           1   @profile
     4                                         def geheugen_intensieve_functie():
     5     58.0 MiB     38.3 MiB     1000003       grote_lijst = [i for i in range(1000000)]
     6     65.2 MiB      7.2 MiB           1       return sum(grote_lijst)

Je ziet nu dat de geheugen_intensieve_functie ongeveer 65MB aan geheugenruimte inneemt. Dat is op zich voor dit programma niet heel spannend. Maar wanneer je het net even verkeerd aanpakt, kan het net zo goed 65GB worden en dan heb je waarschijnlijk te weinig werkgeheugen en wordt je computer traag.