Hoofdstuk 9 - Classes

01 - Een class maken en gebruiken
Intro
  • Een class is een blauwdruk of ontwerp voor objecten, zoals het algemene idee “Dier”
  • Een class beschrijft welke eigenschappen (bijv. naam, leeftijd) en vaardigheden (bijv. geluid maken) alle dieren hebben
  • Een object is een concreet voorbeeld van een class, zoals een echte hond of kat
  • Objecten noemen we ook wel instanties van de class
  • Je kunt van één class heel veel instanties maken: elke hond is anders, maar volgt hetzelfde ontwerp
  • Een class fungeert als de “moeder”, en elke instantie is een “kind” dat eigenschappen van de moeder overneemt
  • Als je de moeder-class aanpast (bijvoorbeeld het standaardgeluid), dan veranderen alle kinderen automatisch mee
  • Classes kunnen ook van elkaar erven: “Hond” kan kind zijn van “Dier”, en “Labrador” kan weer een kind zijn van “Hond”
  • Door overerving kun je gedrag steeds verder specialiseren zonder opnieuw alle code te schrijven
  • Met classes kun je programma’s beter organiseren, uitbreiden en hergebruiken, precies zoals functies dat ook doen — maar dan op een groter niveau
      +------------------+
      |     Dier         |   ← class (moeder)
      |------------------|
      | eigenschappen:   |
      | - naam           |
      | - leeftijd       |
      |------------------|
      | methoden:        |
      | - zitten         |
      | - omrollen       |
      +------------------+
              |
   -------------------------
   |                       |
   v                       v
+-----------+        +---------------+
| hond      |        | kat           |
| (kind)    |        | (kind)        |
+-----------+        +---------------+
CLASS-HIERARCHIE
--------------------------------------

          +-------------+
          |    Dier     |    ← moeder
          +-------------+
                 |
                 v
          +-------------+
          |    Hond     |    ← kind van Dier
          +-------------+
                 |
                 v
          +----------------+
          |   Labrador     |  ← kind van Hond
          +----------------+
              Labrador
                  |
 -----------------------------------
 |                |               |
 v                v               v
+----------+   +----------+   +-----------+
|  max     |   |  luna    |   |  diesel   |
| object   |   | object   |   | object    |
+----------+   +----------+   +-----------+

  • Complete boom: drie “moeders” boven elkaar → één kind-object
(LEVEL 1)         CLASS
+------------------------+
|        Dier            |
+------------------------+
        |
        v
(LEVEL 2)         SUBCLASS
+------------------------+
|         Hond           |
+------------------------+
        |
        v
(LEVEL 3)         SUB-SUBCLASS
+------------------------+
|       Labrador         |
+------------------------+
        |
        v
(LEVEL 4)         INSTANTIE (object)
+------------------------+
|    max = Labrador()    |
+------------------------+
  • In de praktijk gebruik je meestal 1 tot 3 niveaus van overerving (bijv. Dier → Hond → Labrador); diepere ketens maken de code moeilijker te begrijpen en te onderhouden
  • Een subclass moet logisch een “is een” relatie hebben met zijn parent — een hond is een dier, maar een dierenwinkel is geen dier. Als de relatie niet klopt, klopt de OOP-structuur meestal ook niet


class Dog:
    """Een eenvoudige representatie van een hond."""

    def __init__(self, name, age):
        """Initialiseer de hond met een naam en een leeftijd."""
        self.name = name
        self.age = age

    def sit(self):
        """Laat de hond gaan zitten."""
        print(self.name.title() + " gaat nu zitten.")

    def roll_over(self):
        """Laat de hond omrollen."""
        print(self.name.title() + " rolt om!")
  • Classnamen worden met een hoofdletter geschreven
  • De docstring met drie aanhalingstekens ("""...""") is de officiële manier en wordt o.a. gebruikt door documentatiegenerators
  • Functies binnen een class heten methoden
  • __init__() is een speciale (dunder) methode die wordt uitgevoerd bij het maken van een object
  • De dubbele underscores zijn verplicht: Python herkent deze naam als de constructor
  • __init__() heeft in dit voorbeeld drie parameters: self, name, age
  • self moet altijd de eerste parameter zijn; het verwijst naar het concrete object
  • Bij het aanroepen van een methode wordt self automatisch door Python ingevuld
  • Wanneer je een object van Dog maakt, geef je dus alleen name en age mee
  • Variabelen die met self. worden opgeslagen, heten attributen en zijn binnen de hele class beschikbaar
  • Deze attributen kun je ook buiten de class via het object benaderen, bijvoorbeeld hond.name
  • De class Dog heeft nog twee methoden: sit() en roll_over()
  • Voor deze twee methoden is alleen self als parameter nodig
  • De objecten die later gemaakt worden, hebben toegang tot deze methoden
