Leren Programmeren in Python Deel 3

Wat in dit deel aan bod komt:


Werken met tekstbestanden

Het is niet altijd handig om invoer in te moeten tikken en om uitvoer slechts op het scherm te kunnen zien. Soms komt die invoer in feite uit een ander programma en wil je deze niet overtikken. Of je wilt het programma herhaaldelijk draaien met min of meer dezelfde invoer; denk maar aan testen. Dan is het makkelijker als de invoer in een bestand staat.

We beperken ons hier tot tekstbestanden. We beginnen met een nadere blik op standaard in- en uitvoer. Daarna behandelen we het lezen van invoer uit een tekstbestand en vervolgens het schrijven naar een tekstbestand.

Lezen van standaard invoer

Onze programma's hebben tot nu toe alleen gelezen uit standaard invoer, dat i.h.a. gekoppeld is aan het toetsenbord. De functie input() leest een karakterrij vanaf standaard invoer en retourneert de geëvalueerde waarde. Als dat evalueren niet gewenst is, dan kan invoer gelezen worden met raw_input(). Deze functie retourneert de ongeëvalueerde karakterrij. Het verschil is interactief eenvoudig te zien:

>>> input('Geef een uitdrukking: ')
Geef een uitdrukking: 1+1
2
>>> raw_input('Geef een uitdrukking: ')
Geef een uitdrukking: 1+1
'1+1'
>>> input('Geef een bestandsnaam: ')
Geef een bestandsnaam: bestand.txt
Traceback (most recent call last):
  File "<input>", line 1, in ?
  File "<string>", line 0, in ?
NameError: name 'bestand' is not defined
>>> input('Geef een bestandsnaam: ')
Geef een bestandsnaam: 'bestand.txt'
'bestand.txt'
>>> raw_input('Geef een bestandsnaam: ')
Geef een bestandsnaam: bestand.txt
'bestand.txt'

Soms kan het nuttig zijn om een karakterrij apart te evalueren, d.w.z. los van het lezen van invoer. Hiervoor is de ingebouwde functie eval(), die een karkterrij omzet in een bijbehorend Python-object. De functie input(...) is equivalent met eval(raw_input(...)).

Schrijven naar standaard uitvoer

Onze programma's hebben tot nu toe alleen geschreven naar standaard uitvoer, dat i.h.a. gekoppeld is aan het scherm. Dat gebeurt met de print-opdracht. Die geef je een stel uitdrukkingen mee, waarvan dan een "mooie" tekstuele representatie wordt afgedrukt. Zo worden karakterrijen zonder apostrofs afgedrukt en worden bijzondere karakters, zoals de regelovergang '\n', vertaald naar een visueel effect.

De print-opdracht scheidt de weergave van de uitdrukkingen met een enkele spatie ' '. Als de laatste uitdrukking wel/niet door een komma wordt gevolgd, dan wordt niet/wel een regelovergang geproduceerd. Het programmafragment

print 1, 2, 3,
print 4, 5, 6
print 7, 8, 9

geeft als uitvoer

1 2 3 4 5 6
7 8 9

Soms is het nuttig de waarde van een object weer te geven in de vorm waarin je het ook in een Python programma opschrijft. Hiervoor is de ingebouwde functie repr(object), die een tekstuele weergave van object retourneert als karakterrij. De functie repr() kun je zien als inverse van de hierboven genoemde functie eval().

Als je bij interactief gebruik van Python een uitdrukking opgeeft zonder print ervóór, dan krijg je de repr() van dat object te zien, i.p.v. de "mooie" weergave.

>>> print '1 + 1\n=\n', 2
1 + 1
=
2
>>> '1 + 1\n=\n', 2
('1 + 1\n=\n', 2)

Denk ook aan de formaataanpassing met %, die nuttig is om kolommen uit te lijnen.

Lezen uit een tekstbestand

Om uit een extern opgeslagen tekstbestand te lezen moet eerst een leespoort naar dat bestand geopend worden. De functie-aanroep open(filename, 'r') retourneert zo'n leespoort ('r' staat voor read). De leeskop staat dan vooraan in het bestand. Om deze leespoort te gebruiken moet je hem een naam geven, bijvoorbeeld:

inp = open ( 'bestand.txt', 'r' )  #@ inp is geopend voor lezen

