3.1. Lommeregner (konsolbaseret)

I denne introduktion til programmering og Python udvikles en interaktiv konsolbaseret (tekstbaseret) lommeregner som vist på figur 3.1.

../../_images/calculator-demo.gif

figur 3.1 Et konsolbaseret (tekstbaseret) lommeregnerprogram.

Bemærk

Introduktionerne antager du har gennemgået Installation og Første Python-program og første fejl.

Programmet du om lidt bliver hjulpet med at udvikle skal give brugeren mulighed for at vælge i mellem forskellige regneoperationer ved at taste et tal og trykke [Enter], f.eks. 1 for at udregne a+b. Herefter skal brugeren kunne indtaste de tal eller størrelser som udregningen kræver. Til sidst skal programmet vise brugeren resultatet af regneoperationen og give brugeren muligheden for at vælge en ny regneoperation.

3.1.1. Brugerinput og kommentarer

Opret et nyt program Python-program med et fornuftigt navn, f.eks. calculator.py, og gem det et fornuftigt sted, f.eks. i en mappe kaldet programmering/introduktion/.

Giv programmet følgende indhold:

Anbefaling

Skriv koden manuelt (tegn for tegn) ind i din teksteditor i stedet for at kopiere den og indsætte den (copy-and-paste).

Hvis du skriver Kildekode 3.1 ind i din teksteditor så har du skrevet 5 linjers Python-kode. Hvis du kopiere og indsætter det har du skrevet 0 linjers Python-kode.

Kildekode 3.1 calculator.py
1
2
3
4
5
6
# A simple calculator made as an introduction to programming

a = input("Write a number: ")
b = input("Write another number: ")
c = a+b
print(c)

Kør programmet og når programmet beder dig indtaste et tal skriver du blot et eller flere tal på tastaturet og trykker [Enter]. I din konsol bør du se noget som minder om nedenstående:

Kildekode 3.2 output (calculator.py)
1
2
3
Write a number: 13 
Write another number: 37
1337

Meget muligt forstår du ikke hvorfor eksemplet skriver 1337 som output (Kildekode 3.2, linje 3). Linje 6 (Kildekode 3.1) havde måske ledt dig til at forvente output 50. Den opførsel vender vi tilbage til.

Linje 1 (Kildekode 3.1) som starter med tegnet # 1 er en kommentar. Python ignorerer linjen, men kommentarer er vigtige fordi de hjælper mennesker, f.eks. dig selv i fremtiden, med at forstå koden og programmets formål.

Anbefaling

Start alle programmet med en eller flere linjers kommentarer som beskriver hvad programmet gør og hvorfor du har skrevet det.

I linje 3 bruges funktionen input() til at bede brugeren om et tal (input). "Write a number: " kaldes argumentet til funktionen input(), og vises på skærmen hvorefter programmet venter på brugeren skriver noget. Programmet fortsætter når brugeren trykker [Enter].

Hvad end brugeren skriver gemmes i en variabel a således brugerens input kan bruges igen senere i programmet, f.eks. i linje 5.

I linje 5 produceres en ny værdi baseret på brugerens input (gemt i variablene a og b). Den nye værdi produceres af operatoren + og gemmes i en ny variabel c. Variablene a og b i linje 5 kaldes også operander til operatoren +.

Endlig skrives eller printes resultatet på skærmen i linje 6 vha. funktionen print() som kaldes med variablen c som argument.

3.1.2. 13+37=1337? (datatyper)

Hvorfor giver programmet så output 1337 hvis brugeren skriver tallene 13 og 37 som input?

Det er fordi værdierne gemt i variabel a og b ikke er tal, men tekst og hvis operatoren + får tekst som operander så sætter den teksten sammen. Hvis operatoren havde fået heltallene 13 og 37 som operander havde den, som forventet, summeret tallene og produceret heltallet 50.

Hvordan konvertere man så teksten 13 og 37 til heltallene 13 og 37 således + kan producere heltallet 50? Det gøres med funktionen int() som konvertere sit argument fra tekst til et heltal (int er en forkortelse for integer, engelsk for heltal).

Opret et nyt Python-program med et navn såsom test_data_types.py og giv det følgende indhold og kør programmet.