hond1 = Dog('willie', 6)
hond2 = Dog('lucy', 3)

print("Mijn hond heet  " + hond1.name.title() + ".")
print("Mijn hond is " + str(hond1.age) + " jaar oud.")
hond1.sit()

print("Jouw hond heet  " + hond2.name.title() + ".")
print("Jouw hond is " + str(hond2.age) + " jaar oud.")
hond2.sit()
Mijn hond heet  Willie.
Mijn hond is 6 jaar oud.
Willie gaat nu zitten.
Jouw hond heet  Lucy.
Jouw hond is 3 jaar oud.
Lucy gaat nu zitten.
  • De variabele hond1 wordt nu gevuld met het object dat gemaakt wordt door de class
  • Classes schrijf je met een hoofdletter (PascalCase), variabelen en functies schrijf je met kleine letters (snake_case)
  • Bij het aanroepen van de class worden de juiste argumenten meegegeven aan de __init__()-methode
  • De __init__() methode heeft geen specifieke return-instructie, maar Python retourneert automatisch een nieuw object dat deze hond voorstelt
  • Met de puntnotatie kun je de waarde van een attribuut vinden
  • hond1.name is hetzelfde attribuut als self.name in de __init__() methode
  • Na het maken van een object van de class Dog kun je puntnotatie gebruiken om elke methode in de class aan te roepen
hond1.sit()
hond1.roll_over()
Willie gaat nu zitten.
Willie rolt om!
  • Je kunt van een class zoveel objecten maken als je nodig hebt
  • Ook als een tweede hond dezelfde naam en dezelfde leeftijd heeft, wordt er toch een nieuw object gemaakt
  • De variabele waarin je het hond-object opslaat, moet wel een unieke naam hebben

class Dog:
    - name
    - age
    - sit()
    - roll_over()

        |
        |  Maak een object
        v

hond = Dog("Fifi", 3)

hond ---> [ Dog-object ]
            name = "Fifi"
            age = 3
            methoden: sit(), roll_over()

Gebruik:
    hond.name
    hond.age
    hond.sit()

02 - Werken met classes en objecten
class Car():
    """Eenvoudige representatie van een auto"""

    def __init__(self, manufacturer, model, year):
        """Initialiseren van de attributen die een auto beschrijven"""
        self.manufacturer = manufacturer
        self.model = model
        self.year = year

    def get_descriptive_name(self):
        """Retourneer een mooi opgemaakte beschrijvende naam"""
        long_name = str(self.year) + ' ' + self.manufacturer + ' ' + self.model
        return long_name.title()

my_new_car = Car('Landrover', '90', 2022)
print(my_new_car.get_descriptive_name())
2022 Landrover 90
  • Je kunt een attribuut toevoegen aan de class dat gaandeweg verandert: de kilometerstand
  • Elk attribuut in een class moet een initiële waarde hebben, zelfs als het 0 is of een lege string
  • Als je een standaardwaarde specificeert voor een attribuut, hoef je deze bij het aanroepen niet mee te geven
  • Om de kilometerstand af te lezen plaatsen we een extra methode read_odometer()
class Car():
    """Eenvoudige representatie van een auto"""

    def __init__(self, manufacturer, model, year):
        """Initialiseren van de attributen die een auto beschrijven"""
        self.manufacturer = manufacturer
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def get_descriptive_name(self):
        """Retourneer een mooi opgemaakte beschrijvende naam"""
        long_name = str(self.year) + ' ' + self.manufacturer + ' ' + self.model
        return long_name.title()

    def read_odometer(self):
        """Print het aantal kilometers"""
        print("Deze auto heeft " + str(self.odometer_reading) + " kilometers gereden.")

