Boolske uttrykk


Boolske verdier er også verdier

Typen bool har nøyaktig to verdier: True og False. Dette er verdier på samme måte som 42 er en verdi av typen int og "hei" er en verdi av typen str. Når vi skriver True direkte i koden, er det en literal, akkurat som når vi skriver 42 eller "hei".

print(type(42))     # <class 'int'>
print(type("hei"))  # <class 'str'>
print(type(True))   # <class 'bool'>
print(type(False))  # <class 'bool'>

Merk at True og False skrives med stor forbokstav og uten anførselstegn. "True" er en streng, ikke en boolsk verdi, og true (med liten t) er ikke noe som finnes, eller eventuelt et veldig forvirrende variabelnavn.

En sammenligning som age >= 18 er et uttrykk, på samme måte som age + 1 er et uttrykk. Forskjellen er bare hvilken type verdi uttrykket evaluerer til: age + 1 evaluerer til en int, mens age >= 18 evaluerer til en bool.

age = 20
print(age + 1)    # 21
print(age >= 18)  # True

Siden boolske verdier er helt vanlige verdier, kan vi gjøre det samme med dem som med andre verdier. Vi kan for eksempel lagre dem i variabler, skrive dem ut, legge dem i lister, og bruke dem som argument til og returverdi fra funksjoner.

age = 20
is_adult = age >= 18     # Variabelen is_adult får verdien True
print(is_adult)          # True

answers = [True, False, age > 100]
print(answers)           # [True, False, False]

if is_adult:             # Betingelsen er variabelen is_adult, som er True
    print("Velkommen inn!")

En betingelse i en if-setning trenger altså ikke å være en sammenligning. Det kan være ethvert uttrykk, inkludert en variabel, en literal eller et funksjonskall. Python evaluerer uttrykket, og ser deretter på hvilken verdi det ble.

def is_even(n):
    return n % 2 == 0    # Returnerer resultatet av uttrykket: True eller False

print(is_even(4))  # True
print(is_even(7))  # False

x = 10
if is_even(x):
    print(x, "er et partall")
Evaluering steg for steg

Et boolsk uttrykk evalueres på samme måte som et aritmetisk uttrykk: ett deluttrykk av gangen, og hvert deluttrykk erstattes med verdien det evaluerer til. Når vi regner ut 2 + 3 * 4 regner vi først ut 3 * 4 og erstatter det med 12, slik at vi får 2 + 12, og deretter 14.

Tilsvarende for et boolsk uttrykk:

x = 3
(x < 5) and not (x == 3)
# Steg 1: evaluer  x < 5    -> True and not (x == 3)
# Steg 2: evaluer  x == 3   -> True and not True
# Steg 3: evaluer  not True -> True and False
# Steg 4: evaluer  and      -> False

Hvilken rekkefølge operatorene utføres i bestemmes av presedens (se presedenstabellen). For de logiske operatorene gjelder:

Dette betyr at uttrykket under har «usynlige parenteser» slik som vist i kommentarene:

x = 7
print(x > 5 or x < 0 and not x == 7)
# Med usynlige parenteser:
#   (x > 5) or ((x < 0) and (not (x == 7)))
# Steg for steg:
#   True or (False and (not True))
#   True or (False and False)
#   True or False
#   True

Legg merke til at and binder sterkere enn or, på samme måte som * binder sterkere enn +. Uttrykket True or True and False betyr derfor True or (True and False), og ikke (True or True) and False.

print(True or True and False)    # True
print((True or True) and False)  # False

Selv om det ikke alltid er nødvendig, er det god stil å bruke parenteser for å gjøre det tydelig hva man mener.

Kjedede sammenligninger

I Python kan vi kjede sammen flere sammenligninger, slik vi er vant med fra matematikk. Et uttrykk som 0 < x < 10 tolkes som (0 < x) and (x < 10).

x = 5
print(0 < x < 10)            # True
print((0 < x) and (x < 10))  # True (det samme)

x = 15
print(0 < x < 10)            # False
Logikk og sannhetstabeller

Operatorene not, and og or er i logikken definert ved sine sannhetstabeller (se også logiske operatorer). En sannhetstabell viser resultatet for hver mulige kombinasjon av verdier.

x not x
False True
True False
x y x and y x or y
False False False False
False True False True
True False False True
True True True True

Kort oppsummert:

Sannhetstabeller for sammensatte uttrykk

Vi kan lage sannhetstabeller for større uttrykk ved å regne ut én kolonne for hvert deluttrykk. La oss se på uttrykket not x or y. Siden not har høyere presedens enn or, betyr dette (not x) or y.