We zullen de leespoort vaak vereenzelvigen met het gekoppelde bestand. D.w.z. i.p.v. 'het bestand gekoppeld aan de leespoort inp' zullen we kortweg zeggen 'het bestand inp'.

Python heeft een eenvoudige voorziening om door een tekstbestand te lezen. In het volgende fragment krijgt de variabele regel achtereenvolgens als waarde elke regel uit het, voor lezen geopende, bestand inp. De waarde van regel is een karakterrij, die ook de regelovergang '\n' bevat.

for regel in inp :
    ...  # verwerk regel

Om precies te zien wat er is ingelezen, kun je print repr(regel) gebruiken. Dat werkt beter dan print regel, omdat in het laatste geval bijzondere karakters, zoals regelovergangen, geïnterpreteerd worden en daardoor minder duidelijk zichtbaar zijn.

Meestal ben je niet zo geïnteresseerd in de gelezen karakterrijen zelf, maar wel in de waarden die er staan, zoals getallen. De functie-aanroep eval(regel) evalueert karakterrij regel tot een waarde. Dat passen we toe in het volgende voorbeeld.

Het programma gepast7.py hieronder vraagt de naam van een bestand waarin op elke regel een bedrag staat en drukt voor elk bedrag een minimale gepaste betaling af als lijst.

"""Bepaal voor alle bedragen in het opgegeven tekstbestand
   een minimale gepaste betaling als lijst van munten.
"""

## Definieer constanten
munten = [ 200, 100, 50, 20, 10, 5, 2, 1 ]  # munten in dalende volgorde

## Vraag naam van invoerbestand op en open het voor lezen via inp
bestandsnaam = raw_input ( 'Geef naam van tekstbestand met bedragen: ' )

inp = open ( bestandsnaam, 'r' )  #@ inp is geopend voor lezen

## Verwerk alle regels uit tekstbestand inp

for regel in inp :
    # print repr ( regel )  # alleen voor foutendiagnose
    bedrag = eval ( regel )  # het bedrag op de regel
    print '%3d:' % bedrag,

    ## Bepaal minimale betaling van bedrag
    betaling = [ ]       # reeds gevonden deel van de betaling
    restbedrag = bedrag  # resterende te passen bedrag

    #@ inv: sum(betaling) + restbedrag = bedrag

    for munt in munten :
        while restbedrag >= munt :
            betaling.append ( munt )
            restbedrag = restbedrag - munt

    assert restbedrag == 0  # dus sum(betaling) = bedrag

    ## Schrijf minimale betaling
    print betaling

Stel het bestand testbedragen.txt bevat de volgende zeven regels:

0
1
3
4
200
1 + 2 + 5 + 10 + 20 + 50 + 100 + 200
500

Bij opgeven van de bestandsnaam testbedragen.txt is de uitvoer (op standaard uitvoer):

Geef naam van tekstbestand met bedragen: testbedragen.txt
  0: []
  1: [1]
  3: [2, 1]
  4: [2, 2]
200: [200]
388: [200, 100, 50, 20, 10, 5, 2, 1]
500: [200, 200, 100]

Als het opgegeven bestand niet bestaat, dan geeft het Python-systeem de foutmelding IOError: [Errno 2] No such file or directory: filename. Als de inhoud van het invoerbestand niet past bij wat het programma verwacht, dan geeft het Python-systeem ook een foutmelding, bijvoorbeeld unexpected EOF while parsing.

Schrijven naar een tekstbestand

Deze paragraaf moet nog uitgewerkt worden.

Om het tekstbestand met naam filename te openen voor schrijven ('w' staat voor write):
out = open ( filename, 'w' )
N.B. Als het bestand al bestaat wordt het leeg gemaakt. Dit is onomkeerbaar.
Gebruik open(filename, 'a') om een bestaand bestand te openen om er wat achter te schrijven ('a' staat voor append); de leeskop staat dan automatisch achteraan.

Om uitdrukkingen naar out te schrijven:
print >> out, uitdrukkingen

Om het bestand netjes af te sluiten en te zorgen dat alle geschreven gegevens goed opgeslagen worden:
out.close()


Oefeningen

