Objectgeoriënteerd programmeren (OOP) is een veelgebruikt concept om applicaties te schrijven. Als data scientist moet je applicaties kunnen schrijven om je data te verwerken, naast allerlei andere taken.
In deze tutorial behandel ik de basis van objectgeoriënteerd programmeren in Python. Je leert hoe je een class maakt en objecten instantieert, hoe je methoden definieert en er argumenten aan doorgeeft, en hoe je attributen aan een class toevoegt.
TL;DR
OOP organiseert code rond classes (blauwdrukken) en objecten (instanties van die blauwdrukken)
Definieer een class met het
class-keyword en initialiseer attributen in__init__(self)Instantiemethoden werken op objectdata via
selfen worden aangeroepen met puntnotatie:my_object.method()Python ondersteunt alle vier OOP-pijlers: encapsulation, inheritance, polymorphism en abstraction
OOP bevordert hergebruik van code om meerdere objecten te maken vanuit één class-sjabloon
Wat is OOP?
Objectgeoriënteerd programmeren (OOP) is gebaseerd op het imperatieve programmeerparadigma, dat statements gebruikt om de toestand van een programma te veranderen. Imperatief betekent dat het zich richt op hóé een programma moet werken. Voorbeelden van imperatieve programmeertalen zijn C, C++, Java, Go, Ruby en Python.
Dit staat in contrast met declaratief programmeren, dat zich richt op wát het computerprogramma moet bereiken zonder te specificeren hoe (bijv. SQL, XQuery). Voor een vergelijking van paradigma’s, zie onze gids over functioneel programmeren vs. OOP.
OOP gebruikt de concepten objecten en classes. Een class kun je zien als een ‘blauwdruk’ voor objecten. Deze kunnen hun eigen
- Attributen: eigenschappen die ze bezitten
- Methoden: acties die ze uitvoeren
OOP heeft een paar belangrijke voordelen ten opzichte van andere ontwerpprincipes. Ontwikkeling is vaak sneller en goedkoper, en de modulaire aanpak maakt software makkelijker te onderhouden. Dit leidt op zijn beurt tot software van hogere kwaliteit, die ook uitbreidbaar is met nieuwe methoden en attributen.
De leercurve is echter langer. De kernconcepten vergen meer investering vooraf om volledig te begrijpen. Computationeel is OOP-software trager en gebruikt ze meer geheugen, omdat er meer code moet worden geschreven.
De vier pijlers van OOP
Naast classes en methoden rust OOP op vier pijlers die ik tot nu toe slechts kort heb aangestipt. Elk verdient een eigen deep dive, maar hier is een snel overzicht met links naar gerichte tutorials.
| Pijler | Wat het betekent | Meer leren |
|---|---|---|
| Encapsulation | Data en methoden bundelen en toegang beheren met naamconventies (_private, __name_mangled) | Encapsulation in Python |
| Inheritance | Subclasses maken die gedrag van de parent class hergebruiken en uitbreiden | Python Inheritance |
| Polymorphism | Verschillende classes delen dezelfde interface, zodat code uitwisselbaar met objecten kan werken | OOP in Python-cursus |
| Abstraction | Complexiteit verbergen achter een nette interface met abstracte basisklassen | Python Abstract Classes |
OOP-voorbeeld
Een voorbeeld van een class is de class Dog. Denk niet aan een specifieke hond of je eigen hond. We beschrijven wat een hond in het algemeen is en kan doen. Honden hebben meestal een name en age; dit zijn instantie-attributen. Honden kunnen ook bark; dit is een methode.
Als je over een specifieke hond praat, heb je in programmeren een object: een object is een instantie van een class. Dit is het basisprincipe waarop objectgeoriënteerd programmeren is gebaseerd. Mijn hond Ozzy behoort bijvoorbeeld tot de class Dog. Zijn attributen zijn name = 'Ozzy' en age = '2'. Een andere hond heeft andere attributen.
OOP in Python
Python is een van de populairste talen om OOP te leren en toe te passen. Hieronder behandel ik waarom Python goed geschikt is voor objectgeoriënteerde code en loop ik door de kernsyntax.
Is Python objectgeoriënteerd?
Python is een geweldige programmeertaal die OOP ondersteunt. Je gebruikt het om een class met attributen en methoden te definiëren, die je vervolgens aanroept.
Het grootste voordeel van Python ten opzichte van andere programmeertalen zoals Java, C++ of R is dat het een dynamische taal is met high-level datatypes. Dit betekent dat de programmeur geen types voor variabelen en argumenten hoeft te declareren, wat ontwikkeling veel sneller maakt en code leesbaarder dan in Java of C++.
Als je nieuw bent met Python, bekijk dan zeker onze cursus Introduction to Python.
Hoe maak je een class
Om een class in Python te definiëren, gebruik je het class-keyword, gevolgd door de classnaam en een dubbele punt.
Binnen de class wordt typisch een __init__-methode (een dunder-methode) gedefinieerd met def. Dit is de initializer die je later kunt gebruiken om objecten te instantiëren. Het lijkt op een constructor in Java.
__init__ wordt automatisch aangeroepen wanneer je een nieuw object maakt. Het neemt één argument: self, dat verwijst naar het object zelf.
In de methode wordt voorlopig het keyword pass gebruikt, omdat Python verwacht dat je daar iets typt. Vergeet niet de juiste inspringing te gebruiken!
class Dog:
def __init__(self):
passLet op: self in Python is gelijk aan this in C++ of Java.
In dit geval heb je een (vrij lege) Dog-class, maar nog geen object. Laten we er één maken!
Objecten instantiëren
Om een object te instantiëren, typ je de classnaam gevolgd door twee haakjes. Je kunt dit aan een variabele toewijzen om het object bij te houden.
ozzy = Dog()
En print het:
print(ozzy)
<__main__.Dog object at 0x111f47278>
Attributen toevoegen aan een class
Na het printen van ozzy is duidelijk dat dit object een hond is. Maar je hebt nog geen attributen toegevoegd. Laten we de Dog-class een naam en leeftijd geven door hem te herschrijven:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
Je ziet dat de functie nu twee argumenten na self accepteert: name en age. Deze worden vervolgens respectievelijk toegewezen aan self.name en self.age. Je kunt nu een nieuw ozzy-object met een naam en leeftijd maken:
ozzy = Dog("Ozzy", 2)
Om attributen van een object in Python te benaderen, kun je de puntnotatie gebruiken. Dit doe je door de naam van het object te typen, gevolgd door een punt en de naam van het attribuut
print(ozzy.name)
print(ozzy.age)
Ozzy
2
Dit kan ook gecombineerd worden in een meer uitgebreide zin:
print(ozzy.name + " is " + str(ozzy.age) + " year(s) old.")
Ozzy is 2 year(s) old.
De functie str() wordt hier gebruikt om het age-attribuut, een integer, om te zetten naar een string, zodat je het in de functie print() kunt gebruiken.
Methoden definiëren in een class
Nu je een Dog-class hebt, heeft die wel een naam en leeftijd die je kunt bijhouden, maar hij doet nog niets. Hier komen instantiemethoden van pas. Je kunt de class herschrijven zodat deze nu een bark()-methode bevat. Let op hoe het keyword def weer wordt gebruikt, net als het argument self.
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print("bark bark!")
De bark-methode kan nu worden aangeroepen met de puntnotatie, nadat je een nieuw ozzy-object hebt geïnstantieerd. De methode zou "bark bark!" op het scherm moeten printen. Let op de haakjes in .bark(). Deze gebruik je altijd bij het aanroepen van een methode. Ze zijn in dit geval leeg, omdat de bark()-methode geen argumenten aanneemt.
ozzy = Dog("Ozzy", 2)
ozzy.bark()
bark bark!
Weet je nog dat je eerder ozzy printte? De onderstaande code implementeert deze functionaliteit nu in de Dog-class met de doginfo()-methode. Daarna instantieer je een paar objecten met verschillende eigenschappen en roep je de methode op voor elk van hen.
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print("bark bark!")
def doginfo(self):
print(self.name + " is " + str(self.age) + " year(s) old.")
ozzy = Dog("Ozzy", 2)
skippy = Dog("Skippy", 12)
filou = Dog("Filou", 8)
ozzy.doginfo()
skippy.doginfo()
filou.doginfo()
Ozzy is 2 year(s) old.
Skippy is 12 year(s) old.
Filou is 8 year(s) old.
Zoals je ziet, kun je de doginfo()-methode op objecten aanroepen met de puntnotatie. De respons hangt nu af van op welk Dog-object je de methode aanroept.
Omdat honden ouder worden, is het handig als je hun leeftijd dienovereenkomstig kunt aanpassen. Ozzy is net 3 geworden, dus laten we zijn leeftijd veranderen.
ozzy.age = 3
print(ozzy.age)
3
Het is zo eenvoudig als een nieuwe waarde aan het attribuut toekennen. Je zou dit ook als een birthday()-methode in de Dog-class kunnen implementeren:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print("bark bark!")
def doginfo(self):
print(self.name + " is " + str(self.age) + " year(s) old.")
def birthday(self):
self.age +=1
ozzy = Dog("Ozzy", 2)
print(ozzy.age)
2
ozzy.birthday()
print(ozzy.age)
3
Nu hoef je de leeftijd van de hond niet handmatig te veranderen. Wanneer hij jarig is, kun je gewoon de birthday()-methode aanroepen.
Argumenten doorgeven aan methoden
Je wilt graag dat onze honden een buddy hebben. Dit moet optioneel zijn, omdat niet alle honden even sociaal zijn.
Bekijk de setBuddy()-methode hieronder. Die neemt zoals gebruikelijk self en buddy als argumenten. In dit geval is buddy een ander Dog-object. Stel het attribuut self.buddy in op buddy, en het attribuut buddy.buddy op self.
Dit betekent dat de relatie wederkerig is: je bent de buddy van je buddy. In dit geval wordt Filou Ozzy’s buddy, wat betekent dat Ozzy automatisch Filou’s buddy wordt.
Je zou deze attributen ook handmatig kunnen instellen in plaats van een methode te definiëren, maar dat kost meer werk (twee regels code schrijven in plaats van één) elke keer dat je een buddy wilt instellen.
Let op dat je in Python niet hoeft te specificeren welk type het argument is. In Java zou dat wel vereist zijn.
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print("bark bark!")
def doginfo(self):
print(self.name + " is " + str(self.age) + " year(s) old.")
def birthday(self):
self.age +=1
def setBuddy(self, buddy):
self.buddy = buddy
buddy.buddy = self
Je kunt de methode nu met de puntnotatie aanroepen en er een ander Dog-object aan doorgeven. In dit geval wordt Filou Ozzy’s buddy:
ozzy = Dog("Ozzy", 2)
filou = Dog("Filou", 8)
ozzy.setBuddy(filou)
Als je nu informatie over Ozzy’s buddy wilt ophalen, kun je de puntnotatie twee keer gebruiken: eerst om naar Ozzy’s buddy te verwijzen, en een tweede keer om naar zijn attribuut te verwijzen.
print(ozzy.buddy.name)
print(ozzy.buddy.age)
Filou
8
Merk op dat dit ook voor Filou kan.
print(filou.buddy.name)
print(filou.buddy.age)
Ozzy
2
De methoden van de buddy kunnen ook worden aangeroepen. Het self-argument dat aan doginfo() wordt doorgegeven is nu ozzy.buddy, oftewel filou.
ozzy.buddy.doginfo()
Filou is 8 year(s) old.
Python OOP-voorbeeld
Een voorbeeld waar objectgeoriënteerd programmeren in Python handig kan zijn, is onze tutorial Python For Finance: Algorithmic Trading. Daarin leggen we uit hoe je een tradingstrategie opzet voor een aandelenportefeuille.
De tradingstrategie is gebaseerd op het voortschrijdend gemiddelde van een aandelenkoers. Als signals['short_mavg'][short_window:] > signals['long_mavg'][short_window:] waar is, wordt een signaal gecreëerd. Dit signaal is een voorspelling voor de toekomstige koersverandering van het aandeel.
In de onderstaande code zie je dat er eerst een initialisatie is, gevolgd door de berekening van het voortschrijdend gemiddelde en het genereren van signalen. Omdat dit geen objectgeoriënteerde code is, is het gewoon één groot blok dat in één keer wordt uitgevoerd.
Let op dat we in het voorbeeld aapl gebruiken, de tickersymbol van Apple. Als je dit voor een ander aandeel wilt doen, moet je de code herschrijven.
# Initialize
short_window = 40
long_window = 100
signals = pd.DataFrame(index=aapl.index)
signals['signal'] = 0.0
# Create short simple moving average over the short window
signals['short_mavg'] = aapl['Close'].rolling(window=short_window, min_periods=1, center=False).mean()
# Create long simple moving average over the long window
signals['long_mavg'] = aapl['Close'].rolling(window=long_window, min_periods=1, center=False).mean()
# Create signals
signals['signal'][short_window:] = np.where(signals['short_mavg'][short_window:] > signals['long_mavg'][short_window:], 1.0, 0.0)
# Generate trading orders
signals['positions'] = signals['signal'].diff()
# Print `signals`
print(signals)
In een objectgeoriënteerde aanpak hoef je de initialisatie- en signaalgeneratiecode maar één keer te schrijven.
Je kunt vervolgens voor elk aandeel waarvoor je een strategie wilt berekenen een nieuw object maken en daarop de methode generate_signals() aanroepen. Merk op dat de OOP-code erg lijkt op de code hierboven, met de toevoeging van self.
class MovingAverage():
def __init__(self, symbol, bars, short_window, long_window):
self.symbol = symbol
self.bars = bars
self.short_window = short_window
self.long_window = long_window
def generate_signals(self):
signals = pd.DataFrame(index=self.bars.index)
signals['signal'] = 0.0
signals['short_mavg'] = self.bars['Close'].rolling(window=self.short_window, min_periods=1, center=False).mean()
signals['long_mavg'] = bars['Close'].rolling(window=self.long_window, min_periods=1, center=False).mean()
signals['signal'][self.short_window:] = np.where(signals['short_mavg'][self.short_window:] > signals['long_mavg'][self.short_window:], 1.0, 0.0)
signals['positions'] = signals['signal'].diff()
return signals
Je kunt nu eenvoudig een object instantiëren met de parameters die je wilt en daar signalen voor genereren.
apple = MovingAverage('aapl', aapl, 40, 100)
print(apple.generate_signals())
Dit voor een ander aandeel doen wordt heel eenvoudig. Het is slechts een kwestie van een nieuw object instantiëren met een ander tickersymbool.
microsoft = MovingAverage('msft', msft, 40, 100)
print(microsoft.generate_signals())
Tot slot
We hebben enkele van de belangrijkste OOP-concepten in Python behandeld. Je weet nu hoe je classes en methoden declareert, objecten instantieert, hun attributen instelt en instantiemethoden aanroept. Deze skills komen van pas in je toekomstige carrière als data scientist.
Voor een modernere manier om classes te definiëren, bekijk Python data classes, die boilerplate-code verminderen. Wil je de sleutelconcepten uitbreiden die je nodig hebt om verder met Python te werken, bekijk dan zeker onze cursus Intermediate Python.
Met OOP wordt je code complexer naarmate je programma groter wordt. Je krijgt verschillende classes, subclasses, objecten, inheritance, instantiemethoden en meer. Voor complexe overervingsscenario’s, leer over multiple inheritance en super().
Python OOP FAQ
Wat is objectgeoriënteerd programmeren (OOP)?
Objectgeoriënteerd programmeren is een programmeerparadigma dat is gebaseerd op het concept van "objecten", die data en code kunnen bevatten die die data manipuleert. In OOP worden objecten gemaakt op basis van sjablonen genaamd "classes", die de eigenschappen en het gedrag van de objecten die ze creëren definiëren. OOP stelt je in staat herbruikbare code te maken en concepten uit de echte wereld nauwkeuriger te modelleren, wat het tot een populaire keuze maakt voor veel softwareprojecten.
Wat zijn classes en objecten in Python?
In Python is een class een sjabloon voor het maken van objecten. Het definieert de eigenschappen en het gedrag van de objecten die ervan worden gemaakt. Een object is een instantie van een class, gemaakt door de class als een functie aan te roepen. Het object bevat de data en het gedrag dat door de class is gedefinieerd, evenals een unieke identiteit.
Hoe definieer ik een class in Python?
Om een class in Python te definiëren, gebruik je het keyword class, gevolgd door de naam van de class en een dubbele punt. De classdefinitie is ingesprongen en het ingesprongen blok bevat de eigenschappen en methoden (functies) die bij de class horen.
Hoe maak ik een object vanuit een class in Python?
Om in Python een object uit een class te maken, roep je de class aan als een functie en geef je eventuele vereiste argumenten door aan de constructor van de class (de __init__-methode).
Wat zijn de vier pijlers van OOP in Python?
De vier pijlers van objectgeoriënteerd programmeren zijn: Encapsulation (data en methoden bundelen en toegang beperken), Inheritance (classes laten erven van parent classes om attributen en methoden over te nemen), Polymorphism (objecten van verschillende types uniform kunnen behandelen) en Abstraction (complexe implementatiedetails verbergen en alleen de essentiële features tonen).
Wat is het verschil tussen een classattribuut en een instantie-attribuut?
Een classattribuut wordt gedeeld door alle instanties van een class en wordt direct in de class body gedefinieerd. Een instantie-attribuut is uniek voor elk object en wordt binnen de __init__-methode gedefinieerd met self. Bijvoorbeeld: alle honden kunnen een classattribuut species delen, maar elke hond heeft zijn eigen instantie-attribuut name.