x y not x (not x) or y
False False True True
False True True True
True False False False
True True False True
De Morgans lover

To uttrykk er ekvivalente dersom de har samme sannhetstabell. To nyttige ekvivalenser er De Morgans lover:

Med andre ord: når vi flytter en not inn i en parentes, må vi bytte and med or og omvendt. Dette er nyttig for å gjøre negerte betingelser enklere å lese.

Norsk har et eget ord for den andre loven: «verken … eller». «Jeg drikker verken kaffe eller te» betyr «det er ikke sant at jeg drikker kaffe eller te», altså not (coffee or tea). Det betyr det samme som «jeg drikker ikke kaffe, og jeg drikker ikke te», altså (not coffee) and (not tea).

age = 10
# Disse to betingelsene er ekvivalente
if not (age >= 18 and age <= 67):
    print("Utenfor arbeidsfør alder")
if age < 18 or age > 67:
    print("Utenfor arbeidsfør alder")
Kortslutning

Når Python evaluerer x and y, evalueres x først. Hvis x er False, er svaret uansett False, uavhengig av hva y er. Python gidder da ikke å evaluere y i det hele tatt. Tilsvarende for x or y: hvis x er True, er svaret uansett True, og y blir ikke evaluert. Dette kalles kortslutning (engelsk: short-circuit evaluation).

Vi kan se at høyresiden ikke blir evaluert ved å la høyresiden være noe som ville krasjet programmet, for eksempel en divisjon med null:

print(False and 1/0)  # False (1/0 blir aldri evaluert)
print(True or 1/0)    # True (1/0 blir aldri evaluert)
print(True and 1/0)   # Krasjer med ZeroDivisionError

Vi kan også se det ved å bruke funksjoner som skriver ut noe når de kalles:

def yes():
    print("  yes() ble kalt")
    return True

def no():
    print("  no() ble kalt")
    return False

print("no() and yes():")
print(no() and yes())   # Bare no() blir kalt
print()
print("yes() or no():")
print(yes() or no())    # Bare yes() blir kalt
print()
print("yes() and no():")
print(yes() and no())   # Begge blir kalt
Kortslutning som vakt

Kortslutning er nyttig for å beskytte et uttrykk som bare gir mening dersom en annen betingelse er oppfylt. Venstresiden fungerer da som en vakt for høyresiden.

x = 0
y = 5
# Hvis x er 0, blir y / x aldri evaluert, og programmet krasjer ikke
if x != 0 and y / x > 2:
    print("y er mer enn dobbelt så stor som x")
else:
    print("Kan ikke dele på 0, eller y er ikke stor nok")
names = []
# Hvis listen er tom, blir names[0] aldri evaluert (som ville gitt IndexError)
if len(names) > 0 and names[0] == "Ada":
    print("Ada er først i køen")
else:
    print("Ada er ikke først i køen")

Rekkefølgen er viktig! Dersom vi bytter om på venstre- og høyresiden, krasjer programmet:

names = []
if names[0] == "Ada" and len(names) > 0:  # Krasjer med IndexError
    print("Ada er først i køen")
Truthy og falsy verdier

En betingelse i en if-setning trenger ikke å evaluere til en boolsk verdi. Dersom verdien er av en annen type, vil Python likevel tolke den som enten True eller False. Verdier som tolkes som True kalles truthy, mens verdier som tolkes som False kalles falsy.

if "Hello":
    print("Strengen 'Hello' er truthy")

if 0:
    print("Dette skrives ikke ut, for 0 er falsy")

if [1, 2, 3]:
    print("En liste med elementer er truthy")

if []:
    print("Dette skrives ikke ut, for en tom liste er falsy")

Funksjonen bool konverterer en verdi til den boolske verdien den tolkes som:

print(bool(42))       # True
print(bool(-1))       # True
print(bool(0))        # False
print(bool(0.0))      # False
print()
print(bool("hei"))    # True
print(bool(" "))      # True (et mellomrom er ikke tomt)
print(bool("0"))      # True (en streng med tegnet 0 er ikke tom)
print(bool("False"))  # True (en streng med ordet False er ikke tom)
print(bool(""))       # False
print()
print(bool([1, 2]))   # True
print(bool([0]))      # True (listen har ett element, selv om det er 0)
print(bool([False]))  # True
print(bool([]))       # False
print()
print(bool(None))     # False

Verdier som er falsy er:

