Cursus
Encapsulation is een fundamenteel objectgeoriënteerd principe in Python. Het beschermt je classes tegen per ongeluk wijzigen of verwijderen en bevordert hergebruik en onderhoudbaarheid van code. Bekijk deze eenvoudige class-definitie:
class Smartphone:
def __init__(self, brand, os):
self.brand = brand
self.os = os
iphone = Smartphone("Apple", "iOS 17")
Veel Python-programmeurs definiëren classes zoals deze. Maar dit is verre van de best practices die pro-Pythonistas volgen. Het probleem met deze class wordt duidelijk zodra je de data probeert te wijzigen:
iphone.os = "Android"
print(iphone.os)
Android
Stel je een iPhone voor die op Android draait — wat een gotspe! We moeten duidelijk grenzen stellen binnen onze class zodat gebruikers de attributen niet naar believen kunnen veranderen.
Als ze ze toch willen wijzigen, dan moet dat onder onze voorwaarden en volgens onze regels. Maar we willen wel dat de attributen .os of .brand aan de buitenkant hetzelfde blijven.
Al dit soort zaken kun je bevorderen met encapsulation en in deze tutorial leer je er alles over. Laten we beginnen!
Hoe bereik je encapsulation in Python?
Python ondersteunt encapsulation via conventies en programmeerpraktijken in plaats van afgedwongen access modifiers. Het achterliggende uitgangspunt van veel Python-codeontwerp is: “We zijn hier allemaal volwassenen.” We kunnen encapsulation alleen als conventie toepassen en verwachten dat andere Python-ontwikkelaars onze code respecteren.
In andere OOP-talen zoals Java en C++ wordt encapsulation strikt afgedwongen met access modifiers zoals public, private of protected, maar Python heeft die niet:
| Feature | Python | Java | C++ |
|---|---|---|---|
| Access modifiers | Geen afgedwongen modifiers; gebruikt naamgevingsconventies (_protected, __private) |
Afdwinging met public, protected, private trefwoorden |
Afdwinging met public, protected, private trefwoorden |
| Getter/setter-methoden | Optioneel, vaak gebruikt met de @property decorator voor gecontroleerde toegang |
Gangbare praktijk, doorgaans geïmplementeerd als methoden | Gangbare praktijk, doorgaans geïmplementeerd als methoden |
| Data-toegang | Toegankelijk via naamgevingsconventies; vertrouwt op discipline van ontwikkelaars | Gecontroleerd door access modifiers; afgedwongen door de compiler | Gecontroleerd door access modifiers; afgedwongen door de compiler |
| Filosofie | "We zijn hier allemaal volwassenen" – vertrouwt op conventies en vertrouwen | Strikte afdwinging van toegang | Strikte afdwinging van toegang |
Dus de meeste, zo niet alle, technieken voor encapsulation die ik je ga laten zien, zijn Python-conventies. Je kunt ze eenvoudig negeren als je dat wilt. Maar ik ga ervan uit dat je ze respecteert en volgt in je eigen projecten.
Access modifiers in Python
Stel, we hebben deze eenvoudige class:
class Tree:
def __init__(self, height):
self.height = height
pine = Tree(20)
print(pine.height)
20
Er is één attribuut height dat we kunnen printen. Het probleem is dat we het ook naar willekeur kunnen veranderen:
pine.height = 50
pine.height
50
pine.height = "Grandma"
pine.height
'Grandma'
Hoe laten we gebruikers weten dat height veranderen niet is toegestaan? We kunnen er een protected member van maken door één voorafgaande underscore toe te voegen:
class Tree:
def __init__(self, height):
self._height = height
pine = Tree(20)
pine._height
20
Mensen die deze conventie kennen, begrijpen nu dat ze het attribuut alleen mogen benaderen en dat we het gebruik en wijzigen ervan sterk afraden. Maar als ze willen, kunnen ze het toch aanpassen, jazeker.
Hoe voorkomen we dat dan? Door nog een conventie te gebruiken — maak van het attribuut een private member door twee voorafgaande underscores toe te voegen:
class Tree:
def __init__(self, height):
self.__height = height
pine = Tree(20)
pine.__height
AttributeError: 'Tree' object has no attribute '__height'
Nu zal Python een fout geven als iemand het attribuut probeert te benaderen, laat staan te wijzigen.
Maar zie je wat we net deden? We hebben de enige informatie over onze objecten verstopt voor gebruikers. Onze class is nu nutteloos geworden omdat hij geen publieke attributen heeft.
Hoe geven we de boomhoogte dan wel bloot aan gebruikers, maar houden we controle over hoe die wordt benaderd en gewijzigd? Bijvoorbeeld, we willen dat boomhoogtes binnen een specifiek bereik liggen en alleen gehele waarden hebben. Hoe dwingen we dat af?
Op dit punt zal je Java-vriend waarschijnlijk voorstellen om getter- en setter-methoden te gebruiken. Laten we dat eerst proberen:
class Tree:
def __init__(self, height):
self.__height = height
def get_height(self):
return self.__height
def set_height(self, new_height):
if not isinstance(new_height, int):
raise TypeError("Tree height must be an integer")
if 0 < new_height <= 40:
self.__height = new_height
else:
raise ValueError("Invalid height for a pine tree")
pine = Tree(20)
pine.get_height()
20
Zo maak je een privaat attribuut __height, maar laat je gebruikers het gecontroleerd benaderen en wijzigen via de methoden get_height en set_height.
pine.set_height(25)
pine.get_height()
25
Voor het instellen van een nieuwe waarde zorgt set_height ervoor dat de hoogte numeriek is en binnen een bepaald bereik valt.
pine.set_height("Password")
TypeError: Tree height must be an integer
Maar deze methoden voelen als overkill voor een eenvoudige handeling. Bovendien is het lelijk om code als deze te schrijven:
# Increase height by 5
pine.set_height(pine.get_height() + 5)
Zou het niet mooier en leesbaarder zijn als we deze code konden schrijven:
pine.height += 5
en toch het juiste datatype en bereik voor height afdwingen? Het antwoord is ja, en in de volgende sectie leer je precies hoe.
De @property-decorator gebruiken in Python-classes
We introduceren een nieuwe techniek — properties maken voor attributen:
class Tree:
def __init__(self, height):
# First, create a private or protected attribute
self.__height = height
@property
def height(self):
return self.__height
pine = Tree(17)
pine.height
17
We willen dat gebruikers een verborgen attribuut met de naam __height kunnen benaderen alsof het een normaal attribuut height is. Om dat te bereiken, definiëren we een methode height die self.__height teruggeeft en we decoreren die met @property.
Nu kunnen we height aanroepen en het private attribuut benaderen:
pine.height
17
Maar het mooiste is: gebruikers kunnen het niet wijzigen:
pine.height = 15
AttributeError: can't set attribute 'height'Dus voegen we een andere methode toe,
height(self, new_height), die is omwikkeld met eenheight.setter-decorator. In deze methode implementeren we de logica die het gewenste datatype en bereik voor height afdwingt:class Tree: def __init__(self, height): self.__height = height @property def height(self): return self.__height @height.setter def height(self, new_height): if not isinstance(new_height, int): raise TypeError("Tree height must be an integer") if 0 < new_height <= 40: self.__height = new_height else: raise ValueError("Invalid height for a pine tree")Nu, wanneer een gebruiker het attribuut
heightprobeert te wijzigen, wordt@height.setteraangeroepen, waardoor de juiste waarde wordt afgedwongen:pine = Tree(10) pine.height = 33 # Calling @height.setter pine.height = 45 # An error is raisedValueError: Invalid height for a pine treeWe kunnen ook aanpassen hoe het attribuut
heightvia puntnotatie wordt benaderd met@height.getter:class Tree: def __init__(self, height): self.__height = height @property def height(self): return self.__height @height.getter def height(self): # You can return a custom version of height return f"This tree is {self.__height} meters" pine = Tree(33) pine.height'This tree is 33 meters'Hoewel we
pinemet een gehele hoogte hebben gemaakt, konden we de waarde aanpassen met@height.getter.Dit waren voorbeelden van hoe we encapsulation in een Python-class kunnen bevorderen. Onthoud: encapsulation blijft een conventie, want we kunnen het interne private member
__heightnog steeds omzeilen:pine._Tree__height = "Gotcha!" pine.height'This tree is Gotcha! meters'Alles in Python-classes is publiek, en dat geldt ook voor private methoden. Het is geen ontwerpfout, maar een uiting van de “We zijn hier allemaal volwassenen”-benadering.
Hier is een overzicht van de Python-accessmodifiers die we zojuist hebben besproken:
| Modifier/Conventie | Beschrijving | Voorbeeld |
|---|---|---|
| Public | Standaardtoegang; attributen en methoden zijn toegankelijk buiten de class | self.attribute |
| Protected | Aangegeven met een enkele underscore; een conventie die gedeeltelijk beperkte toegang aanduidt | self._attribute |
| Private | Aangegeven met dubbele underscores; name mangling biedt beperkte toegangsbeperking | self.__attribute |
| Properties | Biedt gecontroleerde toegang tot private attributen met de @property decorator |
@property def attribute(self): |
| Read-only properties | Geen @attribute.setter; staat alleen-lezen toegang tot een attribuut toe |
@property def attribute(self): |
Best practices bij het implementeren van encapsulation
Er zijn een aantal best practices die je kunt volgen om te zorgen dat je code goed aansluit bij die van andere ervaren OOP-ontwikkelaars:
- Maak protected of private attributen of methoden als ze alleen door jou worden gebruikt. Protected of private members worden uitgesloten van documentatie en signaleren aan anderen dat jij, de ontwikkelaar, ze zonder aankondiging kunt wijzigen, wat het gebruik ervan ontmoedigt.
- Je hoeft niet voor elk class-attribuut properties te maken. Voor grote classes met veel attributen kunnen
attr.getter- enattr.setter-methoden een hoofdpijndossier worden. - Overweeg een waarschuwing te geven telkens wanneer een gebruiker een protected member (
_) benadert. - Gebruik private members spaarzaam, omdat ze code onleesbaar kunnen maken voor wie niet bekend is met de conventie.
- Geef prioriteit aan duidelijkheid boven verhulling. Omdat encapsulation gericht is op onderhoudbaarheid en databeveiliging, moet je belangrijke implementatiedetails van je class niet volledig verbergen.
- Als je alleen-lezen properties wilt maken, implementeer dan geen
@attr.setter-methode. Gebruikers kunnen de property dan wel benaderen, maar niet wijzigen. - Onthoud altijd dat encapsulation een conventie is, geen afgedwongen onderdeel van de Python-syntaxis.
- Overweeg voor eenvoudige classes dataclasses, waarmee je encapsulation met één regel code kunt inschakelen. Dataclasses zijn echter bedoeld voor eenvoudigere classes met voorspelbare attributen en methoden. Bekijk deze dataclasses-tutorial voor meer informatie.
Conclusie en verdere bronnen
In deze tutorial hebben we een van de kernelementen van objectgeoriënteerd programmeren in Python geleerd: encapsulation.
Encapsulation stelt je in staat om gecontroleerde toegang te definiëren tot data die in objecten van je class is opgeslagen. Zo schrijf je schone, leesbare en efficiënte code en voorkom je onbedoelde wijzigingen of verwijdering van je class-data.
Hier zijn nog enkele verwante bronnen om je OOP-kennis te verdiepen:
FAQs
Hoe verschilt encapsulation van abstraction in Python?
Encapsulation draait om het bundelen van data en de methoden die op die data werken binnen één eenheid, zoals een class, en het beperken van de toegang tot sommige componenten. Abstraction daarentegen gaat over het verbergen van de complexe realiteit terwijl alleen de essentiële delen zichtbaar blijven. Waar encapsulation zich richt op het beperken van toegang, richt abstraction zich op het vereenvoudigen van interacties.
Kan encapsulation worden gebruikt in functioneel programmeren, of is het exclusief voor OOP?
Encapsulation is nauw verbonden met objectgeoriënteerd programmeren (OOP) en wordt doorgaans niet gebruikt in functioneel programmeren. Functioneel programmeren legt de nadruk op onveranderlijkheid en stateloze functies, in tegenstelling tot OOP, dat zich richt op objecten en het inkapselen van data binnen die objecten.
Wat zijn veelvoorkomende valkuilen bij het implementeren van encapsulation in Python?
Veelvoorkomende valkuilen zijn het overmatig gebruik van private variabelen, wat de code moeilijk te onderhouden kan maken, en het niet bieden van voldoende toegangsmethoden (getters/setters), wat tot fragiele code kan leiden. Daarnaast vergeten ontwikkelaars soms dat encapsulation in Python op conventie is gebaseerd en dus kan worden omzeild.
Hoe kan encapsulation de beveiliging van code verbeteren?
Encapsulation kan de beveiliging van code verbeteren door de toegang tot de interne staat van een object te beperken en ongeoorloofde wijzigingen te voorkomen. Door te bepalen hoe data wordt benaderd en gewijzigd, kunnen ontwikkelaars ervoor zorgen dat de data geldig en consistent blijft.
Hoe beïnvloedt het gebruik van de @property-decorator de performance in Python?
Het gebruik van de @property decorator kan een kleine overhead introduceren door extra methodeaanroepen, maar in de meeste gevallen is deze impact verwaarloosbaar. De voordelen van verbeterde leesbaarheid en onderhoudbaarheid wegen meestal ruimschoots op tegen de lichte prestatiekosten.
Is het mogelijk om encapsulation in Python net zo strikt af te dwingen als in Java of C++?
Python dwingt encapsulation niet strikt af zoals Java of C++. De filosofie is gebaseerd op conventies en het begrip dat ontwikkelaars het beoogde gebruik van private en protected members respecteren. Het gebruik van naamgevingsconventies zoals underscores kan misbruik echter wel ontmoedigen.
Wat zijn realistische toepassingen van encapsulation in Python-projecten?
Encapsulation wordt gebruikt om gevoelige data te beschermen in toepassingen zoals banksoftware, waar accountgegevens worden afgeschermd. Het wordt ook gebruikt in libraries en frameworks om complexe implementatiedetails te verbergen, zodat ontwikkelaars met eenvoudige interfaces kunnen werken.
Hoe test ik ingekapselde code effectief in Python?
Het testen van ingekapselde code houdt in dat je unittests schrijft voor de publieke interface van een class. Door de methoden en properties te testen die aan de gebruiker worden blootgesteld, zorg je ervoor dat interne toestand correct wordt beheerd en dat de encapsulation-logica standhoudt.
Kan encapsulation worden toegepast op Python-modules, of is het beperkt tot classes?
Hoewel encapsulation primair met classes wordt geassocieerd, kun je het concept ook toepassen op modules door functies of variabelen met een underscore te prefixen om privégebruik binnen een module te signaleren.
Hoe verhoudt encapsulation zich tot polymorphism in Python?
Encapsulation en polymorphism zijn beide OOP-principes, maar hebben verschillende doelen. Encapsulation beperkt de toegang tot bepaalde delen van een object, terwijl polymorphism het mogelijk maakt om objecten te behandelen als instanties van hun bovenliggende class, wat het uitbreiden en onderhouden van code vergemakkelijkt. Samen kun je er flexibele en veilige codestructuren mee bouwen.
Ik ben een contentmaker op het gebied van data science met meer dan 2 jaar ervaring en een van de grootste achterbannen op Medium. Ik schrijf graag uitgebreide artikelen over AI en ML met een vleugje sarcastische stijl, want je moet toch íets doen om ze wat minder saai te maken. Ik heb meer dan 130 artikelen en een DataCamp-cursus gemaakt, en er is nog een nieuwe in de maak. Mijn content is door meer dan 5 miljoen paar ogen gezien, van wie 20k volgers werden op zowel Medium als LinkedIn.