my_new_car = Car('Landrover', '90', 2022)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()
2022 Landrover 90
Deze auto heeft 0 kilometers gereden.

  • Je kunt de waarde van een attribuut op drie manieren wijzigen
    1. De waarde instellen rechtstreeks via het object
    2. De waarde instellen via een methode
    3. De waarde aanpassen met een methode
# 1 - Attributen van het object rechtstreeks aanpassen
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
Deze auto heeft 23 kilometers gereden

# 2 - De waarde van een attribuut instellen met een extra methode in de class
    def update_odometer(self, km):
        """Stel de kilometerteller in op de opgegeven waarde."""
        if km >= self.odometer_reading:
            self.odometer_reading = km
        else:
            print("Je kunt je kilometerteller niet terugdraaien!")
# Aanroepen van nieuwe methode update_odometer()
my_new_car.update_odometer(23)
my_new_car.read_odometer()
Deze auto heeft 23 kilometers gereden

# 3 - De waarde van een attribuut aanpassen met een extra methode in de class
    def decrement_odometer(self, km):
        """Verlaag de kilometerteller met de opgegeven kilometers"""
        self.odometer_reading -= km
# Aanroepen van nieuwe methode decrement_odometer()
my_new_car.decrement_odometer(10)
my_new_car.read_odometer()
Deze auto heeft 13 kilometers gereden.
03 - Overerven
  • Waarom gebruik je overerving? Om code te hergebruiken: je zet gedeelde eigenschappen en methoden in een moederclass, zodat je die niet opnieuw hoeft te schrijven in elke subclass
  • Wanneer gebruik je overerving? Wanneer er een logische “is-een”-relatie bestaat tussen twee dingen: een hond is een dier, een sportauto is een auto; maar een dierenwinkel is geen dier
  • Hoe gebruik je overerving? Je laat een class erven door de parent-class tussen haakjes achter de subclass te zetten, bijvoorbeeld class Hond(Dier):, waardoor de subclass automatisch alle eigenschappen en methoden van de parent-class overneemt
class Car():
    """Eenvoudige representatie van een auto"""

    def __init__(self, manufacturer, model, year):
        """Initialiseren van de attributen die een auto beschrijven"""
        self.manufacturer = manufacturer
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def get_descriptive_name(self):
        """Retourneer een mooi opgemaakte beschrijvende naam"""
        long_name = str(self.year) + ' ' + self.manufacturer + ' ' + self.model
        return long_name.title()

    def read_odometer(self):
        """Print het aantal kilometers"""
        print("Deze auto heeft " + str(self.odometer_reading) + " kilometers gereden.")

    def update_odometer(self, km):
        """Stel de kilometerteller in op de opgegeven waarde."""
        if km >= self.odometer_reading:
            self.odometer_reading = km
        else:
            print("Je kunt je kilometerteller niet terugdraaien!")

    def decrement_odometer(self, km):
        """Verlaag de kilometerteller met de opgegeven kilometers"""
        self.odometer_reading -= km


class ElectricCar(Car):
    """Representeert een auto, specifiek voor elektrische auto's."""

    def __init__(self, manufacturer, model, year):
        """Initialiseer eerst de attributen van de moederclass,
        en daarna de attributen die specifiek zijn voor elektrische auto's.
        """
        super().__init__(manufacturer, model, year)
        # hier komen straks de extra ElectricCar-attributen

my_car = ElectricCar('Landrover', 'defender', '2030')
print(my_car.get_descriptive_name())
2030 Landrover Defender
  • De parent-class moet al gedefinieerd zijn voordat je de child-class kunt maken
  • De functie super() geeft vanuit de child-class toegang tot de methoden van de parent-class
  • super().__init__() voert de constructor van de parent-class uit
  • Daarna kun je in de child-class extra attributen toevoegen
  • Het woord “super” komt van de term superclass: de class waarvan een andere class erft

class ElectricCar(Car):
    """Representeert een auto, specifiek voor elektrische auto's."""

    def __init__(self, manufacturer, model, year):
        """Initialiseer eerst de attributen van de moederclass,
        en daarna de attributen die specifiek zijn voor elektrische auto's.
        """
        super().__init__(manufacturer, model, year)
        # hier komen straks de extra ElectricCar-attributen
        self.battery_size = 70

    def describe_battery(self):
        """Print hoe groot de batterij is."""
        print("Deze auto heeft een " + str(self.battery_size) + " kWh batterij")

