You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 4 Next »

I rpncalc3-eksemplet ser vi mer på funksjoner, spesielt hvilke vi velger vi å definere ift. i rpncalc2.py. I tillegg viser vi noen flere Python-teknikker som er nyttige å kjenne til.

isOperand

isOperand-funksjonen har en svakhet, nemlig at den gjetter at argumentet er et tall utelukkende på grunnlag av første tegn. En litt bedre implementasjon bruker float-funksjonen for å sjekke om konverteringen går. Denne teknikker er forøvrig foreslått i følgende stackoverflow-spørsmål: http://stackoverflow.com/questions/354038/how-do-i-check-if-a-string-is-a-number-in-python 

def isOperand(token):
    try:
        float(token)
        return True
    except ValueError:
        return False

Denne isOperand-implementasjonen sjekker om token er et gyldig desimaltall ved å kalle float og se om den utløser et såkalt unntak (tilsvarende å kræsje). Ved å bruke try-except-konstruksjonen så unngår en at programmet kræsjer og kan returnere True dersom det ikke utløses noe unntak og False dersom det gjør.

Konstruksjonen try-except fungerer omtrent slik: Koden mellom try og except kjøres som vanlig, men hvis det underveis utløses et unntak av typen angitt i except-delen (her: unntakstypen ValueError), så kan det fanges opp og koden i except-delen kjøres, før koden etter try-except kjører som normalt. (Akkurat her returneres jo en verdi i begge tilfeller, så det er ikke noe kode etter try-except-konstruksjonen.)

I dette tilfellet vil en normal utførelse, hvor float returnerer en verdi (brukes ikke i dette tilfellet), gjøre at return True bli utført. Dersom unntak utløses fordi float ikke gjenkjenner token som et (desimal)tall, så blir return False utført.

popOperands

I rpncalc2.py så definerte vi hasOperands, som sa om det var nok operander i operands-lista. Dersom så var tilfelle, så ble de i praksis pop-et rett etterpå. I dette eksemplet så velger vi å gjøre begge deler i samme funksjon, kalt popOperands. Funksjonen vil altså fjerne og returnere n operander, dersom det er nok av dem, ellers returneres en verdi som angir "ingenting".

operands = []
...
def popOperands(n):
    size = len(operands)
    if size >= n:
        result = operands[size - n : size]
        global operands
        operands = operands[0 : size - n]
        return result
    else:
        return None

popOperands tar inn antall operander som skal pop-es som argument.

Først sjekker den om det er nok operander i operands-lista. I så fall så henter lager den en ny liste bestående av de siste n elementene i lista og husker dem i result-variablen. Notasjonen list[n : m] en hendig måte å lage en ny liste bestående av elementene fra n til (men ikke med) m fra list. Dersom en skal hente uten resten av lista fra element n, som en gjør her, så kan bruke kortformen list[n :].

Etter at result er tilordnet, så skal vi fjerne de samme elementene fra operands-lista. Her velger vi å ikke endre lista, men å lage en helt ny liste som operands tilordnes. En skulle tro at det var nok å skrive operands = ... men det vil opprette en ny operands-variabel i funksjonsarket, tilsvarende result. Derfor har vi først setningen global operands, fordi det deklarerer at operands er ment å være en variabel deklarert utenfor funksjonen, altså i rpncalc3-arket.

Til slutt returneres result-lista, altså de ønskede operandene, som ikke lenger finnes i operands-lista.

Dersom det ikke er nok operander i lista, så returneres verdien None, som brukes for å angi nettopp "ingen verdi" eller "ingenting". Denne kan sjekkes med is None, som vi skal se lenger ned.

pushOperand

def pushOperand(op):
   operands.append(op) 

Motstykket til popOperands er pushOperand, som rett og slett legg argumentet til bakers i operands-lista. Den er mest med for symmetriens skyld, men også for å sikre at all tilgang til operands-lista fra while-løkka skjer gjennom funksjoner. Merk at denne ikke returnerer noen verdi, side-effekten er faktisk hele effekten av funksjonen.

Setningen i while-løkka som legger en ny-innlest operand på stacken må skrives om til å kalle pushOperand-funksjonen. Prøv å se om du får det til selv, før du ser i koden nederst på siden!

plus og minus

def plus():
    ops = popOperands(2)
    if not (ops is None):
        pushOperand(ops[0] + ops[1])

def minus():
    ops = popOperands(2)
    if not (ops is None):
        pushOperand(ops[0] - ops[1])
plus- og minus-funksjonene skrives om til å bruke popOperands. Først kalles popOperands med det antall operander som trengs og resultatet tilordnes ops-variablen. Så sjekkes at ops ikke er None med not (ops is None) før resultatet beregnes og push-es på stacken (legges til operands-lista) med pushOperand.

printOperands

Som nevnt over i forklaringen til pushOperand, så kan det være et poeng at all tilgang til en datastruktur som operands-lista skjer gjennom definerte funksjoner. Dette gir litt bedre oversikt over hvor i koden operands leses eller endres, og gjør feilsøking enklere. Det gjenstår nå bare én setning i while-løkka som leser eller endrer operands, hvilken? Finn den du, og definer og bruk en passende funksjon!

Hele rpncalc3.py

Og her er for ordensskyld hele koden:

# rpncalc3.py
operands = []

def isOperand(token):
    try:
        float(token)
        return True
    except ValueError:
        return False

def printOperands():
    print(operands)

def popOperands(n):
    size = len(operands)
    if size >= n:
        result = operands[size - n : size]
        global operands
        operands = operands[0 : size - n]
        return result
    else:
        return None

def pushOperand(operand):
    operands.append(operand)

def plus():
    ops = popOperands(2)
    if not (ops is None):
        pushOperand(ops[0] + ops[1])

def minus():
    ops = popOperands(2)
    if not (ops is None):
        pushOperand(ops[0] - ops[1])

# the functions above handles all access to the operand stack
# the code below has no references to the operand stack, only to the functions above

def main():
	while (True):
    	printOperands()
    	token = raw_input(" > ")
    	if isOperand(token):
    	    operand = float(token)
    	    pushOperand(operand)
    	elif token == "exit":
    	    break
    	elif token == "+":
    	    plus()
    	elif token == "-":
    	    minus()
    	else:
    	    print("Unsupported operator: " + token)
	print("program exited")

main()
  • No labels