Hier volgen wat oefeningen met bestanden.

  1. Haal het programma gepast7.py en tekstbestand testbedragen.txt op. Draai het programma met het tekstbestand als invoer en controleer de uitvoer.
  2. Activeer vervolgens de opdracht print repr ( regel ) door verwijdering van # ervóór (de print-opdracht moet wel even ver zijn ingesprongen als de opdrachten eronder) en probeer het nogmaals.
  3. Maak een fout in het tekstbestand, bijv. '0' of nul i.p.v. 0, en probeer het programma opnieuw.
  4. Probeer het programma ook met een niet-bestaand tekstbestand als invoer en aanschouw de foutmelding.
  5. Pas het programma zó aan dat het na verwerken van het invoerbestand ook afdrukt hoeveel bedragen er zijn verwerkt en wat het gemiddelde aantal munten per betaling (met één cijfer achter de komma) was, mits het aantal niet nul was. De uitvoer bij het tekstbestand testbedragen.txt dient te eindigen met:
    Aantal bedragen: 7
    Gemiddeld aantal munten per bedrag: 2.4
    

Verdeel en heers met routines

De menselijke geest kan maar circa zeven "dingen" tegelijk "bevatten": zie George A. Miller. "The Magical Number Seven, Plus or Minus Two: Some Limits on our Capacity for Processing Information", Psychological Review, 63:81-97 (1956). Uit moderner onderzoek blijkt zelfs dat de limiet eerder bij vier dingen ligt (zie Nelson Cowan. "The Magical Number 4 in Short-Term Memory: A Reconsideration of Mental Storage Capacity", Behavior and Brain Sciences, 24(1):87-114)! Probeer je meer dingen tegelijk aandacht te geven, dan neemt de kans op fouten dramatisch toe.

Een algemene methode om complexiteit te beteugelen is de methode van verdeel en heers. Bij het oplossen van een groter (programmeer)probleem komt dat neer op de volgende stappen.

  1. Het probleem in een klein aantal deelproblemen splitsen.
  2. De deelproblemen afzonderlijk oplossen.
  3. De deeloplossingen tot totaaloplossing samenvoegen.

Als een deelprobleem zelf ook weer te groot is, dan pas je dezelfde methode nogmaals toe. Dat doe je net zo lang tot je uitkomt bij voldoend kleine problemen, die je "rechtstreeks" kan oplossen.

Verdeel en heers biedt een aantal voordelen:

Verdeel en heers werkt pas goed indien

Bij wijze van voorbeeld analyseren we in deze optiek het programma gepast7.py van hierboven. Het programma bestaat uit een dozijn opdrachten, teveel om in één oogopslag te overzien en doorgronden. Je kan het opgedeeld denken in drie delen:

  1. Het openen van het juiste invoerbestand.
  2. Het extraheren van de bedragen uit het invoerbestand en ze één voor één verwerken.
  3. Het bepalen van een minimale gepaste betaling voor een gegeven bedrag.

Deze omschrijvingen van de delen zijn niet erg precies, maar volstaan voor dit moment. Hier zijn de drie bijbehorende programmafragmenten:

  1. ## Vraag naam van invoerbestand op en open het voor lezen via inp
    bestandsnaam = raw_input ( 'Geef naam van tekstbestand met bedragen: ' )
    
    inp = open ( bestandsnaam, 'r' )  #@ inp is geopend voor lezen
    
    ...
    
  2. ...
    
    ## Verwerk alle regels uit tekstbestand inp
    
    for regel in inp :
        # print repr ( regel )  # alleen voor foutendiagnose
        bedrag = eval ( regel )  # het bedrag op de regel
        print '%3d:' % bedrag,
    
        ...
    
        ## Schrijf minimale betaling
        print betaling
    
  3.     ...
    
        ## Bepaal minimale betaling van bedrag
        betaling = [ ]       # reeds gevonden deel van de betaling
        restbedrag = bedrag  # resterende te passen bedrag
    
        #@ inv: sum(betaling) + restbedrag = bedrag
    
        for munt in munten :
            while restbedrag >= munt :
                betaling.append ( munt )
                restbedrag = restbedrag - munt
    
        assert restbedrag == 0  # dus sum(betaling) = bedrag
    
        ...
    

De delen zijn samengevoegd door Deel 1 te laten volgen door Deel 2 waarbinnen Deel 3 is opgenomen (we negeren verder even de constante munten):

"""Bepaal voor alle bedragen in het opgegeven tekstbestand
   een minimale gepaste betaling als lijst van munten.
"""