my_car = ElectricCar('Landrover', 'defender', '2030')
print(my_car.get_descriptive_name())
my_car.describe_battery()
2030 Landrover Defender
Deze auto heeft een 70 Kwh batterij
  • Het nieuwe attribuut battery_size voor de elektrische auto heeft een standaardwaarde van 70
  • Dit attribuut is alleen beschikbaar in de subclass ElectricCar
  • De nieuwe methode describe_battery() is ook alleen beschikbaar in de class ElectricCar

Methoden uit de parent-class overschrijven
  • Je kunt elke methode uit een parent-class overschrijven als je deze niet wilt gebruiken in de child-class
  • Je doet dit door in de child-class een methode te definiëren met precies dezelfde naam
  • Stel: de class Car heeft een methode met de naam fill_gas_tank()
  • Deze methode is zinloos voor een volledig elektrische auto
class ElectricCar(Car):
    ...
    def fill_gas_tank(self):
        """Elektrische auto's hebben geen brandstoftank."""
        print("Deze auto heeft geen brandstoftank!")
  • Als iemand nu fill_gas_tank() aanroept op een ElectricCar-object, krijgt hij een duidelijke melding
  • Met overerving kun je een child-class precies laten behouden wat nodig is en de rest overschrijven

Objecten als attributen (compositie)
  • Wanneer je steeds meer details toevoegt aan een class, kan deze onbedoeld te groot en onoverzichtelijk worden
  • Het kan dan handiger zijn om bepaalde eigenschappen of onderdelen in een aparte class onder te brengen
  • Op deze manier kunnen verschillende classes met elkaar samenwerken, en blijft elke class overzichtelijk en gericht op één taak
  • Als je steeds meer batterijgegevens aan de class ElectricCar toevoegt, wordt de class al snel te lang
  • Verplaats dan de batterij-attributen en batterij-methoden naar een aparte class, bijvoorbeeld Batterij
  • Vervolgens maak je in ElectricCar een attribuut dat een Batterij-object bevat, zodat beide classes netjes samenwerken
class Car:
    """Eenvoudige representatie van een auto."""

    def __init__(self, manufacturer, model, year):
        """Initialiseren van de attributen die een auto beschrijven."""
        self.manufacturer = manufacturer
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def get_descriptive_name(self):
        """Retourneer een mooi opgemaakte beschrijvende naam."""
        long_name = str(self.year) + ' ' + self.manufacturer + ' ' + self.model
        return long_name.title()

    def read_odometer(self):
        """Print het aantal kilometers."""
        print("Deze auto heeft " + str(self.odometer_reading) + " kilometers gereden.")

    def update_odometer(self, km):
        """Stel de kilometerteller in op de opgegeven waarde."""
        if km >= self.odometer_reading:
            self.odometer_reading = km
        else:
            print("Je kunt je kilometerteller niet terugdraaien!")

    def decrement_odometer(self, km):
        """Verlaag de kilometerteller met de opgegeven kilometers."""
        self.odometer_reading -= km


class Battery:
    """Representeert een batterij voor een elektrische auto."""

    def __init__(self, battery_size=60):
        """Initialiseren van de attributen van de batterij."""
        self.battery_size = battery_size

    def describe_battery(self):
        """Print hoe groot de batterij is."""
        print("Deze auto heeft een " + str(self.battery_size) + " kWh batterij.") 


class ElectricCar(Car):
    """Representeert een auto, specifiek voor elektrische auto's."""

    def __init__(self, manufacturer, model, year):
        """Initialiseer eerst de attributen van de moederclass,
        en daarna de attributen die specifiek zijn voor elektrische auto's.
        """
        super().__init__(manufacturer, model, year)
        self.battery = Battery()  # standaard batterij van 60 kWh


