Presedens og evalueringsrekkefølge


Operator-presedens

En av de aller vanligste feilene som gjøres (f. eks. på eksamen) er at man gjør feil antakelse om hvilken rekkefølge operasjoner i et uttrykk utføres i. For aritmetikk-operasjoner gjelder de samme reglene som i matematikken.

print("Presedens:")
print(2+3*4)  # gir 14, ikke 20   ( * har høyere presedens enn + )
print(5+4%3)  # gir  6, ikke 0    ( % har høyere presedens enn + )
print(2**3*4) # gir 32, ikke 4096 (** har høyere presedens enn * )

print()

print("Assosiativitet:")
print(5-4-3)   # gir -2, ikke 4 (- assosierer venstre-til-høyre)
print(4**3**2) # gir 262144, ikke 4096 (** assosierer til høyre-til-venstre)

Vi gir her en oversikt over noen operatorer rangert fra høyeste til laveste presedens. Parenteser vil alltid overstyre presedens og assosiativitet, og er derfor øverst på listen. Alle operatører untatt ** og relasjonene assosierer venstre-til-høyre dersom det er aktuelt.

Operatorer
()Parentes. Det som er mellom parentesene evalueres før en operator utenfor parentesen.
**Eksponentiering. Assosierer høyre-til-venstre.
* / // %Multiplikasjon, divisjon og modulo.
+ -Addisjon og subtraksjon.
< <= > >= == != in not inRelasjoner. Disse vil ikke assosiere verken til høyre eller venstre; dersom man har flere slike etter hverandre vil de komponeres som en konjunksjon i stedet. For eksempel, -1 < 0 == False gir det samme svaret som -1 < 0 and 0 == False, og altså ikke det samme som (-1 < 0) == False slik man ellers kunne trodd.
notLogisk negasjon.
andLogisk konjunksjon.
orLogisk disjunksjon.
if elseBetinget verdi.

Flere eksempler (se om du kan forutsi hva hvert uttrykk evaluerer til før du kjører koden og ser fasiten):

print(True or True and False) 
print(not False or True)
print(not (False or True))
print()
print(2 < 3 < 4)
print(not 3 < 2 < 1)
print(not 3 < 2 and 2 < 1)
print()
print("b" in "box")
print("b" in "box" == True) # (Brython/nettleser krasjer, men prøv i python)
print("a" and "b" in "box")
print("a" or "z" in "box")
Evalueringsrekkefølge og utsatt evaluering
def en():
    print("en", end=" ")
    return 1

def to():
    print("to", end=" ")
    return 2

def tre():
    print("tre", end=" ")
    return 3

def fire():
    print("fire", end=" ")
    return 4

print("Uttrykk evalueres som hovedregel fra venstre mot høyre")
print(en() * to() + tre()) # en to tre 5
print(en() + to() * tre()) # en to tre 7
print(en() + to() ** tre()) # en to tre 9
print(en()**to()**tre()) # en to tre 1
print(en() and to() and tre()) # en to tre 3
print()
print("Ingen regel uten unntak: ")
print(en() < to() < tre()) # to en tre True
print(en() if to() < tre() else fire()) # to tre en 1

Legg merke til at fire() aldri blir kjørt på den siste linjen over. Dette kalles utsatt evaluering (engelsk: lazy evaluation), og skjer fordi python prøver å unngå å kjøre kode dersom den ikke trenger det.

Når man benytter logiske konjunksjoner (and) eller disjunksjoner (or) skjer det også en variant av dette, som vi kaller kortslutningsevaluering (engelsk: short-circuit evaluation):

def ja():
    return True

def nei():
    return False

def krasj():
    return 1/0 # krasjer!

print(nei() and krasj()) # False
print(krasj() and nei()) # Krasjer!
print (ja() and krasj()) # Ville også krasjet om vi kom hit

Det samme for disjunksjoner (or):

def ja():
    return True

def nei():
    return False

def krasj():
    return 1/0 # krasjer!

print(ja() or krasj()) # True
print(krasj() or ja()) # Krasjer!
print (nei() or krasj()) # Ville også krasjet om vi kom hit

Vi kan utnytte kortslutningsevaluering for å unngå å krasje.

def solid_sjekk(tekst):
    """ Sjekker om første bokstav i teksten er 'a' """
    return len(tekst) > 0 and tekst[0] == 'a'

def ubeskyttet_sjekk(tekst):
    """ Sjekker om første bokstav i teksten er 'a' """
    return tekst[0] == 'a'

print(solid_sjekk("ananas")) # True
print(ubeskyttet_sjekk("ananas")) # True
print()
print(solid_sjekk("banan")) # False
print(ubeskyttet_sjekk("banan")) # False
print()
print(solid_sjekk("")) # False
print(ubeskyttet_sjekk("")) # Krasjer (ops!)

I kombinasjon med at at de logiske operasjonene assosierer venstre-til-høyre, vil en rekke med konjunksjoner (and) returnere den første (lengst til venstre) falsy verdien i rekken; eventuelt den siste verdien dersom det ikke var noen falsy verdier i kjeden:

def ja():
    print("Ja-funksjonen blir kalt")
    return True

print(3 and "False" and None and -2 and ja() and False) # None
print(3 and ja() and "Hurra!") # Hurra! (kjører også ja-funksjonen)

En rekke med disjunksjoner vil på samme måte returnere den første truthy verdien:

def ja():
    print("Ja-funksjonen blir kalt")
    return True

print(0 or "" or 10 or False or None or ja()) # 10