## Definieer constanten
munten = [ 200, 100, 50, 20, 10, 5, 2, 1 ]  # munten in dalende volgorde
Deel 1
Deel 2
Deel 3

Wat betreft de relatie tussen de delen onderling en met de omgeving is het volgende op te merken:

De overige variabelen spelen een beperktere rol:

De abstracte structuur is weergegeven in het volgende plaatje.

Plaatje met drie blokken en pijlen

Je ziet van deze opdeling in het uiteindelijke programma niet veel meer terug dan het commentaar achter ##. De manier waarop de delen met elkaar verband houden, zowel qua volgorde van uitvoering (Eng.: control flow) als qua communicatie van gegevens (Eng.: data flow) is volledig impliciet in het uiteindelijke programma.

Overigens is het programma ook anders op te delen. Het openen van het tekstbestand zou bijvoorbeeld ook bij Deel 2 getrokken kunnen worden en het afdrukken van de betaling zou onder Deel 3 kunnen vallen. Er is niet zoiets als de enige juiste opdeling of de beste opdeling. Het is wel van belang een gemaakte opdeling goed vast te leggen.

Routines en globale variabelen

We zullen nu een paar manieren beschouwen waarop in Python de opdeling wel expliciet gemaakt kan worden. Al deze manieren hebben gemeen dat via een def-opdracht elk deel als aparte routine worden gedefinieerd en een eigen naam krijgt: openTekstbestand, verwerkTekstbestand en bepaalMinimaleBetaling. Het verschil zit in de manier waarop de control flow en data flow zijn geregeld. De eerste manier gebruikt, net als het oorspronkelijke programma, globale variabelen voor de onderlinge communicatie: gepast7b.py.

"""Bepaal voor alle bedragen in het opgegeven tekstbestand
   een minimale gepaste betaling als lijst van munten.
"""

## Definieer constanten
munten = [ 200, 100, 50, 20, 10, 5, 2, 1 ]  # munten in dalende volgorde

## Definieer routines
def openTekstbestand ( ) :
    """Vraag naam van invoerbestand op en open het voor lezen via inp"""
    global inp

    bestandsnaam = raw_input ( 'Geef naam van tekstbestand met bedragen: ' )

    inp = open ( bestandsnaam, 'r' )  #@ inp is geopend voor lezen

def verwerkTekstbestand ( ) :
    """Verwerk alle regels uit tekstbestand inp"""
    global bedrag

    for regel in inp :
        # print repr ( regel )  # alleen voor foutendiagnose
        bedrag = eval ( regel )  # het bedrag op de regel
        print '%3d:' % bedrag,

        bepaalMinimaleBetaling ( )

        ## Schrijf minimale betaling
        print betaling

def bepaalMinimaleBetaling ( ) :
    """Bepaal minimale betaling van bedrag"""
    global betaling

    betaling = [ ]       # reeds gevonden deel van de betaling
    restbedrag = bedrag  # resterende te passen bedrag

    #@ inv: sum(betaling) + restbedrag = bedrag

    for munt in munten :
        while restbedrag >= munt :
            betaling.append ( munt )
            restbedrag = restbedrag - munt

    assert restbedrag == 0  # dus sum(betaling) = bedrag

## Begin van het programma opgebouwd uit de routines
openTekstbestand ( )
verwerkTekstbestand ( )

## Einde

Dit programma kun je als volgt begrijpen in termen van verdeel en heers:

  1. Splitsen: De opdeling in routines is vastgelegd in de witte stukken van het programma beginnend met def. Hierin zie je dat er drie routines zijn, wat hun doel is en via welke globale variabelen ze communiceren.
  2. Afzonderlijk oplossen: De uitwerking van iedere routine staat apart vermeld in het ingesprongen blok opdrachten onder def. Je kunt ze afzonderlijk bestuderen, zonder je te bekommeren om de andere delen.
  3. Samenvoegen: De opbouw van het geheel in termen van de routines staat achteraan: eerst wordt openTekstbestand() geactiveerd en vervolgens verwerkTekstbestand(); binnen verwerkTekstbestand() wordt herhaaldelijk bepaalMinimaleBetaling() geactiveerd.

De algemene vorm van zo een routine-definitie is

def routine-naam ( ) :
    """Omschrijving"""
    global namen-van-globale-variabelen-die-gewijzigd-worden

    ingesprongen-blok-opdrachten