my_car = ElectricCar('Landrover', 'Defender', 2030)
print(my_car.get_descriptive_name())
my_car.battery.describe_battery()
2030 Landrover Defender
Deze auto heeft een 60 kWh batterij.
  • Voor elk ElectricCar-object wordt nu automatisch een Batterij-object aangemaakt
  • Dit lijkt veel extra werk, maar nu kun je de Batterij-class zo gedetailleerd beschrijven als je wilt, zonder dat de ElectricCar-class rommelig wordt
  • Zo kun je eenvoudig nieuwe methoden toevoegen aan de Batterij-class, zoals hieronder de methode get_range()
    def get_range(self):
        """Print hoeveel kilometers deze batterij kan rijden."""
        if self.battery_size == 60:
            range = 140
        elif self.battery_size == 85:
            range = 185

    message = "Deze auto kan ongeveer " + str(range)
    message += " kilometer rijden als hij volledig is opgeladen."
    print(message)
# het aanroepen van de methode
my_car.battery.get_range()
Deze auto kan ongeveer 140 kilometer rijden als hij volledig is opgeladen.

Is het bereik van een auto een eigenschap van de auto of van de accu?


Overerving vs. Compositie
  • Overerving gebruik je wanneer er een logische “is-een” relatie bestaat: een hond is een dier, een elektrische auto is een auto
  • Compositie gebruik je wanneer een object andere objecten “bezit”: een auto heeft een batterij, een laptop heeft een scherm
  • Bij overerving neemt de child-class attributen en methoden over van de parent-class, waardoor je gedrag uitbreidt of specialiseert
  • Bij compositie voeg je een object toe als attribuut in een andere class (bijv. self.battery = Battery()), waardoor de code overzichtelijk blijft en elke class één duidelijke taak heeft
  • Je kiest compositie wanneer overerving onlogisch wordt of tot te veel “kunstmatige” subclasses zou leiden. Je kiest overerving wanneer je object echt een gespecialiseerde versie is van een ander object
OVERERVING  (is-een)
-----------------------------------

        +-----------+
        |   Car     |     ← parent
        +-----------+
              |
              v
        +----------------+
        |  ElectricCar   | ← child
        +----------------+
      (is een auto)



COMPOSITIE  (heeft-een)
-----------------------------------

        +----------------+
        |  ElectricCar   |
        +----------------+
                |
      heeft een | 
                v
        +----------------+
        |    Battery     |
        +----------------+

ElectricCar IS EEN Car
ElectricCar HEEFT EEN Battery
04 - Classes importeren
  • Bestanden met veel classes kunnen erg lang worden, zelfs als je overerving correct gebruikt
  • Om alles overzichtelijk te houden kun je classes opslaan in aparte modules (losse Python-bestanden)
  • Wanneer je een class nodig hebt, kun je deze eenvoudig uit een module importeren
  • Stel dat we de class Car in het bestand car.py zetten. In hetzelfde mapje staat dan my_car.py met de volgende code:
from car import Car

my_new_car = Car('Landrover', 'Defender 90', 2030)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
2030 Landrover Defender 90
Deze auto heeft 23 kilometers gereden.
  • Je kunt classes in aparte bestanden (modules) opslaan om je project overzichtelijk te houden
  • Met from bestandsnaam import Classnaam kun je een class in een andere file gebruiken
  • Het bestand moet in dezelfde map staan, of in een map die door Python gevonden kan worden
