← Terug naar overzicht
Lesmateriaal · Python

Python Les

Data types, operators, control flow en functions/scope

1. Data types

Python is dynamically typed: je hoeft het type van een variabele niet expliciet op te geven, Python leidt dit af op basis van de waarde.

1.1 De basistypen

geheel_getal = 42          # int
kommagetal = 3.14          # float
tekst = "Hallo wereld"     # str
waarheid = True            # bool
niets = None                # NoneType

Je kunt het type van een variabele controleren met type():

print(type(geheel_getal))  # <class 'int'>
print(type(kommagetal))    # <class 'float'>

1.2 Collection types

TypeMutable?Ordered?Voorbeeld
listJaJa[1, 2, 3]
tupleNeeJa(1, 2, 3)
dictJaJa (sinds 3.7){"naam": "Anne"}
setJaNee{1, 2, 3}
mijn_lijst = [1, 2, 3]
mijn_tuple = (1, 2, 3)
mijn_dict = {"naam": "Anne", "leeftijd": 25}
mijn_set = {1, 2, 2, 3}   # dubbele waarden verdwijnen automatisch

print(mijn_set)   # {1, 2, 3}

1.3 Type conversion

Je kunt waarden expliciet omzetten naar een ander type met int(), float() en str().

x = "10"                # str
y = int(x)               # 10, nu een int
z = float(x)              # 10.0, nu een float

print(type(x), x)   # <class 'str'> 10
print(type(y), y)   # <class 'int'> 10
print(type(z), z)   # <class 'float'> 10.0

Conversie naar een string gaat net zo, met str(). Dit is bijvoorbeeld nodig als je een getal wilt samenvoegen met tekst, want + werkt niet zomaar tussen een str en een int:

leeftijd = 25

# print("Ik ben " + leeftijd + " jaar oud")   # FOUT! kan str en int niet optellen
print("Ik ben " + str(leeftijd) + " jaar oud")  # werkt wel

print(type(str(leeftijd)))   # <class 'str'>

1.4 Verdieping: floats en bits

Een float gedraagt zich soms verrassend, bijvoorbeeld: 0.1 + 0.2 != 0.3. Om te begrijpen waarom, is het nuttig om te weten wat een float eigenlijk is op bit-niveau.

IEEE 754 — double precision (64-bit)

Python's standaard float is een 64-bit getal volgens de IEEE 754-standaard ("double precision"). Deze 64 bits zijn verdeeld in drie delen:

OnderdeelAantal bitsFunctie
Sign1 bit0 = positief, 1 = negatief
Exponent11 bitsbepaalt de "schaal" (macht van 2)
Mantissa (fraction)52 bitsde significante cijfers

De bits daadwerkelijk bekijken

Met de module struct kun je een float omzetten naar zijn ruwe bits:

import struct

def toon_bits(getal):
    # pak de float in 8 bytes, volgens IEEE 754
    data = struct.pack('>d', getal)
    # zet elke byte om naar 8 bits (0'en en 1'en)
    bits = ''.join(format(byte, '08b') for byte in data)
    return bits

bits = toon_bits(1.0)
print("Alle 64 bits: ", bits)
print("Sign (1 bit):      ", bits[0])
print("Exponent (11 bit): ", bits[1:12])
print("Mantissa (52 bit): ", bits[12:])

Vergelijk de uitkomst voor een paar verschillende getallen:

print(toon_bits(1.0))
print(toon_bits(-1.0))    # alleen het sign-bit verandert
print(toon_bits(2.0))     # exponent verandert, mantissa blijft gelijk
print(toon_bits(0.1))     # geen "nette" bitpatroon -> kern van het probleem

Waarom 0.1 + 0.2 niet precies 0.3 is

Een float heeft een eindig aantal bits, dus een eindige precisie. Niet elk decimaal getal past exact in dat bitpatroon — net zoals 1/3 geen eindig aantal decimalen heeft in het tientallig stelsel. 0.1 is in binaire vorm een oneindig repeterende reeks, en wordt daarom afgerond opgeslagen in de beschikbare bits. Dat verklaart dit klassieke voorbeeld:

print(0.1 + 0.2)          # 0.30000000000000004
print(0.1 + 0.2 == 0.3)   # False

Vuistregel: vergelijk floats nooit met ==. Gebruik round() of een kleine tolerantie:

print(round(0.1 + 0.2, 2) == 0.3)   # True

2. Operators

Voordat we naar control flow gaan (sectie 3), eerst een overzicht van de operators die daar veel gebruikt worden.

2.1 Arithmetic operators

OperatorBetekenisVoorbeeldResultaat
+Optellen5 + 27
-Aftrekken5 - 23
*Vermenigvuldigen5 * 210
/Delen (levert altijd een float)5 / 22.5
//Floor division (deling, naar beneden afgerond)5 // 22
%Modulo (rest bij deling)5 % 21
**Machtsverheffen5 ** 225

2.2 Comparison operators

OperatorBetekenisVoorbeeldResultaat
==Gelijk aan5 == 5True
!=Niet gelijk aan5 != 3True
>Groter dan5 > 3True
<Kleiner dan5 < 3False
>=Groter dan of gelijk aan5 >= 5True
<=Kleiner dan of gelijk aan5 <= 3False

2.3 Logical operators

OperatorBetekenisVoorbeeldResultaat
andTrue als beide kanten True zijnTrue and FalseFalse
orTrue als minstens één kant True isTrue or FalseTrue
notKeert de waarheidswaarde omnot TrueFalse
leeftijd = 20
print(leeftijd >= 18 and leeftijd < 65)   # True
print(leeftijd < 18 or leeftijd > 65)     # False
print(not (leeftijd < 18))                # True