Nesten alle andre verdier er truthy. En god tommelfingerregel er at «null» og «tomt» er falsy, mens alt annet er truthy.

Operatoren not fungerer også med truthy og falsy verdier, og returnerer alltid en boolsk verdi:

print(not 0)       # True
print(not 42)      # False
print(not "")      # True
print(not "hei")   # False
print(not [])      # True

En vanlig bruk av truthy og falsy er å sjekke om en liste eller streng er tom:

names = []
if not names:
    print("Listen er tom")

name = input("Hva heter du? ")
if name:
    print(f"Hei, {name}!")
else:
    print("Du skrev ikke noe navn")
Hva and og or egentlig returnerer

Så langt har vi sett på and og or med boolske verdier. Men hva skjer om vi bruker dem med andre verdier?

print(0 or 5)        # 5
print(3 or 5)        # 3
print(0 and 5)       # 0
print(3 and 5)       # 5
print("" or "tom")   # tom

Resultatet er verken True eller False, men en av de opprinnelige verdiene! For å forstå hvorfor, må vi kombinere det vi vet om kortslutning og truthy/falsy verdier. Python evaluerer venstresiden først. Dersom venstresiden alene avgjør svaret, returneres venstresiden. Ellers er det høyresiden som avgjør svaret, og da returneres høyresiden.

Med andre ord returnerer and og or alltid enten venstresiden eller høyresiden, uendret. Det er ikke nødvendigvis en boolsk verdi. Dersom vi bruker resultatet som en betingelse, vil det tolkes som truthy eller falsy, og da får vi akkurat samme oppførsel som i sannhetstabellen. Derfor merker vi vanligvis ikke dette.

Vi kan uttrykke det samme med if-else -uttrykk:

x and y   # gir det samme som:   x if not x else y
x or y    # gir det samme som:   x if x else y

La oss se på noen eksempler steg for steg:

0 and 5
# 0 er falsy, så svaret er avgjort; venstresiden returneres
# -> 0

3 and 5
# 3 er truthy, så høyresiden avgjør svaret; høyresiden returneres
# -> 5

0 or "" or "standard"
# or assosierer venstre til høyre: (0 or "") or "standard"
# Steg 1: evaluer parentesen (0 or "")
#   0 er falsy, så høyresiden i parentesen, "", returneres
#   -> "" or "standard"
# Steg 2: evaluer "" or "standard"
#   "" er falsy, så høyresiden, "standard", returneres
#   -> "standard"
print(0 and 5)                  # 0
print(3 and 5)                  # 5
print(0 or "" or "standard")    # standard
print([] or [1, 2])             # [1, 2]
print([1] and [])               # []
print(None or 0)                # 0
print(True and "hei")           # hei
print(False or None)            # None

Dette blir noen ganger brukt for å angi en standardverdi:

name = input("Hva heter du? ")
name = name or "anonym"  # Hvis name er tom streng, blir name "anonym"
print(f"Hei, {name}!")

Selv om dette er lov, er det ofte tydeligere å skrive det som et if-else -uttrykk, slik at leseren ikke trenger å kjenne til disse reglene (se God stil).

Merk at not er et unntak: not returnerer alltid True eller False.

Vanlige feil og misforståelser
Sammenligne med flere verdier

En vanlig feil er å skrive en betingelse slik vi ville sagt det i dagligtale: «hvis svaret er ja eller Ja».

answer = "nei"
if answer == "ja" or "Ja":   # Feil!
    print("Du svarte ja")    # Skrives ut uansett hva answer er

Med presedens og parenteser blir betingelsen (answer == "ja") or "Ja". Når answer er "nei", blir dette False or "Ja", som evaluerer til "Ja". Det er en streng som ikke er tom, og derfor truthy. Betingelsen er altså truthy uansett hva answer er.

answer = "nei"
print(answer == "ja" or "Ja")  # Ja

Det riktige er å skrive ut begge sammenligningene, eller å bruke in med en liste:

answer = "nei"
if answer == "ja" or answer == "Ja":
    print("Du svarte ja")

if answer in ["ja", "Ja"]:
    print("Du svarte ja")
Sjekke om en verdi er innenfor et intervall
if x > 0 and < 10:   # SyntaxError: begge sider av and må være hele uttrykk
    ...

Det riktige er x > 0 and x < 10, eller kortere 0 < x < 10.

Strengen “True” er ikke True

True og "True" er to forskjellige verdier av forskjellige typer. Dette er spesielt lett å blande når man leser input fra brukeren, siden input() alltid returnerer en streng.