Meer over importeren
  • Python zoekt modules eerst in dezelfde map als het script dat je uitvoert; dat is altijd het startpunt voor alle imports
  • Daarna zoekt Python in de mappen die in sys.path staan, zoals de huidige werkmap, de standaardbibliotheek en de map van je virtual environment
  • Een importregel verwijst altijd naar een module-naam (bijv. from car import Car) en niet naar een bestandsnaam of pad; Python accepteert dus geen imports zoals from ../map/car import Car
  • Wanneer een class zoals Car in een apart bestand (car.py) staat, werkt de import alleen als dit bestand zich in dezelfde map bevindt als het script dat je uitvoert
  • Een module kan meerdere classes bevatten; bijvoorbeeld car.py kan Car, ElectricCar en Battery tegelijk bevatten
  • Je kunt meerdere classes uit dezelfde module importeren door ze comma-gescheiden te noemen, zoals from car import Car, ElectricCar, Battery
  • Je kunt ook de hele module importeren met import car; je gebruikt de classes dan via de module-namespace, bijvoorbeeld car.Car(...).
    • Een namespace is een verzameling namen (variabelen, functies, classes) die bij elkaar horen, zodat Python precies weet welke naam waarnaar verwijst
    • Namespaces voorkomen conflicten: verschillende modules mogen dezelfde naam gebruiken (bijv. twee keer Car), omdat ze in hun eigen namespace leven, zoals car.Car en rental.Car
    • Elke module krijgt automatisch een eigen namespace: bij import car gebruikt je code alles via car., bijvoorbeeld car.Car()
    • Namespaces houden je code overzichtelijk: ze zorgen dat modules, classes en functies gestructureerd blijven en duidelijk maken waar elke naam vandaan komt
  • Het importeren van alle namen met from car import * wordt afgeraden, omdat het onduidelijk maakt welke namen uit de module komen, naamconflicten kan veroorzaken en de leesbaarheid van je code vermindert
  • Modules kunnen ook andere modules importeren; bijvoorbeeld electric_car.py kan from battery import Battery bevatten, zolang beide modules binnen een geldige zoeklocatie staan
  • Wil je modules uit andere mappen importeren, dan moet die map vindbaar zijn voor Python — een map is vindbaar als hij voorkomt in sys.path
    • Je kunt een map vindbaar maken door een package-structuur te gebruiken: plaats modules in een map met een __init__.py-bestand en importeer daarna via paden zoals from voertuigen.car import Car.
      • Een package is simpelweg een map met Python-bestanden en een __init__.py; daardoor herkent Python de map als importeerbaar pakket
      • Een __init__.py-bestand mag volledig leeg zijn; het enige doel is dat Python de map als package herkent
      • De mappenstructuur bepaalt het importpad: een bestand in voertuigen/car.py wordt geïmporteerd als voertuigen.car
      • Packages voorkomen naamconflicten en houden code georganiseerd, bijvoorbeeld door auto-gerelateerde classes in een eigen pakket voertuigen te bewaren
  • Een map kan ook tijdelijk worden toegevoegd aan sys.path met sys.path.append("pad/naar/map"), zodat Python daar modules kan vinden
  • Als je je programma start vanuit de hoofdmap van je project, wordt die map automatisch opgenomen in sys.path en kunnen alle modules binnen die structuur eenvoudiger geïmporteerd worden
  • Het is belangrijk om te onthouden dat Python modules zoekt vanaf de locatie waar het script wordt uitgevoerd, niet vanaf de locatie van de file waarin de importregel staat
  • Door modules logisch op te splitsen en imports correct te gebruiken, blijft je project overzichtelijk, schaalbaar en duidelijk voor jezelf en anderen
05 - De standaardbibliotheek van Python
  • Python wordt geleverd met een uitgebreide standaardbibliotheek: honderden modules die je direct kunt gebruiken zonder iets te installeren
  • Deze modules bevatten oplossingen voor veelvoorkomende taken, zoals bestandsbeheer, datum- en tijdverwerking, wiskunde, netwerkverkeer en data-analyse
  • De standaardbibliotheek maakt Python krachtig: je hoeft veel functionaliteit niet zelf te programmeren, maar kunt bestaande modules hergebruiken
  • Je importeert modules uit de standaardbibliotheek net zoals je eigen modules, bijvoorbeeld met import os of from datetime import datetime
  • Omdat deze modules altijd beschikbaar zijn, kun je in elk Python-project meteen starten met professionele bouwstenen voor vrijwel elke taak
from datetime import datetime

# haal het huidige datum- en tijdstip op
nu = datetime.now()

print("Het is nu:")
print(nu)

# mooi geformatteerde datum en tijd
mooie_datum = nu.strftime("%d-%m-%Y")
mooie_tijd = nu.strftime("%H:%M:%S")

print("Datum:", mooie_datum)
print("Tijd:", mooie_tijd)
Het is nu:
2025-02-14 12:48:23.507391
Datum: 14-02-2025
Tijd: 12:48:23

Meest gebruikte modules uit de standaard bibliotheek
  • os — werken met bestanden en mappen
  • datetime — werken met datum en tijd
  • math — wiskundige functies
  • random — willekeurige waarden
  • collections — handige datastructuren