3. Control flow: if, while, for

3.1 if — conditional logic

leeftijd = 17

if leeftijd >= 18:
    print("Je bent meerderjarig")
elif leeftijd >= 16:
    print("Je bent bijna meerderjarig")
else:
    print("Je bent minderjarig")

Let op de indentation: dit is in Python niet optioneel, maar bepaalt welke code bij welk blok hoort.

if leeftijd >= 18 and leeftijd < 65:
    print("Werkende leeftijd")

3.2 while — herhalen zolang een voorwaarde geldt

teller = 0
while teller < 5:
    print("Teller staat op", teller)
    teller += 1
print("Klaar!")

Let op infinite loops: vergeet nooit de teller/voorwaarde bij te werken binnen de loop.

# Voorbeeld: gebruiker input vragen tot juist antwoord
antwoord = ""
while antwoord != "python":
    antwoord = input("Wat is mijn favoriete taal? ")
print("Correct!")

break en continue:

teller = 0
while True:
    teller += 1
    if teller == 3:
        continue   # sla deze iteratie over
    if teller > 5:
        break      # stop de loop
    print(teller)

3.3 for — itereren over een reeks

for i in range(5):
    print(i)   # 0, 1, 2, 3, 4

range(start, stop, step):

for i in range(2, 10, 2):
    print(i)   # 2, 4, 6, 8

Itereren over collections:

namen = ["Anne", "Bilal", "Chen"]
for naam in namen:
    print("Hallo,", naam)

# Met index via enumerate
for index, naam in enumerate(namen):
    print(index, naam)

# Itereren over een dict
leeftijden = {"Anne": 25, "Bilal": 30}
for naam, leeftijd in leeftijden.items():
    print(naam, "is", leeftijd, "jaar oud")

List comprehension (compacte manier om een for-loop te schrijven):

kwadraten = [x**2 for x in range(10)]
print(kwadraten)  # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

4. Functions en scope

4.1 Functions definiëren

def groet(naam):
    return "Hallo, " + naam + "!"

print(groet("Anne"))   # Hallo, Anne!

Onderdelen:

4.2 Parameters: default values en keyword arguments

def bereken_prijs(prijs, korting=0):
    return prijs - (prijs * korting)

print(bereken_prijs(100))          # 100 (geen korting)
print(bereken_prijs(100, 0.1))     # 90.0
print(bereken_prijs(prijs=100, korting=0.2))  # 80.0

*args en **kwargs (variabel aantal arguments):

def tel_op(*getallen):
    return sum(getallen)

print(tel_op(1, 2, 3, 4))   # 10

def toon_info(**gegevens):
    for sleutel, waarde in gegevens.items():
        print(sleutel, ":", waarde)

toon_info(naam="Anne", leeftijd=25)

4.3 Scope — waar bestaat een variabele?

Local scope: variabelen die binnen een function worden aangemaakt, bestaan alleen daarbinnen.

def mijn_functie():
    x = 10   # local variabele
    print(x)

mijn_functie()
print(x)   # FOUT! NameError: x bestaat hier niet

Global scope: variabelen buiten elke function zijn overal leesbaar (maar niet zomaar aanpasbaar).

teller = 0

def verhoog():
    print(teller)   # dit werkt: lezen mag

verhoog()
teller = 0

def verhoog():
    teller += 1   # FOUT! UnboundLocalError

verhoog()

Om een global variabele binnen een function te wijzigen, is het global keyword nodig:

teller = 0

def verhoog():
    global teller
    teller += 1

verhoog()
print(teller)   # 1

Vuistregel: gebruik global zo min mogelijk — het maakt code moeilijker te volgen. Geef liever waarden mee als parameter en return het resultaat.

def verhoog(teller):
    return teller + 1

teller = 0
teller = verhoog(teller)
print(teller)   # 1

4.4 Scope en modulariteit: een function als "los bestand"

Een handige manier om scope te begrijpen: vergelijk een function met een apart Python-bestand.

Stel je hebt twee losse .py-bestanden. Een variabele die je in bestand_a.py aanmaakt, bestaat automatisch niet in bestand_b.py — die twee bestanden weten niets van elkaars variabelen, tenzij je ze expliciet doorgeeft (bijvoorbeeld via een import). Een function werkt in principe hetzelfde:

def bereken_oppervlakte(lengte, breedte):
    oppervlakte = lengte * breedte   # bestaat alleen "binnen dit bestandje"
    return oppervlakte

resultaat = bereken_oppervlakte(4, 5)
print(resultaat)      # 20
print(oppervlakte)    # FOUT! oppervlakte bestaat hier niet, net als in een ander bestand

Dit idee heet modulariteit: een function is een afgesloten eenheid. Als gebruiker van de function hoef je niet te weten wat er precies binnenin gebeurt — je hoeft alleen te weten wat erin moet (de parameters) en wat eruit komt (de return value). Dat maakt code makkelijker te lezen, te testen en te hergebruiken: de inhoud van een function kan later aangepast worden zonder de rest van het programma kapot te maken, zolang de "ingang" en "uitgang" hetzelfde blijven.

4.5 Scope-volgorde: de LEGB rule

Python zoekt variabelen in deze volgorde op:

  1. Local — binnen de huidige function
  2. Enclosing — binnen een omliggende function (bij geneste functions)
  3. Global — op module-niveau
  4. Built-in — Python's ingebouwde namen (zoals print, len)
x = "globaal"

def buiten():
    x = "omliggend"
    def binnen():
        x = "lokaal"
        print(x)   # lokaal
    binnen()
    print(x)   # omliggend

buiten()
print(x)   # globaal