De global-opdracht vervalt als er geen globale variabelen worden gewijzigd (bijv. omdat de routine alleen maar uitvoer schrijft via print):
def routine-naam ( ) :
    """Omschrijving"""

    ingesprongen-blok-opdrachten

De routine wordt geactiveerd in een opdracht, die routine-aanroep heet:

routine-naam ( )

Variabelen gebruikt in het blok onder def vallen in drie groepen:

  1. Variabelen die achter global vermeld staan: dit zijn zogenaamde globale resultaat-variabelen. Hun binding is buiten de routine vastgelegd. De routine kan deze binding wijzigen (met =) en daarmee een resultaat doorgeven.
  2. Variabelen die niet achter global vermeld staan en waar in het blok geen toekenningen aan gedaan worden: dit zijn globale inspectie-variabelen. Hun binding is buiten de routine vastgelegd. De routine kan deze binding niet wijzigen, maar wel (de waarde van) het gebonden object inspecteren.
  3. Variabelen die niet achter global vermeld staan en waar in het blok wel toekenningen aan gedaan worden: dit zijn zogenaamde lokale variabelen. Ze zijn buiten de routine niet direct toegankelijk via hun naam (zie ook de oefeningen). Je hoeft je daarom bij het uitwerken of lezen van een routine geen zorgen te maken over eventuele naamsconflicten met lokale variabelen in andere routines of met globale variabelen.

De volgende tabel toont de classificatie van de variabelen in programma gepast7b.py:

VariabeleRoutine
openTekstbestand verwerkTekstbestand bepaalMinimaleBetaling
bestandsnaam Lokaal--
inp Globaal resultaatGlobale inspectie-
regel -Lokaal-
bedrag -Globaal resultaatGlobale inspectie
betaling -Globale inspectieGlobaal resultaat
munt --Lokaal
restbedrag --Lokaal

Contract tussen gebruiker en maker

De onderlinge communicatie tussen routines via globale variabelen heeft enkele nadelen:

Het eerste bezwaar moet ondervangen worden door beter commentaar op te nemen. Daartoe is het goed om een routine te zien als een deelproduct met een aparte gebruiker (aanroeper) en maker (definitie-schrijver), ook als het gaat om één persoon. De verplichtingen van gebruiker en maker worden vastgelegd in een tweezijdig contract, d.m.v. een preconditie (voorwaarde) en postconditie (gevolg). De gebruiker moet zorgen dat aan de preconditie is voldaan, terwijl de maker daar van uit kan gaan. Andersom, moet de maker zorgen dat de gewenste postconditie wordt bewerkstelligd, terwijl de gebruiker daar van uit kan gaan.

Contract
PreconditiePostconditie
Gebruikerzorgnut
|
v
^
|
Makernut --> zorg

De kunst van verdeel en heers is om te zorgen dat:

Het programma gepast7c.py. hieronder illustreert het vermelden van contracten. Achter pre staat vermeld welke aanname dient te gelden vóór de aanroep, terwijl achter post staat vermeld welk effect de routine dient te bewerkstelligen na de aanroep.

...

## Definieer routines
def openTekstbestand ( ) :
    """Vraag naam van invoerbestand op en open het voor lezen via inp
       Pre: True
       Post: inp is geopend voor lezen, de naam ervan
             is opgevraagd via standaard invoer, de leeskop staat vooraan
    """
    global inp

    ...

def verwerkTekstbestand ( ) :
    """Verwerk alle regels uit tekstbestand inp
       Pre: inp is gekoppeld aan tekstbestand geopend voor lezen;
            de leeskop in inp staat vooraan
       Post: voor alle bedragen in inp is een minimale betaling afgedrukt;
             de leeskop in inp staat achteraan
    """
    global bedrag

    ...

def bepaalMinimaleBetaling ( ) :
    """Bepaal minimale betaling van bedrag
       Pre: munten is lijst van muntsoorten, dalend gesorteerd
            bedrag is geheel getal met 0 <= bedrag <= 500
       Post: betaling is minimale betaling van bedrag met munten
    """
    global betaling

    ...

...

Routines met parameters en resultaten

Om ook aan de overige twee bezwaren tegemoet te komen is het beter om de routines minder direct te koppelen aan hun omgeving. Dit kan via parameters (waarin de routine bij aanroep waarden ontvangt) en resultaten (die de routine retourneert na de aanroep). Het volgende programma gepast7d.py illustreert deze aanpak om bij elke bedrag een minimale gepaste betaling in zowel euromunten als oude Nederandse munten af te drukken.