06 - Classes opmaken
  • Namen van classes worden geschreven in PascalCase (bijv. ElectricCar of DogHouse)
  • Namen van objecten, variabelen, functies en modules worden in kleine letters met underscores geschreven (snake_case), bijv. my_car of read_odometer
  • Elke class hoort een docstring te hebben die direct onder de class-definitie staat en uitlegt waarvoor de class bedoeld is
  • Elke module (Python-bestand) kan ook een docstring hebben bovenaan, waarin je kort beschrijft wat er in die module zit (zoals welke classes of functies)
  • Gebruik witregels om je code overzichtelijk te maken, maar overdrijf niet. Twee witregels tussen classes, één witregel tussen methoden binnen een class
  • Plaats imports bovenaan het bestand: eerst standaardbibliotheek-modules, dan een lege regel, dan externe modules, dan opnieuw een lege regel, en dan pas je eigen modules (PEP 8-importvolgorde)

PEP 8 (Python Enhancement Proposal nr. 8) is de officiële stijlrichtlijn voor Python-code. Het beschrijft hoe je code leesbaar, consistent en onderhoudbaar houdt door afspraken te maken over zaken zoals naamgeving (zoals snake_case en PascalCase), inspringing, witregels, lengte van regels, import-volgorde, documentatie (docstrings) en algemene opmaak. Het doel is dat alle Python-programmeurs wereldwijd dezelfde stijl gebruiken, zodat code er uniform uitziet en veel makkelijker te begrijpen, te debuggen en samen te ontwikkelen is.

Oefening
Quiz

Vraag 1. Wat beschrijft het beste het verschil tussen een class en een object?




Goed! De class is het ontwerp, een object is een instantie van dat ontwerp.
Niet helemaal. Een object wordt gemaakt uit een class en is een concreet voorbeeld.

Vraag 2. Waarvoor wordt de methode __init__() in een class gebruikt?




Klopt. __init__() wordt automatisch aangeroepen bij het maken van een nieuw object.
Let op. __init__() is de constructor: daar geef je startwaarden mee aan een nieuw object.

Vraag 3. Waar verwijst het sleutelwoord self naar binnen een methodedefinitie?




Juist. self verwijst naar het object dat de methode aanroept.
Niet goed. self is steeds het huidige object, niet de class of een globale variabele.

Vraag 4. Welke regel maakt een object van de class Dog met de naam hond1?

class Dog:
def __init__(self, name):
    self.name = name



Goed gezien. hond1 = Dog("Fifi") maakt een nieuw object van de class.
Let op. Je maakt een object door de class aan te roepen als functie, en de variabele links krijgt dat object.

Vraag 5. Welke regel laat een class ElectricCar erven van de class Car?




Klopt. class ElectricCar(Car): betekent dat ElectricCar een child-class is van Car.
Niet juist. Je zet de parent-class tussen haakjes achter de naam van de child-class.

Vraag 6. Wat doet de regel super().__init__(manufacturer, model, year) in ElectricCar?




Precies. super().__init__(...) zorgt dat de constructor van de parent-class wordt uitgevoerd.
Let op. super() gebruikt je om methoden van de parent-class aan te roepen, zoals __init__().

Vraag 7. In de class ElectricCar staat:

self.battery = Battery()

Wat betekent dit?




Juist. Dit is compositie: een auto heeft een batterij als onderdeel.
Niet goed. Hier is geen overerving, maar compositie: een object wordt attribuut van een ander object.

Vraag 8. Welke van de onderstaande namen volgen de conventies uit hoofdstuk 9?





Goed! Classes in PascalCase, variabelen en methoden in snake_case.
Niet helemaal. Onthoud: PascalCase voor classes, snake_case voor functies, methoden en variabelen.

Vraag 9. De class Car staat in het bestand car.py in dezelfde map. Welke importregel is juist als je alleen de class Car wilt gebruiken?




Klopt. Je importeert uit een module zonder .py: from car import Car.
Let op. Je geeft de modulenaam zonder .py op, en de classnaam met hoofdletter.

Vraag 10. Welke uitspraken over de standaardbibliotheek van Python kloppen?





Mooi! Standaardmodules zoals os en datetime zijn direct beschikbaar en importeer je net zoals je eigen modules.
Niet helemaal. Modules uit de standaardbibliotheek hoef je niet te installeren en importeer je op dezelfde manier als eigen modules.