Data types, operators, control flow en functions/scope
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.
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'>
| Type | Mutable? | Ordered? | Voorbeeld |
|---|---|---|---|
list | Ja | Ja | [1, 2, 3] |
tuple | Nee | Ja | (1, 2, 3) |
dict | Ja | Ja (sinds 3.7) | {"naam": "Anne"} |
set | Ja | Nee | {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}
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'>
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.
Python's standaard float is een 64-bit getal volgens de IEEE 754-standaard ("double precision"). Deze 64 bits zijn verdeeld in drie delen:
| Onderdeel | Aantal bits | Functie |
|---|---|---|
| Sign | 1 bit | 0 = positief, 1 = negatief |
| Exponent | 11 bits | bepaalt de "schaal" (macht van 2) |
| Mantissa (fraction) | 52 bits | de significante cijfers |
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
0.1 + 0.2 niet precies 0.3 isEen 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
==. Gebruikround()of een kleine tolerantie:print(round(0.1 + 0.2, 2) == 0.3) # True
Voordat we naar control flow gaan (sectie 3), eerst een overzicht van de operators die daar veel gebruikt worden.
| Operator | Betekenis | Voorbeeld | Resultaat |
|---|---|---|---|
+ | Optellen | 5 + 2 | 7 |
- | Aftrekken | 5 - 2 | 3 |
* | Vermenigvuldigen | 5 * 2 | 10 |
/ | Delen (levert altijd een float) | 5 / 2 | 2.5 |
// | Floor division (deling, naar beneden afgerond) | 5 // 2 | 2 |
% | Modulo (rest bij deling) | 5 % 2 | 1 |
** | Machtsverheffen | 5 ** 2 | 25 |
| Operator | Betekenis | Voorbeeld | Resultaat |
|---|---|---|---|
== | Gelijk aan | 5 == 5 | True |
!= | Niet gelijk aan | 5 != 3 | True |
> | Groter dan | 5 > 3 | True |
< | Kleiner dan | 5 < 3 | False |
>= | Groter dan of gelijk aan | 5 >= 5 | True |
<= | Kleiner dan of gelijk aan | 5 <= 3 | False |
| Operator | Betekenis | Voorbeeld | Resultaat |
|---|---|---|---|
and | True als beide kanten True zijn | True and False | False |
or | True als minstens één kant True is | True or False | True |
not | Keert de waarheidswaarde om | not True | False |
leeftijd = 20
print(leeftijd >= 18 and leeftijd < 65) # True
print(leeftijd < 18 or leeftijd > 65) # False
print(not (leeftijd < 18)) # True
if — conditional logicleeftijd = 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")
while — herhalen zolang een voorwaarde geldtteller = 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)
for — itereren over een reeksfor 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]
def groet(naam):
return "Hallo, " + naam + "!"
print(groet("Anne")) # Hallo, Anne!
Onderdelen:
def start de definitiegroet is de function namenaam is een parameterreturn geeft een waarde terug aan de callerdef 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)
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
globalzo min mogelijk — het maakt code moeilijker te volgen. Geef liever waarden mee als parameter enreturnhet resultaat.def verhoog(teller): return teller + 1 teller = 0 teller = verhoog(teller) print(teller) # 1
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:
return.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.
Python zoekt variabelen in deze volgorde op:
print, len)x = "globaal"
def buiten():
x = "omliggend"
def binnen():
x = "lokaal"
print(x) # lokaal
binnen()
print(x) # omliggend
buiten()
print(x) # globaal