Kildekode 3.3 test_data_types.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Test of data types (+ operator with text and numbers)
c = "13"
d = input("Type 37 and press [Enter]: ")
e = 37

cc = int(c)
dd = int(d)

print(c+d)
print(cc+dd)
print(cc+e)
print(c+e)
Kildekode 3.4 output (test_data_types.py)
1
2
3
4
5
6
7
8
Type 37 and press [Enter]: 37
1337
50
50
Traceback (most recent call last):
  File "test_data_types.py", line 11, in <module>
    print(c+e)
TypeError: can only concatenate str (not "int") to str

Outputtets linje 2 (Kildekode 3.4) er resultatet af programmets linje 9 (Kildekode 3.3) hvor + operatoren får "13" og "37" som operander og resultatet bliver teksten 1337.

I programmets linje 6 og 7 bruges funktionen int() til at konvertere teksten indeholdet i variablene c og d til heltal (integers) og resultatet gemmes i variablene cc og dd. Outputtets linje 3 er resultatet af programmets linje 10 hvor + får operanderne cc og dd (som nu er heltal) og resultatet bliver nu tallet 50 (selvfølgelig printet som teksten 50 på skærmen).

Outputtets linje 4 viser også 50, resultatet af cc+e (programlinje 11). Variablen e er direkte tildelt heltalsværdien 13 (i modsætning til tekstværdien "13").

I Python og mange andre programmeringssprog repræsentere "13" teksten 13 og kaldes en string (engelsk) eller tekststreng (dansk) hvorimod 13 repræsentere heltallet 13 (engelsk: integer).

Outputtets linje 5-8 viser en fejl TypeError: can only concatenate str (not "int") to str. Som fejlmeddelelsen angiver sker fejlen i programmets linje 12 hvor variablene c og e gives som operander til +. Det virker ikke fordi de to variable indeholder værdier af forskellige typer (str: tekst/string, int: heltal/integer). Mens man måske kan retfærdiggøre resultatet af teksten "2"+2 skulle være "4" eller 4, er det svært at retfærdiggøre "horse" + 7 ikke skal give en fejl.

Lad os bruge funktionen type() til at bekræfte datatypen af de variable vi lige har omtalt. Modificer programmet således:

Kildekode 3.5 test_data_types.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Test of data types (+ operator with text and numbers)
c = "13"
d = input("Type 37 and press [Enter]: ")
e = 37

cc = int(c)
dd = int(d)

print(c+d)
print(cc+dd)
print(cc+e)
#print(c+e) # TypeError from int + str

print(type(c))
print(type(d))
print(type(e))
print(type(cc))
Kildekode 3.6 output (test_data_types.py)
1
2
3
4
5
6
7
8
Type 37 and press [Enter]: 37
1337
50
50
<class 'str'>
<class 'str'>
<class 'int'>
<class 'int'>

Linje 12 er nu det man kalder udkommenteret, # som første tegn på linjen gør linjen ignoreres. Således resultere programmet ikke længere i en fejl. Bemærk der også er tilføjet en ekstra kommentar i slutningen af linjen som forklarer hvad der er specielt ved denne linje.

Linjerne 14-17 af benytter funktionen type() til at bestemme, eller undersøge, datatypen af programmets variable. Fra outputtets linje 5, som stammer fra programmets linje 14, kan vi se at c er en string (str er en forkortelse for string), og dermed er d også en string. e og cc er integers eller heltal (int er forkortelse for integer).

Udfordring

Gør outputtet fra testprogrammet mere læsevenligt ved at give print()-funktionen flere argumenter som vist her:

1
print("some descriptive text", some_variable)

Udfordring

Kommatal skrives i Python og mange andre programmeringssprog som 2.5 med decimal punktum, hvilket vidner om engelsk talende landes rolle i udviklingen af computeren. Kommatals datatype kaldes float og strings kan konverteres til floats vha. funktionen float().

Lav samme test som vi lige har lavet med strings og integers, med floats i stedet.

3.1.3. Næste version af lommeregnerprogrammet

Med vores nyfundne viden om datatyperne integers og strings (muligvis floats, se udfordringen ovenfor) kan vi nu sørge for at lommeregnerprogrammet bruger brugerens input som tal og ikke som tekst, altså få programmet til at producere outputtet 50 når brugeren giver 13 og 37 som input.

Udfordring

