Objectgeoriënteerd programmeren (OOP) groepeert data en gedrag samen in een klasse. Een klasse is als een blauwdruk; een object (of instantie) is een concreet exemplaar van die blauwdruk.
In de ML-wereld kom je klassen voortdurend tegen: elke bibliotheek (NumPy, Pandas, Matplotlib) is opgebouwd uit klassen. Begrijpen hoe klassen werken helpt je om bibliotheken correct te gebruiken en eenvoudige eigen hulpklassen te schrijven.
Een klasse definiëren¶
Een klasse definieer je met het sleutelwoord class. De constructor __init__ wordt aangeroepen telkens wanneer je een nieuw object aanmaakt.
class Student:
"""Represents a student with a name and a list of scores."""
def __init__(self, name, scores):
self.name = name # instance variable
self.scores = scores # instance variable
def average(self):
"""Return the mean of the scores."""
return sum(self.scores) / len(self.scores)
def is_passing(self, threshold=60):
"""Return True if the average score meets the threshold."""
return self.average() >= threshold
def __repr__(self):
"""Readable representation — useful for debugging and Pandas display."""
return f"Student(name={self.name!r}, avg={self.average():.1f})"
# Create objects
alice = Student("Alice", [88, 92, 79, 95])
bob = Student("Bob", [55, 62, 48, 70])
print(alice.average()) # 88.5
print(alice.is_passing()) # True
print(bob.is_passing(60)) # False
print(alice) # Student(name='Alice', avg=88.5)88.5
True
False
Student(name='Alice', avg=88.5)
Instantievariabelen vs. klassevariabelen¶
Instantievariabelen zijn uniek per object (self.name). Klassevariabelen worden gedeeld door alle objecten van de klasse.
class Counter:
"""Counts the total number of created instances."""
count = 0 # class variable — shared by all instances
def __init__(self, label):
self.label = label # instance variable
Counter.count += 1
a = Counter("a")
b = Counter("b")
c = Counter("c")
print(Counter.count) # 3 — all instances counted
print(a.label) # 'a'3
a
Overerving¶
Met overerving (class inheritance) kun je een nieuwe klasse definiëren die de eigenschappen en methoden van een bestaande klasse erft en eventueel uitbreidt. Je ziet dit patroon overal in bibliotheken.
class Child(Parent):
...class Animal:
"""Base class for animals."""
def __init__(self, name):
self.name = name
def sound(self):
return "..."
def __repr__(self):
return f"{self.__class__.__name__}(name={self.name!r})"
class Dog(Animal):
"""Dog inherits from Animal and overrides sound()."""
def sound(self):
return "Woof!"
class Cat(Animal):
def sound(self):
return "Meow!"
animals = [Dog("Rex"), Cat("Whiskers"), Dog("Buddy")]
for animal in animals:
print(f"{animal.name}: {animal.sound()}")Rex: Woof!
Whiskers: Meow!
Buddy: Woof!
__str__ vs. __repr__¶
__repr__: bedoeld voor ontwikkelaars — moet duidelijk en unambiguous zijn. Wordt gebruikt in de REPL en bijprint()als__str__niet gedefinieerd is.__str__: bedoeld voor eindgebruikers — mag vriendelijker geformatteerd zijn.
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
def __repr__(self):
return f"Temperature(celsius={self.celsius})"
def __str__(self):
return f"{self.celsius}°C ({self.celsius * 9 / 5 + 32:.1f}°F)"
t = Temperature(100)
print(repr(t)) # Temperature(celsius=100)
print(str(t)) # 100°C (212.0°F)
print(t) # uses __str__Temperature(celsius=100)
100°C (212.0°F)
100°C (212.0°F)
Een praktisch voorbeeld: Dataset-klasse¶
Hieronder combineren we alles in een kleine hulpklasse die je zou kunnen gebruiken vóór je Pandas leert.
class Dataset:
"""Simple container for a numeric dataset with basic statistics."""
def __init__(self, name, values):
self.name = name
self.values = list(values)
def n(self):
return len(self.values)
def mean(self):
return sum(self.values) / self.n()
def minimum(self):
return min(self.values)
def maximum(self):
return max(self.values)
def summary(self):
"""Print a concise summary of the dataset."""
print(f"Dataset: {self.name}")
print(f" n = {self.n()}")
print(f" min = {self.minimum()}")
print(f" max = {self.maximum()}")
print(f" mean = {self.mean():.2f}")
def __repr__(self):
return f"Dataset(name={self.name!r}, n={self.n()})"
temperatures = Dataset("Daily temperatures", [12.1, 15.4, 9.8, 18.2, 14.7, 11.3])
temperatures.summary()
print(temperatures)Dataset: Daily temperatures
n = 6
min = 9.8
max = 18.2
mean = 13.58
Dataset(name='Daily temperatures', n=6)
Oefeningen¶
Oefening 1 — Schrijf een klasse Rectangle met:
__init__(self, width, height)— sla breedte en hoogte op als instantievariabelenarea()— geeftwidth * heightterugperimeter()— geeft2 * (width + height)terug__repr__— bijv.Rectangle(width=5, height=3)
Test: maak twee rechthoeken en vergelijk hun oppervlakten.
# your solution hereOefening 2 — Breid de klasse Rectangle uit met een subklasse Square waarbij de constructor slechts één argument side aanvaardt en super().__init__() aanroept. Overschrijf __repr__ om Square(side=4) te tonen.
# your solution hereOefening 3 — Voeg aan de klasse Dataset een methode above_mean() toe die een lijst teruggeeft van alle waarden die strikt groter zijn dan het gemiddelde. Gebruik een list comprehension.
# Copy the Dataset class from above and add the method
# Test:
# ds = Dataset("test", [3, 7, 2, 9, 4, 8, 1])
# ds.above_mean() → [7, 9, 8]Oefening 4 — Schrijf een klasse ColoredRectangle die erft van Rectangle en een extra parameter color heeft. Voeg een methode description() toe die een zin teruggeeft als "A red rectangle of 5x3 with area 15."
# your solution here