Dette kode-eksemplet implementerer en enkel kalkulator og illustrerer ulike aspekter ved (kjøring av) Python-programmer, og er første del av RPNCalc-eksemplet.

rpncalc1.py

Nedenfor vises en enkel Python-implementasjon, med forklaringer til høyre.

# rpncalc1.py
operands = []

while (True):
    print(operands)
    token = raw_input(" > ")
    if token[0].isdigit():
        operand = float(token)
        operands.append(operand)
    elif token == "exit":
        break
    elif token == "+":
        operands.append(operands.pop() + operands.pop())
    elif token == "-":
        operands.append(operands.pop() - operands.pop())
    else:
        print("Unsupported operator: " + token)
print("program exited")

#-tegnet angir at resten av linja er en kommentar. Akkurat denne brukes her for å angi hvor det er lurt å legge koden.

Et Python-program utføres ovenifra og nedeover, og derfor vil et program typisk først deklarere viktige variabler før hovedlogikken kommer. Her tilordnes operands-variablen til en tom liste.

while-løkka (se while-kontrollstrukturen) brukes for å gjenta en blokk med kode, inntil en bestemt betingelse er oppfylt. Her er betingelsen True, dvs. alltid sann, så løkke vil aldri stoppe, med mindre den avbrytes innenfra! Legg forøvrig merke til hvordan en i Python bruker innrykk (mellomrom først på linja) for å angi hvilke setninger som hører til blokken.

print skriver ut operand-stacken, så brukeren ser hva kalkulator-tilstanden er.

raw_input brukes deretter for å lese inn en "noe" fra brukeren, som er er ment å være enten en operand eller en operator.

Vi må først avgjøre om det brukeren has skrevet inn er en operand eller operator. Her bruker vi en enkel test på om første tegn av token, angitt med token[0], er et siffer. Hvis det er tilfellet så konverteres token til et desimaltall med float-funksjonen, som så legges til operands-lista med append, altså bakerst, så det fungerer som en stakk.

Dersom token ikke er et tall, så sjekkes det om programmet skal avsluttes, og bruker i så fall break for å bryte ut av løkka. Videre håndteres hver operator i sin egen elif-gren (se if-kontrollstrukturen). Her har vi begrenset oss til pluss (+) og minus (-).

For hver av disse operatorene så tas to operander av lista med pop, operasjonen utføres og resultatet legges tilbake med append.

Den siste else-grenen sier fra til brukeren at token ikke ble gjenkjent som operator.

Kjør koden og se at det virker ved å skrive inn ulike operander og operatorer.

Vi har plantet (minst) to feil i koden: Den ene har med manglende operander å gjøre, den andre med håndteringen av minus. Prøv å se om du finner feilene og en måte å rette dem på! Du finner et (forslag til) svar i rpncalc2-eksemplet som bygger videre på dette.

Håndtering av variabler og verdier

Koden over bruker flere variabler for å holde rede på tilstanden til programmet. Den viktigste variablen er operands, som refererer til lista med operander, som utvides og minskes ved gjennomgang av løkka. I tillegg har vi de to variablene token og operand, som holder det brukeren skrev inn som henholdvis tekst (string) og konvertert til desimaltall (float). Mens det er viktig at operands-variablen beholder sin verdi for hver runde i løkka, så er det ikke viktig at token og operand gjør det. Tvert imot, så tenker en gjerne at disse er lokale for løkka, selv om Python ikke behandler dem annerledes enn operands i så måte. I andre språk, f.eks. Java, så er det mulig å opprette (deklarere) såkalt blokk-lokale variabler.

La oss se på hvordan variablene hånderes i detalj, når programmet utføres.

rpncalc1Ved oppstart av programmet tanker vi oss at det opprettes et slags notatark, med samme navn som programmet. Fra starten er arket tomt, som vist til venstre.
rpncalc1operands = []Når tilordningen til operands utføres, så noteres navnet og verdien på notatarket.
rpncalc1operands = []token = "1.0"

Når raw_input utføres, så blir programmet liggende og vente til brukeren har skrevet inn tekst, og denne teksten tilordnes så til token. Siden token ikke finnes, så utvides notatarket med denne variablen og tilhørende verdi.

Her antar vi at brukeren skriver inn 1.0.

rpncalc1operands = []token = "1.0"operand = 1.0token har nå verdien 1.0 (som tekst, altså en string) og denne lar seg konvertere til desimaltall (altså en float-verdi). Dermed blir operand-tilordningen utført for første gang, så operand får verdien 1.0.
rpncalc1operands = [1.0]token = "1.0"operand = 1.0På linjen deretter så legges operand til operands-lista. Her leses operands-variablen, men den endres ikke, det er det jo lista som gjør.
rpncalc1token = "1.0"operand = 1.01: list1.0operands

For å gjøre poenget med at det er lista som operands refererer til som endres og ikke operands selv, så kan vi tegne tilstanden litt annerledes. Lista tegnes som en egen boks med nr. (1) og type (list) med verdiene inni seg. operands-variablen tegnes som en pil med navn på, som peker til liste-boksen. En slik pil kalles gjerne en referanse.

Når operands-variablen leses, så følges pila, så det er på en måte referansen til list-boksen som er verdien til operands. Derfor er det list-boksen som endres ved kall til operands.pop() og operands.append(...) og ikke operands selv.

rpncalc1token = "2.0"operand = 2.01: list1.0, 2.0operands

Vi antar i andre runde av løkka at brukeren skriver inn 2.0. Siden token og operand allerede finnes, så vil disse få nye verdier. operands refererer fortsatt til samme liste, som igjen er utvidet med en ny operand.

rpncalc1token = "+"operand = 2.01: list3.0operands

Når brukeren i tredje runde i løkka skriver inn +, så utløses ValueError-unntaket før tilordningen av operand rekker å bli utført. Og derfor endres ikke operand, kun token. operands endrer fortsatt ikke verdi, men lista den refererer til endrer seg.

Hvis en hadde utført linja operands.append(operands.pop() - operands.pop()) i små steg, så ville en sett at lista faktisk endrer seg tre ganger: Først fra [1.0, 2.0] til [1.0] når pop kalles første gang, så fra [1.0] til [] når pop kalles andre gang og så til [3.0] når svaret legges til med append.