answer = input("Skriv True eller False: ")
print(answer == True)     # False, uansett hva du skrev (en str er aldri lik en bool)
print(bool(answer))       # True, så lenge du skrev noe (også om du skrev False)
print(answer == "True")   # True bare hvis du skrev nøyaktig True
Boolske verdier som tall

I Python er bool egentlig en spesialisert variant av int: True oppfører seg som tallet 1, og False oppfører seg som tallet 0.

print(True + True)        # 2
print(True * 10)          # 10
print(False + 5)          # 5
print(True == 1)          # True
print(False == 0)         # True

Dette kan brukes til å telle hvor mange verdier i en liste som er True:

answers = [True, False, True, True, False]
print(sum(answers))  # 3

Vær likevel forsiktig med å bruke dette for å være smart (se God stil).

Funksjonene any og all

Funksjonene any og all tar en liste som argument, og fungerer som or og and over alle elementene i listen:

I motsetning til and og or returnerer any og all alltid True eller False.

print(any([False, False, True]))  # True
print(any([False, False, False])) # False
print(all([True, True, True]))    # True
print(all([True, False, True]))   # False
print()
print(any([0, "", "hei"]))        # True ("hei" er truthy)
print(all([1, "a", [0]]))         # True (alle er truthy)
scores = [7, 9, 4, 10]
passed = []
for score in scores:
    passed.append(score >= 5)
print(passed)        # [True, True, False, True]
print(all(passed))   # False (ikke alle bestod)
print(any(passed))   # True (minst én bestod)
print(sum(passed))   # 3 (antall som bestod)

En litt overraskende konsekvens av definisjonen er hva som skjer med en tom liste. any([]) er False, siden det ikke finnes noe element som er truthy. all([]) er True, siden det ikke finnes noe element som ikke er truthy.

print(any([]))  # False
print(all([]))  # True
God stil

Merk at dette avsnittet omhandler stil og ikke korrekthet. Koden til venstre fungerer, men koden til høyre er lettere å lese.

Unødvendig sammenligning med True/False: Siden en sammenligning allerede evaluerer til True eller False, er det unødvendig å sammenligne resultatet med True en gang til.

# Dårlig
if is_adult == True:
    print("ja")
if is_adult == False:
    print("nei")
# Bra
if is_adult:
    print("ja")
if not is_adult:
    print("nei")

Returnere True/False med en if-setning: Når en funksjon skal returnere om noe er sant eller ikke, er det fristende å bruke en if-setning. Men uttrykket i betingelsen er allerede den verdien vi ønsker å returnere.

# Dårlig
def is_even(n):
    if n % 2 == 0:
        return True
    else:
        return False
# Bra
def is_even(n):
    return n % 2 == 0


 

Tilordne True/False med en if-setning: Det samme gjelder for variabler.

# Dårlig
if age >= 18:
    is_adult = True
else:
    is_adult = False
# Bra
is_adult = age >= 18


 

Negert sammenligning: Det finnes en sammenligningsoperator for det motsatte av hver sammenligning, så vi trenger ikke not.

# Dårlig
if not x == y:
    print("ulike")
if not x < 5:
    print("stor")
# Bra
if x != y:
    print("ulike")
if x >= 5:
    print("stor")

Intervall: En kjedet sammenligning ligner på matematisk notasjon og er lettere å lese.

# Mindre foretrukket
if x > 0 and x < 10:
    print("ett siffer")
# Bra
if 0 < x < 10:
    print("ett siffer")

Bruk av or for å velge en verdi: Dette er lov, men leseren må kjenne til hva or egentlig returnerer for å forstå koden.

# Mindre foretrukket
name = name or "anonym"
# Bra
name = name if name != "" else "anonym"

Fancy bruk av boolske verdier som tall:

# Horribelt
y = (x > 0) * 99
# Bra
y = 99 if x > 0 else 0
Eksempler

Prøv å forutsi hva hvert uttrykk evaluerer til før du kjører koden og ser fasiten. Bruk reglene for presedens, kortslutning og truthy/falsy verdier.

print(True and not False)
print(not True or True)
print(not (True or True))
print(3 > 2 > 1)
print(1 < 3 == 3)
print()
print(0 or 7)
print(7 and 0)
print("" or "tom")
print([] and [1, 2])
print(not "")
print(None or 0)
print()
print(1 == 1 or 2)
print(1 == 2 or 2)
print("a" == "b" or "c")
print(True + True + False)
print(all([1, "a", [0]]))
print(any([0, "", []]))