Benyt det du lige har lært om datatyper til at sørge for lommeregnerprogrammet benytter brugerens input som tal og producere 50 som output når brugeren giver 13 og 37 som input.

En mulig ny version af programmet (og løsning på ovenstående udfordring) kunne se ud på følgende måde, men der er mange andre løsninger som vil give præcist samme output:

Kildekode 3.7 calculator.py
1
2
3
4
5
6
7
8
# A simple calculator made as an introduction to programming

in1 = input("Write a number: ")
in2 = input("Write another number: ")
a = int(in1)
b = int(in2)
c = a+b
print(c)
Kildekode 3.8 output (calculator.py)
1
2
3
Write a number: 13
Write another number: 37
50

I løsningen her er der oprettet to ny variable in1 og in2 (in for input) til at gemme de rå brugerinput (strings) og de to variable a og b bruges nu til at gemme de konverterede værdier (integers), men man kunne også have skrevet a = int(input("Write a number: ")) og undladt variablene in1 og in2.

Lad os forbedre brugervenligheden af programemt ved at sørge for programmet fortæller brugeren hvad de to tal skal bruges til og læsevenligheden af outputtet ved at tydeliggøre hvilken linje der er resultatet:

Kildekode 3.9 calculator.py
1
2
3
4
5
6
7
8
9
# A simple calculator made as an introduction to programming
print("Calculate a + b:")
in1 = input("a=")
in2 = input("b=")
a = int(in1)
b = int(in2)
c = a+b
output = "{}+{}={}".format(a,b,c)
print(output)
Kildekode 3.10 output (calculator.py)
1
2
3
4
Calculate a + b:
a=13
b=37
13+37=50

Det pæne output, f.eks. 13+37=50 dannes i programmets linje 8 vha. metoden str.format() som erstattet {} i en string med noget andet. Første sæt {} i "{}+{}={}" erstattes med variablen a, næste sæt med b og sidste sæt med c. Resultatet af er en ny string som gemmes i variablen output.

3.1.4. Tillad gentagne udregninger

Lommeregnerprogrammet, som demonstreret i starten, giver brugeren mulighed for at lave mere end én udregning uden at skulle genstarte programmet. Vi har altså behov for at kunne gentage linje 2-9 (Kildekode 3.9) flere gange (evt. uendeligt mange gange). Det kan gøres med et såkaldt while-loop (eller while-løkke):

Kildekode 3.11 calculator.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# A simple calculator made as an introduction to programming

while True:
    print("Calculate a + b:")
    in1 = input("a=")
    in2 = input("b=")
    a = int(in1)
    b = int(in2)
    c = a+b
    output = "{}+{}={}".format(a,b,c)
    print(output)

Linje 3 while True: gentager de efterfølgende linjer 4-11 et uendeligt antal gange. Linjerne 4-11 bliver gentaget fordi koden på disse linjer er indrykket (engelsk: indented) i forhold til linje 3.

Prøv

Prøv at rykke forskellige linjers kode udenfor while-loopet. Enten ved at placere dem over linjen med while True: eller ved ikke at rykke dem ind.

Hvordan opfører programmet sig nu og hvorfor?

3.1.5. Tillad brugeren at stoppe programmet

Som programmet er nu kan programmet ikke stoppes af brugeren, det bliver ved med at tilbyde udregning af a+b i det uendelige.

Et loop, f.eks. vores while-loop, kan stoppes vha. Python-udsagnet (engelsk: statement) break. Hvis Python udfører en linje hvorpå der står break så stopper det loop Python er i færd med at udføre. Lad os prøve at indsætte break et sted i vores loop:

Kildekode 3.12 calculator.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# A simple calculator made as an introduction to programming

while True:
    print("Calculate a + b:")
    in1 = input("a=")
    in2 = input("b=")
    break
    a = int(in1)
    b = int(in2)
    c = a+b
    output = "{}+{}={}".format(a,b,c)
    print(output)

Nu stopper programmet selvfølgelig midt i en interaktion med brugeren, det stopper efter at have spurgt om de to inputs. Uanset hvor vi placere break (prøv evt. andre placeringer) så vil programmet enten stoppe et uhensigtsmæssigt sted eller stoppe efter en enkelt udregning.