"""Bepaal voor alle bedragen in het opgegeven tekstbestand
   een minimale gepaste betaling als lijst van euromunten
   en oude NL munten.
"""

## Definieer constanten
euromunten = [ 200, 100, 50, 20, 10, 5, 2, 1 ]  # euromunten in dalende volgorde
oudeNLmunten = [ 500, 250, 100, 25, 10, 5, 1 ]  # oude NL munten in dalende volgorde

## Definieer routines
def openTekstbestand ( ) :
    """Vraag naam van invoerbestand op en open het voor lezen
       Pre: True
       Ret: poort naar tekstbestand geopend voor lezen, de naam ervan
            is opgevraagd via standaard invoer, de leeskop staat vooraan
    """

    bestandsnaam = raw_input ( 'Geef naam van tekstbestand met bedragen: ' )

    return open ( bestandsnaam, 'r' )

def verwerkTekstbestand ( inp ) :
    """Verwerk alle regels uit tekstbestand inp
       Pre: inp is gekoppeld aan tekstbestand geopend voor lezen;
            de leeskop in inp staat vooraan
       Post: voor alle bedragen in inp is een minimale betaling afgedrukt;
             de leeskop in inp staat achteraan
    """

    for regel in inp :
        # print repr ( regel )  # alleen voor foutendiagnose
        bedrag = eval ( regel )  # het bedrag op de regel
        print '%3d:' % bedrag, minimaleBetaling ( euromunten, bedrag )
        print '    ', minimaleBetaling ( oudeNLmunten, bedrag )

def minimaleBetaling ( munten, bedrag ) :
    """Bepaal minimale betaling van bedrag
       Pre: munten is lijst van muntsoorten, dalend gesorteerd
            bedrag is geheel getal met 0 <= bedrag <= 500
       Ret: minimale betaling van bedrag met munten
    """

    betaling = [ ]       # reeds gevonden deel van de betaling
    restbedrag = bedrag  # resterende te passen bedrag

    #@ inv: sum(betaling) + restbedrag = bedrag

    for munt in munten :
        while restbedrag >= munt :
            betaling.append ( munt )
            restbedrag = restbedrag - munt

    assert restbedrag == 0  # dus sum(betaling) = bedrag
    return betaling

## Begin van het programma opgebouwd uit de routines
verwerkTekstbestand ( openTekstbestand ( ) )

## Einde

De koppeling verloopt nu via parameters en geretourneerde resultaten. Een parameter is een lokale variabele, waarvan de beginwaarde door de aanroeper wordt opgegeven. Een routine ontvangt, indien nodig, gegevens via parameters achter def (geel) en retourneert, indien nodig, gegevens via de return-opdracht (blauw). Geretourneerde gegevens blijven onbenoemd. De aanroeper biedt, indien nodig, de gegevens aan (blauw) en gebruikt het resultaat in een uitdrukking (geel). Onderstaande tabel toont de classificatie van variabelen in programma gepast7d.py:

VariabeleRoutine
openTekstbestand verwerkTekstbestand bepaalMinimaleBetaling
bestandsnaam Lokaal--
inp -Parameter-
regel -Lokaal-
bedrag -LokaalParameter
munten --Parameter
betaling --Lokaal
restbedrag --Lokaal
munt --Lokaal

Routines moeten niet (alleen) als afkortingsmechanisme gezien worden om de programmatekst korter te maken. Het primaire doel ervan is om meer structuur aan te brengen en daarmee het programma leesbaarder te maken. Het is goed mogelijk dat in een programma slechts één aanroep van zo'n afkorting gedaan wordt. Dit kan toch nuttig zijn omdat het zo twee deelproblemen uit elkaar houdt, die in afzondering op te lossen zijn.


