Stel dat je de gebruik om paren getallen vraagt in een loop. Voor ieder paar getallen dat de gebruiker ingeeft wil je laten zien wat hun vermenigvuldiging is. Je wilt de gebruiker het programma laten stoppen als hij een nul ingeeft, ongeacht voor welk getal. Vanwege een onbekende reden mogen de twee getallen geen delers van elkaar zijn; als ze dat wel zijn is dat een fout en wordt het programma onmiddellijk gestopt met een foutboodschap. Tenslotte is het een eis dat de getallen in het bereik 0 tot en met 1000 liggen; als de gebruiker een getal ingeeft dat niet in dat bereik zit wordt dat echter niet beschouwd als een fout; je wilt gewoon dat de gebruiker dan een nieuw getal ingeeft. Hoe programmeer je zoiets? Hier is een eerste poging:

from pcinput import getInteger

x = 3
y = 7

while (x != 0) and (y != 0) and (x%y != 0) and (y%x != 0):
    x = getInteger( "Geef nummer 1: " )
    y = getInteger( "Geef nummer 2: " )
    if (x > 1000) or (y > 1000) or (x < 0) or (y < 0):
        print( "Nummers moeten tussen 0 en 1000 zijn" )
        continue
    print( x, "keer", y, "is", x * y )
    
if x == 0 or y == 0:
    print( "Klaar!" )
else:
    print( "Fout: de nummers mogen geen delers zijn" )

Bestudeer deze code en maak een lijstje van alles wat je er slecht aan vindt. Als je dat gedaan hebt, lees dan verder en vergelijk je bevindingen met het lijstje hieronder. Als je zaken hebt aangetroffen die slecht zijn en die niet op de lijst staan, kun je me emailen.

Er zijn veel zaken die ik slecht vind aan deze code. Hier is mijn lijst:

De oplossing voor deze zaken die door sommige programmeurs geprefereerd wordt, is om x en y te initialiseren met waardes die de gebruiker ingeeft. Dit lost de willekeurige initialisatie op, en lost ook het probleem op dat je de vermenigvuldiging uitvoert als dat niet meer hoeft. Je moet dan wel het vragen om input verplaatsen naar het einde van de loop. Als er dan een continue voorkomt in de loop, moet je vlak voor die continue ook om de inputs vragen, anders krijg je een eindeloze loop. De code wordt dan iets als:

from pcinput import getInteger

x = getInteger( "Geef nummer 1: " )
y = getInteger( "Geef nummer 2: " )

while (x != 0) and (y != 0) and (x%y != 0) and (y%x != 0):
    if (x > 1000) or (y > 1000) or (x < 0) or (y < 0):
        print( "Nummers moeten tussen 0 en 1000 zijn" )
        x = getInteger( "Geef nummer 1: " )
        y = getInteger( "Geef nummer 2: " )
        continue
    print( x, "keer", y, "is", x * y )
    x = getInteger( "Geef nummer 1: " )
    y = getInteger( "Geef nummer 2: " )

if x == 0 or y == 0:
    print( "Klaar!" )
else:
    print( "Fout: de nummers mogen geen delers zijn" )

De code vermijdt twee van de drie genoemde problemen, maar voegt een nieuwe toe, die het mijns inziens alleen maar erger maakt. De lijst van problemen wordt:

De “truc” die je kunt gebruiken om deze problemen te verhelpen is de besturing van de loop puur te doen middels continues en breaks (een misschien soms een exit() als er een fout optreedt, maar in het volgende hoofdstuk wordt daar een “nettere” oplossing voor geboden). Dus je voert de loop “altijd” uit, maar je besluit om de loop te verlaten of opnieuw in te gaan als bepaalde omstandigheden optreden die je pas in de loop controleert (en niet vooraf). Om een loop “altijd” uit te voeren kun je while True gebruiken (dit betekent: de test die je uitvoert om te besluiten of de loop uitgevoerd moet worden, geeft altijd True).

from pcinput import getInteger
from sys import exit

while True:
    x = getInteger( "Geef nummer 1: " )
    if x == 0:
        break
    y = getInteger( "Geef nummer 2: " )
    if y == 0:
        break
    if (x < 0 or x > 1000) or (y < 0 or y > 1000):
        print( "Nummers moeten tussen 0 en 1000 zijn" )
        continue
    if x%y == 0 or y%x == 0:
        print( "Fout: de nummers mogen geen delers zijn" )
        exit()
    print( x, "keer", y, "is", x * y )

print( "Klaar!" )

Deze code lost vrijwel alle problemen op. Het vraagt slechts eenmalig om x en y. Er is geen willekeurige initialisatie van x en y. De loop stopt onmiddellijk als een nul wordt ingegeven. En de foutmelding staat meteen daar waar de fout geconstateerd wordt. Er is geen complexe boolean expressie nodig met een hoop ands en ors. De code is een beetje langer dan de eerste versie, maar lengte maakt niet uit, en de code is een stuk leesbaarder.

Het enige probleem dat nog over is, is dat als de gebruiker een waarde ingeeft buiten het bereik 0 tot en met 1000 voor x, hij nog steeds een waarde voor y moet ingeven alvorens het programma zegt dat de getallen opnieuw ingegeven moeten worden. Dat kun je het beste oplossen met functies, wat besproken wordt in het volgende hoofdstuk (je kunt het eventueel nu al oplossen met een geneste loop, maar ik zou me er niet druk om maken).

Een loop als deze, die while True gebruikt, wordt soms een “loop-en-een-half” genoemd. Het is een heel gebruikelijke aanpak voor het schrijven van een loop waarbij je niet precies weet wanneer de loop moet eindigen.

De gebruiker geeft een positief geheel getal. Je gebruikt daarvoor de getInteger() functie van pcinput. Deze functie staat het echter ook toe om negatieve getallen in te geven. Als de gebruiker een negatief getal ingeeft, wil je melden dat dat niet mag, en hem opnieuw een getal laten ingeven. Dit blijf je doen totdat daadwerkelijk een positief getal is ingegeven. Zodra een positief getal is ingegeven, druk je dat af en stopt het programma. Een dergelijk probleem wordt typisch aangepakt met een loop-en-een-half, omdat je geen idee hebt van hoe vaak een gebruiker een negatief getal ingeeft totdat hij wijs wordt. Schrijf zo’n loop-en-een-half. Je heb precies één break nodig, en hoogstens één continue. Druk het positieve getal dat de gebruiker heeft ingegeven af na de loop. De reden om het erna te doen is dat de loop alleen bedoeld is om de input onder controle te krijgen, en niet voor het verwerken van de correcte ingave.

Ik heb vaak geconstateerd dat studenten het gebruik van while True maar verwarrend vinden. Ze zien het vaak in voorbeeldcode, maar ze snappen niet echt het nut ervan. En vervolgens gaan ze while True opnemen in code van henzelf als ze niet precies weten wat ze moeten doen. Als je problemen hebt de loop-en-een-half te begrijpen, bestudeer dan deze paragraaf opnieuw, totdat je het wel begrijpt.