Der er brug for en måde kun at udføre kommandoen break under nogle bestemte forudsætninger, f.eks. hvis bruger skriver quit i stedet for et tal når programmet spørger til tallene a og b. En såkaldt if-sætning kan bruges til det formål:

Kildekode 3.13 calculator.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# A simple calculator made as an introduction to programming

while True:
    print("Calculate a + b:")
    in1 = input("a=")
    in2 = input("b=")

    if in1 == "quit":
        break

    a = int(in1)
    b = int(in2)
    c = a+b
    output = "{}+{}={}".format(a,b,c)
    print(output)

Linje 8 sammenligner indholdet i variablen in1 med stringen "quit", hvis de er ens så udføres linje 9 (break). Således lukker programmet nu hvis brugeren skriver quit når programmet beder om en værdi for a (dog først efter brugeren også har angivet en værdi for b).

Udfordring

Omstruktruer koden således programmet lukker så snart brugeren har skrevet quit og trykket [Enter].

Udvid programmet således programmet lukker uanset om brugeren angiver a eller b til quit.

Udfordring

Prøv at placere if-sætningen efter en af linjerne som konvertere in1 og in2 til integers.

Hvad sker der nu?

3.1.6. Arbejdsmetoden: Trinvis Forbedring

Måske har du lagt mærke til, at vi indtil nu har lavet mange forskellige udgaver af lommeregnerprogrammet som gradvist kan mere og mere, og som gradvist ligner eksemplet vist i starten mere og mere. Det er en meget væsentlig pointe, at programmer ikke skrives i deres fulde endelig version uden at blive testet og omstruktureret under vejs.

figur 3.1, en video af et lommeregner program i brug, er vores mål eller nogle andres krav (engelsk: requirement) til os. Videoen er en optagelse af et Python-program i brug, men den kunne lige så vel være konstrueret i et tegneprogram, eller videoen kunne erstattes af en tekstbeskrivelse, af en person som ønsker sig et lommeregner program, men som ikke selv er i stand til at programmere det. Ud fra kravet eller kravene kan vi opstille nogle specifikationer, f.eks.:

  1. Brugeren skal kunne vælge en regneoperation og operander (tal) til regneoperationen.

  2. Programmet skal printe et letlæseligt resultat af regneoperationen.

  3. Når brugen har udført en beregning skal brugeren tilbydes at lave en ny beregning.

  4. Programmet skal vise en letforståelig fejlmeddelelse når/hvis brugeren angiver en ugyldig operand, f.eks. skriver ordet hest i stedet for et tal når brugeren bedes angive a eller b til udregning a+b.

Nogle af specifikationerne beskriver ikke præcist kravene, f.eks. viser figur 3.1 at brugeren skal kunne vælge regneoperation ved at skrive et tal angivet i en oversigt over regneoperationerne, men det fremgår ikke af specifikationerne.

Kildekode 3.1, Kildekode 3.7 og Kildekode 3.9 med flere kaldes implementeringer af (lommeregner)specifikationerne. Ganske som nogle af specifikationerne ikke præcist beskriver kravene, så opfylder nogle af implementeringerne ikke specifikationerne, f.eks. opfylder implementering Kildekode 3.11 specifikation 2 og 3, men kun dele af 1 og slet ikke 4.

Vores udviklingsprocess eller arbejdsprocess består altså af en række skridt eller delprocesser som gentages indtil vi løber tør for tid eller tilfreds med programmet:

  • Omdannelse af krav til specifikationer.

  • Implementing af specifikationerne.

  • Test af implementeringerne (tjek programmet kan køre/afvikles og overholder specifikationerne).

  • Forbedring af specifikationer som ikke præcist beskriver kravene.

  • Forbedring af implementeringer som ikke overholder specifikationerne.

Udviklingsprocessen eller arbejdsmetoden kaldes trinvis forbedring (engelsk: stepwise improvement). Det er en iterativ process fordi de samme skridt eller delprocesser gentages igen og igen.

Udfordring

  • Lav flere specifikationer (end dem som allerede nævnes i teksten) ud fra kravet/kravene.

  • Find flere specifikationer (end dem som allerede nævnes i teksten) som bør forbedres for at beskrive kravene mere præcist.

  • Find flere eksemler (end dem som nævnes i teksten) på implementeringer (fra kapitlet her) som ikke overholder en eller flere specifikationer.