Meer Oefeningen

  1. Om na te gaan dat lokale variabelen inderdaad niet toegankelijk zijn buiten hun routine, kun je het volgende proberen. Voeg in het programma gepast7b.py enkele print-opdrachten toe om de waarde van de lokale variabelen bestandsnaam, regel, betaling, restbedrag of munt af te drukken. Ga na waar dat wel en waar dat niet lukt.
  2. Als de vorige opgave, maar probeer nu in het programma gepast7d.py de waarde van de parameters inp, munten of bedrag af te drukken. Ga na waar dat wel en waar dat niet lukt.
  3. Stel je wilt de naam van een variabele wijzigen in een programma. Ga na wat het verschil is tussen het wijzigen van een globale variabele, een lokale variabele en een parameter. Op welke plaatsen moet er iets veranderen en waar moet je op letten bij het kiezen van een nieuwe naam? Beschouw bijvoorbeeld de variabelen bedrag en munt in de programma's gepast7b.py en gepast7d.py.
  4. Het stapsgewijs opbouwen van een programma (ook wel integratie genoemd) helpt bij het vlug vinden van fouten. I.p.v. alle onderdelen ineens bij elkaar te zetten (een zogenaamde big bang), kun je vaak de afzonderlijk geteste onderdelen ook in kleinere groepjes testen.

    Beschouw nogmaals het programma gepast7b.py. Onderdruk hierin de aanroep van bepaalMinimaleBetaling() in verwerkTekstbestand() door er een # voor te zetten en voeg eronder de opdracht betaling = [ bedrag ] toe. Hiermee kun je de twee routines openTekstbestand() en verwerkTekstbestand() testen. Als het niet werkt, dan zit het probleem niet in bepaalMinimaleBetaling().

  5. Beschouw nogmaals het programma gepast5.py (uit deel 2), dat het kleinste bedrag bepaalt tussen 0 en 500 met het grootste aantal munten in een minimale gepaste betaling. Herschrijf dit programma door gebruik te maken van de functie minimaleBetaling(...) uit het programma gepast7d.py.

Samenvatting

De ingebouwde functies input() en raw_input() lezen beide van standaard invoer. Het verschil is dat input() de gelezen karakterrij ook evalueert, d.w.z. equivalent is met eval(raw_input()).

De opdracht print schrijft een "mooie" weergave naar standaard uitvoer. De uitdrukkingen worden met een spatie gescheiden. Een komma op het einde onderdrukt de regelovergang. De ingebouwde functie repr() is de inverse van eval() en retourneert een karakterrij die het gegeven Python-object beschrijft. De formaataanpassing met % is handig om kolommen uit te lijnen.

De ingebouwde functie open(filenaam, mode) retourneert een (communicatie)poort naar het extern opgeslagen tekstbestand met naam filenaam. De wijze van openen hangt af van mode:

'r' (read)
lezen; leeskop staat vooraan; N.B. Het bestand moet al bestaan.
'w' (write)
schrijven; N.B. Een bestaand bestand wordt leeg gemaakt!
'a' (append)
achtervoegen; schrijfkop staat achteraan

De opdracht for variabele in file : laat variabele één voor één alle regels van file doorlopen. Daarbij moet file al geopend zijn voor lezen en is variabele een ongeëvalueerde karakterrij, inclusief de regelovergang '\n'. Evalueren van een karakterrij tot een waarde kan met de ingebouwde functie eval().

De opdracht print >> file, uitdrukingen schrijft een "mooie" tekstuele weergave van de waarden van de uitdrukkingen naar file. Daarbij moet file al geopend zijn voor schrijven of achtervoegen.

Een groter programma kan met de methode van verdeel en heers opgebouwd worden uit een aantal kleinere routines. Een routine is een blok opdrachten dat d.m.v. zijn naam geactiveerd kan worden, met daarachter tussen haakjes de parameters waarmee de routine werkt. Het activeren heet ook een aanroep van de routine. Een routine fungeert daarmee ook als een afkorting, die op allerlei plaatsen eenvoudig te gebruiken is.

Je kan routines zien als een abstractie-mechanisme, waarbij een routine de oplossing van een afgesplitst deelprobleem verpakt. Bij het doen van een aanroep geef je alleen aan om welke instantie van het deelprobleem het gaat en abstraheer je van de manieer waarop het effect of resultaat wordt bereikt.

def routine-naam ( parameters ) :
    """Omschrijving en contract"""
    global namen-van-globale-variabelen-die-gewijzigd-worden

    ingesprongen-blok-opdrachten

De koppeling tussen binnen- en buiten-wereld van routines heet ook wel het interface van de routine.

De specificatie van een routine bestaat uit


Copyright © 2003-2004, Tom Verhoeff
Validate HTML