3.1.7. Håndtering af fejl

Prøv at afvikle programmet fra Kildekode 3.13 og angiv enten a eller b til et ord som syv eller hest. Du vil da se programmet stoppe med en fejl som denne:

1
2
3
4
Traceback (most recent call last):
  File "<filename>", line <line number>, in <module>
    a = int(in1)
ValueError: invalid literal for int() with base 10: 'hest'

Som det er krav og som en af vores specifikationer lyder skal denne brugerfejl, som resulterer i en teknisk fejl, håndteres og brugeren se en letlæselig fejlbesked. Dette kan gøres vha. en såkaldt try-except konstruktion. Vi ved fra de fejlmeddelelser vi har set, at det er linjerne som konvertere brugerens input til heltal, linjer hvor funktionen int() kaldes, som er årsag til fejlen. Derfor placeres de linjer i try-except konstruktionen. Linje 11-15 skal læses som: prøv at udføre linje 12 og 13, hvis en af de linjer resultere i en fejl af typen ValueError så udfør linje 15.

Kildekode 3.14 calculator.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# A simple calculator made as an introduction to programming

while True:
    print("Calculate a + b:")
    in1 = input("a=")
    in2 = input("b=")

    if in1 == "quit":
        break

    try:
        a = int(in1)
        b = int(in2)
    except ValueError: 
        print("Error: a or b was an invalid number")

    c = a+b
    output = "{}+{}={}".format(a,b,c)
    print(output)
Kildekode 3.15 output (calculator.py)
1
2
3
4
5
6
7
8
Calculate a + b:
a=2
b=hest
Error: a or b was an invalid number
Traceback (most recent call last):
  File "calc7.py", line 17, in <module>
    c = a+b
NameError: name 'b' is not defined

Som ses fra outputtet har vi lavet en implementering som opfylder en specifikation, men som nu gør vi ikke længere opfylder en anden specifikation: brugeren ser nu en let læselig fejlmeddelelse hvis der indtastes noget som ikke er et tal, men brugeren ser en anden svært læselig fejlmeddelse og brugeren får ikke mulighed for at udføre en ny beregning (prøve igen).

Den nye fejlmeddelse, af typen NameError, kommer fordi variablen b som forsøges oprettet i linje 13 ikke bliver oprettet (den fejler med ValueError). Når programmet fortsætter efter try-except konstruktionen så forsøges linje 17 udført og her skal variablen b, som nu ikke er defineret, bruges.

Problemet med der opstår en ny fejl kan håndteres på mange måder, en af dem kunne være vha. break:

Kildekode 3.16 calculator.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# A simple calculator made as an introduction to programming

while True:
    print("Calculate a + b:")
    in1 = input("a=")
    in2 = input("b=")

    if in1 == "quit":
        break

    try:
        a = int(in1)
        b = int(in2)
    except ValueError: 
        print("Error: a or b was an invalid number")
        break

    c = a+b
    output = "{}+{}={}".format(a,b,c)
    print(output)

Denne løsning bryder dog stadig med en af specifikationerne, nemlig den at brugeren skal tilbydes at udføre en ny udregning. Nu ser vi ganske vist ikke ValueError-fejlen, men programmet stopper.

Løsningen er at bytte break ud med continue som betyder stop nuværende iteration af loopet og fortsæt til den næste:

Kildekode 3.17 calculator.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# A simple calculator made as an introduction to programming

while True:
    print("Calculate a + b:")
    in1 = input("a=")
    in2 = input("b=")

    if in1 == "quit":
        break

    try:
        a = int(in1)
        b = int(in2)
    except ValueError: 
        print("Error: a or b was an invalid number")
        continue

    c = a+b
    output = "{}+{}={}".format(a,b,c)
    print(output)

Udfordring

Udvid programmet fra Kildekode 3.17 således der gives en særskilt fejlmeddelse for fejlindtastning ved a og b.

Udfordring

Brug str.format() metoden til at få fejlmeddelelsen til at se ud som Error choosing operand 'a', your value 'hest' is not a valid number.

Fodnoter

1

Tegnet # hedder ikke et hashtag. På engelsk hedder det hash (symbol), number sign eller pound sign, på dansk hedder det havelåge. eller dobbeltkryds.