NETWERKEN en OBJECTORIËNTATIE

Maat: px
Weergave met pagina beginnen:

Download "NETWERKEN en OBJECTORIËNTATIE"

Transcriptie

1 FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN! CAMPUS DE NAYER! NETWERKEN en OBJECTORIËNTATIE Deel 2: Objectoriëntatie in Python Joost Vennekens

2

3 Inhoudsopgave 1 Objecten en klassen Een beetje geschiedenis Objecten Klassen Constructoren Magische methodes Wijzigen van attributen Voorbeelden De klasse Drank De klasse Rechthoek De klasse Cirkel Een blik achter de schermen Samenwerkende objecten Een object als argument Een object als resultaat Associaties tussen klassen Lijsten en objecten Objecten met lijsten Lijsten van objecten Objecten met lijsten van objecten Voorbeelden Punten en veelhoeken Cocktails en dranken Nog wat terminologie Overerving Het overschrijven van methodes Overervingshiërarchieën Methodes uit een superklasse oproepen Voorbeelden Vierkanten Varia Python als server-side scripting taal Methodes met default Laargumenten Vergelijken van objecten

4 4.4 Nog meer vergelijkingen Uitzonderingen Uitzonderingen opwerpen Uitzonderingen afhandelen Herwerking van het voorbeeld Statische methodes Nog een blik achter de schermen De volledige klasse Cirkel Private variabelen Eigenschappen (Properties) Python voor data-analyse De Pandas bibliotheek De SciKit Learn Bibliotheek Een voorbeeld analyse A Practicum opgaves 87 A.1 Een eenvoudig datamodel: Auto A.2 Een eenvoudig datamodel: Punt A.3 Een eenvoudig datamodel: Ruimte A.4 Visualisatie van een ruimte A.4.1 Installatie op de server A.4.2 Genereren van SVG code A.5 Overerving: bewegende auto s A.6 Visualisatie van oude toestanden A.7 Dynamische visualisatie van een ruimte A.8 Regio s in het beeld

5 Objecten en klassen 1 Dit hoofdstuk introduceert de basisconcepten van het objectgericht programmeren (OP). Deze manier van programmeren werd oorspronkelijk ontwikkeld om een antwoord te bieden op problemen met voorgaande programmeerstijlen. We schetsen in dit hoofdstuk kort over welke problemen het juist gaat en hoe OP deze problemen probeert aan te pakken. In deze cursus wordt gebruik gemaakt van de programmeertaal Python. De concepten van OP die worden aangebracht zijn echter niet beperkt tot Python alleen. We vinden ze even goed terug in andere objectgerichte programmeertalen, zoals bijvoorbeeld Java, Javascript, PHP, C++ of C#. 1.1 Een beetje geschiedenis Als 1 programmeur op 1 dag 100 lijnen Python code kan schrijven, hoeveel lijnen code schrijft hij dan op 10 dagen? Of, hoeveel code schrijft een team van 10 programmeurs dan op 1 dag? Het is vooral voor IT managers verleidelijk om op deze vragen het antwoord 1000 te geven. De praktijk wijst echter uit dat echte antwoord veel minder is. De verklaring daarvoor ligt in het feit dat lijnen code niet onafhankelijk zijn van elkaar. Als de ene lijn een toekenning x = x * 2 doet, en een paar lijnen erna volgt een test op de waarde van x: i f x > 5: print "wat veel!" dan is de eerste lijn code duidelijk relevant voor de tweede lijn. Met andere woorden, om te kunnen begrijpen wat de tweede lijn juist doet, moeten we ons bewust zijn van het bestaan van de eerste lijn. Een programma van 200 regels is daarom niet gewoon maar dubbel zo complex als een programma van 100 regels, maar veel méér: elke lijn van de 200 regels kan immers potentieel relevant zijn voor elke andere lijn. Er zijn dus mogelijke interacties tussen lijnen code, die allemaal relevant kunnen zijn voor de programmeur, ten opzichte van mogelijke interacties in het kortere programma. De complexiteit 5

6 van een programma hoe moeilijk het is om dit programma te begrijpen stijgt dus veel sneller dan het aantal lijnen code. Programmeertalen hebben sinds de begindagen van de computer een grote ontwikkeling doorgemaakt. Deze evoluties kunnen best begrepen worden als een voortdurende zoektocht naar manieren om dit fenomeen tegen te gaan. Globaal gesproken is het de bedoeling om grote programma s zoveel mogelijk uiteen te trekken in kleinere stukjes. Als we ons programma van 200 lijnen code kunnen opdelen in twee stukken van elk 100 lijnen én we kunnen dit zodanig doen dat we er zeker van zijn dat geen enkele lijn code uit het ene deel relevant is voor het andere deel, dan blijven en slechts mogelijke interacties over, wat een pak minder is dan Maar hoe kunnen we dat nu verwezenlijken? In het begin zocht men vooral zijn heil in het opsplitsen van de grote taak die een programma moest vervullen in kleinere deeltaken. Dit heeft geleid tot het invoeren van functies. De hoop hierbij was dat men een programma zou kunnen begrijpen door al zijn functies te begrijpen, en dat men elke functie afzonderlijk zou kunnen begrijpen. Men dacht, met andere woorden, dat enkel maar lijnen code binnen dezelfde functie relevant zouden zijn voor elkaar, zodat het zou volstaan om een programma op te splitsen in functies die klein genoeg zijn. Als we onze 200 lijnen code opsplitsen in 10 functies van 20 lijnen, dan zijn er maar mogelijke interacties, wat een grootte-orde minder is dan de oorspronkelijke : eureka! Helaas bleek al snel dat dit in de praktijk toch niet zo goed werkte. De reden hiervoor ligt zelfs nogal voor de hand: als we ons programma gaan opsplitsen in deeltaken, dan moeten we deze deeltaken natuurlijk na elkaar uitvoeren. Maar dat betekent dat de invoer van taak 2 natuurlijk de uitvoer van taak 1 zal zijn! En de invoer van taak 20 is de uitvoer van taak 19, wiens invoer de uitvoer van taak 18 was, wiens invoer de uitvoer was van taak 17, wiens invoer.... We zien al snel dat een taak helemaal niet onafhankelijk is van de taken die ervoor kwamen, maar dat ze hier juist heel erg van afhangt. Dit betekent ook dat als we in taak 1 een aanpassing doen, deze aanpassing mogelijk een effect kan hebben op taak 2, en dus ook op taak 3, en dus ook op.... Als we een regel veranderen in taak 1, dan zouden we dus eigenlijk alle andere regels code uit taken 2 t/m 20 terug moeten gaan bekijken, om te zien of we ze ook niet moeten aanpassen. Dat is duidelijk niet wat we willen. In het begin van de jaren 90 is men dan op zoek gegaan naar een alternatief. Merk op dat men hier dus niet gewoon maar op zoek was naar een nieuwe programmeertaal, maar naar een nieuw wereldbeeld. Er was nood aan een andere manier van kijken: een programma moest niet langer gezien worden als een verzameling van taken die konden worden opgesplitst in deeltaken, maar als... iets anders? De grote doorbraak die er dan gekomen is, is dat men beseft heeft dat een oud concept, bedacht door academici in de jaren 60, eigenlijk perfect het antwoord op deze vraag kon geven. Het concept was dat van objectgericht programmeren, en het antwoord is simpelweg: beschouw een programma niet langer als een verzameling van (deel-)taken, maar als een verzameling van samenwerkende objecten. Hoewel dit antwoord 6

7 dus al bestaat sinds de jaren 60, heeft het tot de jaren 90 geduurd voor men eindelijk de bijhorende vraag bedacht heeft, namelijk: hoe kunnen we onze programma s zodanig structureren dat zoveel mogelijk regels code onafhankelijk zijn van elkaar? 1.2 Objecten Om echt te kunnen begrijpen hoe het objectgerichte wereldbeeld ertoe kan leiden dat programma s beter (dwz. met meer onafhankelijkheden) gestructureerd worden, is het nodig om eerst wat dieper in te gaan op de betekenis die de term object in deze context heeft. We doen dit door even versneld de geschiedenis van het programmeren door te maken, aan de hand van een eenvoudig voorbeeld. Een cocktail bestaat uit een aantal ingrediënten die in een specifieke verhouding door elkaar gemengd moeten worden. Een vodka-orange bestaat bijvoorbeeld voor een derde uit vodka, en voor de rest uit fruitsap. Dit betekent dat om een glas van 25cl te vullen met vodka-orange, er 8,3cl vodka nodig is en 16,7cl fruitsap. En als we bijvoorbeeld al 10cl vodka hebben ingeschonken, is er nog 20cl fruitsap nodig. Dergelijke berekeningen worden natuurlijk des te uitdagender, naarmate er meer vodka-oranges geconsumeerd worden. Laten we daarom een computerprogramma maken dat ons kan helpen. verhouding = 1.0 / 3.0 cocktail # aandeel vodka per eenheid def vodkavoorcocktail ( cocktail ) : return verhouding * cocktail def fruitsapvoorcocktail ( cocktail ) : return (1 verhouding ) * cocktail def vodkavoorfruitsap ( fruitsap ) : return fruitsap * ( verhouding / (1 verhouding ) ) def fruitsapvoorvodka ( vodka ) : return vodka * ( ( 1 verhouding ) / verhouding ) Deze functies gebruiken we dan natuurlijk als volgt: >>> vodkavoorcocktail(25) >>> fruitsapvoorvodka(10) In traditionele terminologie hebben we hier gebruik gemaakt van een datastructuur, waarin we de gegevens die we nodig hebben kunnen bijhouden, en daarbij horen een aantal functies, die op basis van deze datastructuur de gewenste uitvoer produceren. Natuurlijk is onze datastructuur hier heel eenvoudig, aangezien hij enkel maar bestaat uit de waarde 1/3. Zelfs voor zo n eenvoudige datastructuur zijn er echter tal van alternatieven te bedenken. Bijvoorbeeld, we hadden in 7

8 plaats van de hoeveelheid vodka per eenheid cocktail, ook de verhouding tussen de hoeveelheid fruitsap en de hoeveelheid vodka kunnen gebruiken. verhouding = 2.0 def vodkavoorfruitsap2 ( fruitsap ) : return fruitsap / verhouding def fruitsapvoorvodka2 ( vodka ) : return verhouding * vodka # eenheden fruitsap per eenheid vodka def vodkavoorcocktail2 ( cocktail ) : return cocktail / ( verhouding + 1) def fruitsapvoorcocktail2 ( cocktail ) : vodka = vodkavoorcocktail2 ( cocktail ) return fruitsapvoorvodka2 ( vodka ) Deze functies berekenen natuurlijk net dezelfde resultaten als voorheen: >>> vodkavoorcocktail2(25) >>> fruitsapvoorvodka2(10) 20.0 Hier zien we een eenvoudige illustratie van een belangrijk fenomeen: als we de voorstelling van onze data veranderen, dan moeten we ook alle functies veranderen die deze data gebruiken. Wat zou er gebeuren als we dit zouden vergeten? Dan zouden we bijvoorbeeld per ongeluk onze oude definitie van de functie fruitsapvoorvodka(vodka) samen kunnen gebruiken met onze nieuwe datavoorstelling verhouding = 2.0. Het is duidelijk dat dit een fout resultaat zal opleveren, en een cocktail die veel te licht is. Historisch weetje: In 1999 verloor de NASA $ toen de Mars Climate Orbiter missie mislukte. De oorzaak van het feit dat deze satelliet opbrandde in de atmosfeer van Mars, zonder ook maar één zinvol resultaat te produceren, was een software-fout: de ene functie dacht dat een bepaald getalletje een waarde in Newton voorstelde, terwijl de andere dacht dat dit een waarde in Pond was. Of het nu gaat om ontploffende satellieten of cocktails die niet straf genoeg zijn, de les is dezelfde: het is gevaarlijk om een datastructuur los te zien van de functies die hem moeten gebruiken. Deze les is meteen de belangrijkste motivatie voor objectgericht programmeren. Laat ons, voor we verder gaan, eerst eens een meer realistische versie van ons programma bekijken. Het is natuurlijk een beetje belachelijk dat we code geschreven hebben die enkel maar werkt voor vodka-oranges. Met een kleine beetje extra moeite, zouden we code kunnen schrijven die werkt voor alle cocktails. Hiervoor zullen we natuurlijk onze datastructuur iets ingewikkelder moeten maken, en onze functies daaraan aanpassen. # ingredienten per eenheid cocktail 8

9 vodkaorange = { vodka : 0.33, fruitsap : 0. 67} def ingredientpercocktail ( cocktail, hoeveelheid, ingredient ) : return hoeveelheid * cocktail [ ingredient ] def ingredientperingredient ( cocktail, hoeveelheid, gegeven, gezocht ) : return hoeveelheid * ( cocktail [ gezocht ]/ cocktail [ gegeven ] ) Als we nu bijvoorbeeld willen weten hoeveel vodka er nodig is voor 20cl fruitsap, doen we: >>> ingredientperingredient(vodkaorange, 20, fruitsap, vodka ) We gebruiken nu als datastructuur een woordenboek (dictionary) en hebben twee functies geschreven die gebruik maken van deze datastructuur. Hier is nu eens een idee: aangezien we toch net besloten hebben dat de functies die een datastructuur gebruiken onafscheidelijk met deze datastructuur verbonden zijn, waarom steken we die functies dan niet gewoon bij in dat woordenboek? In Python kan dit immers perfect: we kunnen met een functie alles doen wat we met bijvoorbeeld een getal of een string kunnen doen. Iets als dit kan bijvoorbeeld perfect: def dubbel ( x ) : return x * 2 print dubbel ( 3 ) functie = dubbel print functie ( 3 ) De toekenning in de voorlaatste lijn geeft de functie dubbel in wezen gewoon een tweede naam, die we zoals de laatste lijn laat zien daarna eveneens kunnen gebruiken om de functie op te roepen. We kunnen dus even goed onze functie nemen en deze bij in de gegevensstructuur van onze cocktail plaatsen. def ingredientpercocktail ( cocktail, hoeveelheid, ingredient ) : return hoeveelheid * cocktail [ ingredient ] def ingredientperingredient ( cocktail, hoeveelheid, gegeven, gezocht ) : verhouding = cocktail [ gezocht ] / cocktail [ gegeven ] return hoeveelheid * verhouding vodkaorange = { vodka : 0.33, fruitsap : 0.67, ipc : ingredientpercocktail, ipi : ingredientperingredient } Eender welke bewerking we met onze vodkaorange willen doen, kunnen we nu voor elkaar krijgen door enkel maar naar dit 9

10 woordenboek te kijken. Het berekenen van een hoeveelheid fruitsap per hoeveelheid vodka, kan nu bijvoorbeeld zo: vodkaorange [ ipi ] ( vodkaorange, 20, fruitsap, vodka ) Hier komen dus zowel de functie die we toepassen als de data waarop we ze toepassen uit hetzelfde woordenboek. Een groot voordeel van deze aanpak is dat gelijk welke datastructuur we nu kiezen om onze gegevens in voor te stellen, we altijd de juiste functies erbij zullen hebben. Veronderstel bijvoorbeeld dat we, in plaats van te zeggen dat een Bloody Mary voor 0.25 uit vodka bestaat en voor 0.75 uit tomatensap (zoals we hierboven deden voor onze vodka-orange), liever zeggen dat hij één deel vodka moet bevatten per 3 delen tomatensap. bloodymary = { vodka : 1. 0, tomatensap : 3. 0} Bij deze voorstelling horen nu natuurlijk andere functies dan bij onze vodka-orange, namelijk: def ingredientpercocktail2 ( cocktail, hoeveelheid, ingredient ) : som = 0 for drank in cocktail : som += cocktail [ drank ] return hoeveelheid * cocktail [ ingredient ] / som (De functie ingredientperingredient mag dezelfde blijven, aangezien de som daar in zowel teller als noemer zou staan.) Deze functie kunnen we nu ook bij in onze bloodymary steken. bloodymary [ ipi ] = ingredientperingredient bloodymary [ ipc ] = ingredientpercocktail2 Nu lopen we dus nooit het risico dat we ons zullen vergissen tussen ingredientpercocktail en ingredientpercocktail2! (Opmerking voor de aandachtige lezer: Moest je bovenstaande code effectief proberen uit te voeren, zou je merken dat er nog een fout in dit programma zit. Het is instructief om eens na te denken over hoe we deze fout in het algemeen zouden kunnen vermijden.) Er is nog een tweede voordeel, dat mooi geïllustreerd wordt door volgend fragmentje, dat berekent hoeveel vodka we nodig hebben voor 25cl Vodka-orange en 25cl Bloody Mary samen. cocktails = [ vodkaorange, bloodymary ] vodkanodig = 0 for c in cocktails : vodkanodig += c [ ipc ] ( c, 25, vodka ) Hoewel dat aan dit fragmentje helemaal niet te zien is, zal er hier voor onze twee verschillende cocktails een verschillende functie worden opgeroepen. Bovendien zal dit ook telkens de juiste functie zijn! Het feit dat de twee cocktails achter de schermen een verschillende voorstelling voor hun gegevens gebruiken is nu vanuit het oogpunt van bovenstaand fragmentje niet meer relevant. We hebben hier dus een mooie onafhankelijkheid tussen verschillende delen van onze code kunnen realiseren. 10

11 Deze manier van werken is nu de essentie van het objectgeöriënteerd programmeren. Met de term object bedoelt men immers niet meer of niet minder dan een gegevensstructuur waar alle functies die nodig zijn om deze gegevensstructuur te manipuleren bij inzitten. Samengevat: een gegevensstructuur wéét iets, een functie kán iets, en een object weet niet alleen iets, maar kan daar ook iets mee. Het voornaamste voordeel van de objectgeöriënteerde manier van werken is encapsulatie. Dit betekent dat, zolang we de gegevens die in een object vervat zitten enkel maar manipuleren door middel van de functies die in het object zitten, we helemaal niet hoeven te weten hoe dit object deze gegevens juist voorstelt. Al deze details zitten immers netjes ingekapseld in het object. Hierdoor kunnen we ook op elk moment de gegevensvoorstelling veranderen bijvoorbeeld van onze vodkaorange voorstelling naar de bloodymary zonder dat we aan de rest van ons programma iets hoeven te veranderen. De essentie van objectgeöriënteerd programmeren zit hem dus in de functies die bij in het object zitten. Aangezien dit concept van een functie die bij in een object zit zodanig belangrijk is, heeft men daar dan ook maar meteen een woord voor verzonnen: dit noemt men een methode. object = gegevens + gedrag Een methode is een functie in een object 1.3 Klassen In de vorige sectie hebben we de essentie van objectgericht programmeren uit de doeken gedaan, zonder daarvoor iets meer over Python te zien dan er in de cursus van het 1 e jaar reeds besproken werd. Python is echter een objectgerichte programmeertaal, wat betekent dat deze taal een aantal speciale voorzieningen zogenaamde syntactische suiker aanbiedt om programmeren op de objectgerichte manier aangenamer te maken. Een eerste ding dat al meteen opvalt als we onze cocktailbar verder willen uitbreiden, is dat we nogal veel repetitief tikwerk moeten doen. bloodymary = { vodka : 0.34, tomatensap : 0.66, ipc : ingredientpercocktail, ipi : ingredientperingredient } vodkaorange = { vodka : 0.34, fruitsap : 0.66, ipc : ingredientpercocktail, ipi : ingredientperingredient } gintonic = { gin : 0.34, tonic : 0.66, ipc : ingredientpercocktail, ipi : ingredientperingredient } Al deze cocktail-objects hebben immers dezelfde methodes. Aangezien luiheid een grote deugd is voor een programmeur, zouden we liever gewoon één keer zeggen dat alle cocktail-objectjes deze methodes moeten hebben, in plaats van dit elke keer opnieuw te moeten tikken. 11

12 Hiervoor bestaat het concept van een klasse: een klasse is een verzameling van objecten die allemaal dezelfde methodes hebben. In het geval van ons voorbeeld, gaan we dus een klasse Cocktail invoeren, de methodes ingredientpercocktail en ingredientperingredient koppelen aan deze klasse, en tot slot zeggen dat bloodymary, vodkaorange en gintonic allemaal Cocktails zijn. Dit gaat als volgt: class Cocktail : def ingredientpercocktail ( cocktail, hoeveelheid, ingredient ) : pass # <- Hier komt nog een berekening def ingredientperingredient ( cocktail, hoeveelheid, gegeven, gezocht ) : pass # <- Hier komt nog een berekening gintonic = Cocktail ( ) bloodymary = Cocktail ( ) vodkaorange = Cocktail ( ) Met het sleutelwoord class definiëren we dus een klasse met een bepaalde naam, waarbij we dan alle methodes van deze klasse opsommen. Nadien kunnen we de naam van deze klasse gebruiken als een functie die een nieuw objectje aanmaakt, dat tot deze bepaalde klasse behoort. We zeggen dan ook wel dat dit object een instantiatie van deze klasse is. Het netto-effect is dus dat aan elk object dat door middel van de uitdrukking Cocktail() wordt aangemaakt, de twee methodes worden toegevoegd die in de declaratie van deze klasse zijn opgenomen. In de voorgaan sectie hebben we een woordenboek gebruikt om een object voor te stellen. In Python zijn echte objecten (dwz. objecten die zijn aangemaakt op basis van een class) een klein beetje verschillend van woordenboeken. Daar waar we in een woordenboek volgende notatie gebruiken om aan een nieuw sleutel-waarde paar toe te voegen: woordenboek [ sleutel ] = waarde doen we dat met een object als volgt: object. sleutel = waarde Ook hier is een beetje terminologie voor: we noemen sleutel in dit geval een attribuut van het object. Nadat we dus bovenstaande Cocktails hebben aangemaakt, kunnen we er als volgt ingrediënten aan toevoegen: gintonic. gin = 0.34 gintonic. tonic = 0.66 Nu heeft het object gintonic dus twee methodes (namelijk de methodes ingredientpercocktail en ingredientperingredient van zijn klasse) en twee attributen (gin en tonic). De notatie om methodes en attributen van een object aan te spreken is trouwens 12

13 identiek dezelfde, alleen zijn er natuurlijk ook haakjes en argumenten nodig om een methode op te roepen. gintonic. ingredientpercocktail ( argumenten ) Zonder de haakjes en argumenten, zouden we de methode niet oproepen, maar krijgen we gewoon deze methode zelf terug, zoals te zien is in de Python interpreter: De gegevens in een object heten attributen >>> gintonic.ingredientpercocktail <bound method Cocktail.ingredientPerCocktail of < main.cocktail instance at 0x50da08>> Laten we nu deze methode ook eens implementeren. In de vorige sectie hadden we deze functie: def ingredientpercocktail ( cocktail, hoeveelheid, ingredient ) : return hoeveelheid * cocktail [ ingredient ] Nu we van de cocktail een object gemaakt hebben in plaats van een woordenboek, moeten we deze functie natuurlijk aanpassen. Het idee is dat we eigenlijk dit zouden willen doen: def ingredientpercocktail ( cocktail, hoeveelheid, ingredient ) : return hoeveelheid * cocktail. ingredient Maar dit zal helaas niet werken! Deze code zal immers op zoek gaan naar een (niet-bestaand) attribuut ingredient van het object gintonic. Terwijl we eigenlijk willen dat als we deze functie oproepen met als laatste argument bv. de string gin, dat dan het attribuut cocktail.gin gezocht zou worden. Gelukkig kent Python speciaal voor dit probleem een functie getattr(object,naam), waarvan het tweede argument een string moet zijn. Met andere woorden, als we getattr(gintonic, gin ) doen, dan zal het attribuut gintonic.gin worden opgehaald. Hiermee wordt de definitie van onze klasse dan: class Cocktail : def ingredientpercocktail ( z e l f, hoeveelheid, ingredient ) : return hoeveelheid * getattr ( z e l f, ingredient ) def ingredientperingredient ( z e l f, hoeveelheid, gegeven, gezocht ) : verhouding = getattr ( z e l f, gezocht ) /getattr ( z e l f, gegeven ) return hoeveelheid * verhouding We hebben nu ook nog een tweede, kleine wijziging gedaan tov. onze vorige code. We hebben het eerste argument van onze methodes hernoemd naar zelf. In Python is het eerste argument van een methode altijd het object waarbij de methode hoort. De conventie is om dit argument altijd deze naam te geven (of self in Engelstalige code). 13

14 zelf wordt impliciet meegegeven We zouden verwachten dat we deze methode nu als volgt kunnen oproepen: gintonic. ingredientpercocktail ( gintonic, 20, gin ) Dit is echter niet helemaal juist. In werkelijkheid is het namelijk nog net iets eenvoudiger. Het eerste argument van deze methode-oproep zal immers toch altijd hetzelfde zijn als het object waarin de methode zelf zit. Meer nog, het feit dat de methode gegevens manipuleert die in haar eigen object zitten is net de essentie van objectgericht programmeren! Daarom zal Python dit eerste argument impliciet achter de schermen zelf doorgeven, zonder dat wij dit zelf hoeven te doen gintonic. ingredientpercocktail ( 20, gin ) Hoewel er dus in de definitie van deze methode drie argumenten waren, moeten we er bij de oproep van deze methode slechts twee zelf expliciet doorgeven. Het ontbrekende argument is het object waarop de methode wordt opgeroepen (gintonic, in dit geval), dat als impliciet eerste argument wordt doorgegeven. Het eerste argume van een methode zelf 1.4 Constructoren We hebben tot dusver de ingrediënten van onze cocktails gewoon voorgesteld door een string ( gin, vodka,... ). Als we in ons programma meer moeten weten over een drank dan enkel maar zijn naam, dan zal deze voorstelling ontoereikend zijn en hebben we nood aan een gegevensstructuur waarin we al de relevante informatie over een drank kunnen bijhouden. Hiervoor kunnen we natuurlijk ook weer objecten gaan gebruiken. We hebben dan een klasse nodig, die we hier Drank gaan noemen. Het is vaak nuttig om, vooraleer we effectief Python code gaan schrijven, even kort samen te vatten wat wij juist van plan zijn, door de attributen en methodes van de klasse op te lijsten. We doen dit in de vorm van een info-kaartje, dat er zo uitziet: Klasse Drank Attr. naam : string alcoholpercentage : R prijs : R Meth. Op dit ogenblik, zijn we dus niet van plan om methodes te voorzien in onze Drank-objecten. Dit betekent dat we eigenlijk evengoed een woordenboek zouden kunnen gebruiken om deze gegevens in voor te stellen, als een object. Er zijn twee goede redenen om toch voor een object te kiezen. Ten eerste is het verwarrend om in hetzelfde programma sommige gegevens voor te stellen door een object en sommige door een woordenboek; in een objectgericht programma kiezen we dus best zoveel mogelijk voor objecten. Ten tweede zou het altijd nog kunnen dat we later alsnog methodes blijken nodig te hebben. Als we van in het begin voor objecten gekozen hebben, is dit een veel eenvoudigere operatie, dan wanneer we een woordenboek zouden moeten gaan omvormen tot een object. 14

15 Wegens het gebrek aan methodes, krijgen we dus een klasse definitie die leeg is. class Drank: pass Deze lege klasse dient vooral om ons nu alvast een plaats te geven waar we later als we het programma verder gaan uitbreiden eventuele methodes van de klasse Drank kunnen gaan toevoegen. Het feit dat de definitie van deze klasse leeg is, belet ons natuurlijk niet om objecten hiervan aan te maken. gin = Drank ( ) gin.naam = gin gin. alcoholpercentage = 0.15 gin. p r i j s = 12 vodka = Drank ( ) vodka.naam = vodka vodka. alcoholprecentage = 0.40 vodka. p r i j s = 20 We zien hier opnieuw een hoop tikwerk opduiken, met bovendien het risico op moeilijk te vinden fouten. Zo staat er in het voorbeeld hierboven een tikfout, die op dit moment nog ongemerkt voorbij zal gaan, maar ongetwijfeld later voor problemen gaat zal zorgen als we het alcoholpercentage van onze dranken willen raadplegen. Beter is het dus om een methode te definiëren die de verschillende attributen van een Drank invult. class Drank: def vulattributenin ( z e l f, naam, perc, p r i j s ) : z e l f.naam = naam z e l f. alcoholpercentage = perc z e l f. p r i j s = p r i j s gin = Drank ( ) gin. vulattributenin ( gin, 0.15, 12) vodka = Drank ( ) vodka. vulattributenin ( vodka, 0.40, 20) In dit voorbeeld zien we dat we, telkens als we een Drank aanmaken, we als eerste werk de initializatie-functie vulattributenin hierop gaan oproepen. Dit zal bovendien in heel ons programma waarschijnlijk altijd zo zijn. Python laat ons toe om ons programma nog wat compacter te maken door deze twee stappen het aanmaken van een object en het initializeren ervan in één instructie uit te voeren. Het enige dat we hiervoor moeten doen, is onze initializatie-methode een speciale naam geven: init. De naam van deze functie bestaat dus uit het woordje init (als afkorting van initializatie), voorafgegaan en gevolgd door telkens twee underscores _. De reden voor de underscores is dat Python deze 15

16 notatie gebruikt voor dingen die op één of andere manier speciaal zijn, in de zin dat Python zelf er achter de rug van de programmeur dingen mee zal doen. Deze notatie oogt een beetje vreemd, maar dat is eigenlijk precies de bedoeling: de underscores dienen als een waarschuwing voor mensen die de code zouden lezen zonder de speciale functie te kennen. Als ze de underscores zien, dan weten ze dat deze functie iets speciaals doet, en dat ze best eens de Python documentatie erop zouden naslaan om te weten te komen wat dit speciale juist is. Deze speciale functies worden ook wel magische functies genoemd, omdat ze een effect kunnen hebben op het gedrag van een programma in delen die er op het eerste zicht helemaal niets mee te maken hebben. class Drank: def i n i t ( z e l f, naam, perc, p r i j s ) : z e l f.naam = naam z e l f. alcoholpercentage = perc z e l f. p r i j s = p r i j s gin = Drank ( gin, 0.15, 12) vodka = Drank ( vodka, 0.40, 20) Als we in onze definitie van onze klasse een methode voorzien met de naam init, dan zal Python dus voor ons een functie definiëren met volgende eigenschappen: de naam van de functie is dezelfde als de naam van de klasse; het aantal argumenten van de functie is hetzelfde als het aantal argumenten van de init methode. Wat deze functie zal doen is: 1. Eerst maakt de functie een nieuw object aan van de klasse, en koppelt hieraan alle methodes die bij de klasse horen; 2. Daarna roept de functie de initializatie-methode init van deze klasse op op het object dat ze net heeft aangemaakt, met als argumenten de argumenten die ze zelf gekregen heeft; 3. Tot slot geeft deze functie het nieuw aangemaakte en geïnitializeerde object terug als haar resultaat. Deze functie wordt de constructor van de klasse genoemd. (Opmerking terzijde: Sommige objectgerichte programmeertalen bieden de mogelijkheid aan om per klasse meerdere constructoren te voorzien, die bijvoorbeeld objecten initializeren op basis van verschillende parameters. In Python is dit niet mogelijk, aangezien het gedrag van de constructor volledig bepaald wordt door hetgeen er in de init -methode staat, en er maar één methode met deze naam in elke klasse kan zijn. Wel is het mogelijk om sommige argumenten van deze methode een default waarde mee te geven, waarmee een deel van de functionaliteit waarvoor het hebben van meerdere constructors in andere programmeertalen gebruikt wordt, toch gerealizeerd kan worden.) 16

17 1.5 Magische methodes Naast init zijn er in Python nog een hele hoop andere speciale functies en methodes. Een greep uit het gamma. Python biedt een aantal functies aan die objecten van één datatype omzetten naar een ander datatype. Bijvoorbeeld: >>> int(5.4) 5 >>> float(3) 3.0 >>> str(4) 4 >>> int( 7 ) 7 >>> float( 7 ) 7.0 Al deze functies werken door achter de schermen een corresponderende magische methode op te roepen, die dezelfde naam heeft maar dan aangevuld met de nodige underscores. Door in onze eigen klassen deze methodes te implementeren, kunnen we dus bepalen hoe onze eigen objecten zullen worden omgezet naar andere datatypes: class Drank: def i n i t ( z e l f, naam, perc, p r i j s ) : z e l f.naam = naam z e l f. alcoholpercentage = perc z e l f. p r i j s = p r i j s def str ( z e l f ) : return z e l f.naam + ( + str ( z e l f. alcoholpercentage ) + %) def f l o a t ( z e l f ) : return z e l f. alcoholpercentage Laten we dit eens uitproberen: >>> d = Drank( pils, 0.4, 2) >>> str(d) pils (0.4%) >>> float(d) >>> int(d) Traceback (most recent call last): File "<stdin>", line 1, in? AttributeError: Drank instance has no attribute int In bovenstaande klasse is het waarschijnlijk niet nodig om een methode float te hebben. Er zijn immers weinig situaties te bedenken waarin we op het idee zouden komen om een drank als een 17

18 kommagetal te gebruiken. De methode str lijkt daarentegen wel zinvol. Telkens als we een drank zouden willen afprinten, moeten we deze immers transformeren naar een string. Sterker nog: als we een drank meegeven aan een print opdracht, dan zal Python achter de schermen deze conversie uitvoeren. Normaalgezien krijgen we iets als dit te zien, als we een Drank-object proberen af te printen: >>> print Drank( pils, 0.4, 2) < main.drank instance at 0x50e9b8> Nu we echter een str methode gedefiniëerd hebben in de klasse Drank, ziet het resultaat er anders uit: >>> print Drank( pils, 0.4, 2) pils (0.4%) Van de verschillende conversie-methodes die hierboven werden aangehaald, is str dan ook veruit de meest gebruikte. Tot slot nog een laatste beetje magie. In de interactieve Python shell, kan je het commando help gebruiken om meer informatie in te winnen over ingebouwde objecten, klassen, functies of methodes. Bijvoorbeeld: >>> help(str)... Return a nice string representation of the object. If the argument is a string, the return value is the same object.... Bij de definitie van een nieuwe klasse, kan je als eerste instructie een string opgeven, en deze zal dan afgebeeld worden als gebruikers om hulp vragen over deze klasse. Deze string wordt per conventie tussen driedubbele aanhalingstekens geplaatst, ook als hij op één lijn past, en wordt de docstring van de klasse genoemd. class WatDoetDit : """ Een klasse die het gebruik van een docstring illustreert. """ pass >>> help(watdoetdit) Help on class WatDoetDit in module main : class WatDoetDit Een klasse die het gebruik van een docstring illustreert. 1.6 Wijzigen van attributen De attributen van een object komen altijd tot stand tijdens zijn initializatie. Het is natuurlijk mogelijk om achteraf de waarde van deze 18

19 attributen nog te gaan wijzigen. Als we bijvoorbeeld plots 2,5 e korting krijgen op gin, dan kan dit als volgt verwerkt worden: class Drank: def i n i t ( z e l f, naam, perc, p r i j s ) : z e l f.naam = naam z e l f. alcoholpercentage = perc z e l f. p r i j s = p r i j s... # Nog wat andere methodes gin = Drank ( gin, 0.35, 10) gin. p r i j s = gin. p r i j s 2.5 Hierbij wordt de waarde van attribuut prijs van het object gin dus aangepast van buiten deze klasse. Een alternatief is dat we de klasse Drank een extra methode geven, waarmee we deze aanpassing binnen de klasse doen. class Drank: def i n i t ( z e l f, naam, perc, p r i j s ) : z e l f.naam = naam z e l f. alcoholpercentage = perc z e l f. p r i j s = p r i j s... # Nog wat andere methodes def krijgkorting ( z e l f, bedrag ) : z e l f. p r i j s = z e l f. p r i j s bedrag gin = Drank ( gin, 0.35, 10) gin. krijgkorting ( 2. 5 ) Aangezien het de bedoeling van objectgericht programmeren is dat klassen zoveel mogelijk hun eigen gegevens inkapselen, is de tweede optie vaak de beste. 1.7 Voorbeelden Tot slot van dit hoofdstuk, nog een aantal voorbeelden van volledige klassen De klasse Drank Het info-kaartje van onze klasse Drank is intussen dit geworden: Klasse Drank Attr. naam : string alcoholpercentage : R prijs : R Meth. init (zelf, naam, perc, prijs) str (zelf) 19

20 En de bijhorende Python code is dan: class Drank: """ Objecten van deze klasse stellen een drank voor met: """ - een naam, - een alcoholpercentage, - een prijs (in euro per liter). def i n i t ( z e l f, naam, perc, p r i j s ) : z e l f.naam = naam z e l f. alcoholpercentage = perc z e l f. p r i j s = p r i j s def str ( z e l f ) : return z e l f.naam + ( + str ( z e l f. alcoholpercentage ) + %) De klasse Rechthoek Volgende klasse laat ons toe om rechthoeken voor te stellen, af te printen, en hun oppervlakte en omtrek te berekenen. Klasse Rechthoek Attr. hoogte : R breedte : R Meth. oppervlakte(zelf) : R omtrek(zelf) : R class Rechthoek : """ Objecten van deze klasse stellen een meetkundige rechthoek voor met een hoogte en breedte. """ def i n i t ( z e l f, b, h) : z e l f. breedte = b z e l f. hoogte = h def str ( z e l f ) : return "Rechthoek van " + str ( z e l f. breedte ) + "x" + str ( z e l f. hoogte ) def oppervlakte ( z e l f ) : return z e l f. breedte * z e l f. hoogte def omtrek ( z e l f ) : return 2 * ( z e l f. breedte + z e l f. hoogte ) 20

21 Deze klasse gebruiken we dan bijvoorbeeld zo: >>> r = Rechthoek(2,3) >>> print r Rechthoek van 2x3 >>> r.oppervlakte() 6 >>> r.omtrek() De klasse Cirkel Andere meetkundige vormen kunnen natuurlijk op een gelijkaardige manier worden voorgesteld. Voor een cirkel hebben we het getal π nodig, dat we kunnen aanspreken als math.pi, nadat we eerst deze module math geïmporteerd hebben. Klasse Cirkel Attr. straal : R Meth. oppervlakte(zelf) : R omtrek(zelf) : R class Cirkel : def i n i t ( z e l f, straal ) : z e l f. straal = straal def str ( z e l f ) : return "Cirkel met straal " + str ( z e l f. straal ) def omtrek ( z e l f ) : import math return z e l f. straal * 2 * math. pi def oppervlakte ( z e l f ) : import math return ( z e l f. straal ** 2) * math. pi 1.8* Een blik achter de schermen Veel programmeertalen hebben de filosofie dat ze programmeurs tegen zichzelf of tegen hun collega s moeten beschermen. Python heeft deze filosofie niet. Dit is natuurlijk slecht nieuws voor programmeurs die deze bescherming nodig hebben, maar goed nieuws voor de anderen. We hebben eerder gezien dat er een grote gelijkenis bestaat tussen attributen van een object en sleutel-waarde paren in een woordenboek. Dit hoeft geen verwondering te wekken, want achter de schermen worden de attributen van een object gewoon in een woordenboek gestoken. Dit magische woordenboek heeft de naam dict en is zelf een attribuut van het object. 21

22 >>> d = Drank( pils, 0.04, 2) >>> d. dict { naam : pils, alcoholpercentage : 0.04, prijs : 2} Elk object behoort, zoals je weet, tot een bepaalde klasse. Deze klasse wordt bijgehouden in het attribuut class. >>> d. class <class main.drank at 0x505b40> Op basis van de inleiding van dit hoofdstuk, had je misschien verwacht dat de methodes van de klasse Drank bij in het woordenboek d. dict zouden zitten. Dit is echter niet het geval. De reden hiervoor is gewoon zuinigheid: aangezien alle objecten van de klasse toch dezelfde methodes delen, hoeven ze die niet allemaal afzonderlijk bij te houden. Het is voldoende als gewoon de klasse d. class de methodes bijhoudt. Dit doet ze in haar eigen dict, waar we o.a. ook de docstring van de klasse terugvinden. >>> d. class. dict { module : main, doc : Objecten van deze klasse stellen een drank voor met:\n\n - een naam,\n - een alcoholpercentage, \n - een prijs (in euro per liter). \n, str : <function str at 0x50a230>, init : <function init at 0x50a630>} 22

23 Samenwerkende objecten 2 In het vorige hoofdstuk hebben we enkel maar naar geïsoleerde klassen en objecten gekeken. In een echt programma zullen verschillende klassen normaalgezien moeten samenwerken. Er zijn drie manieren waarop één klasse een andere kan gebruiken: Een klasse kan een methode hebben, waarin een object van een andere klasse als argument voorkomt; Een klasse kan een methode hebben, die een object van een andere klasse teruggeeft; Een klasse kan een attribuut hebben waarin ze een object van een andere klasse bijhoudt. Er is duidelijk verschil tussen de eerste twee mogelijkheden en de derde, namelijk de duurtijd van de samenwerking. In de eerste twee gevallen is dit een tijdelijke samenwerking, waarbij de ene klasse de andere enkel maar nodig heeft tijdens één enkel methode oproep. Het laatste geval, daarentegen, beschrijft een duurzame binding tussen twee objecten, die mogelijk hun hele levensduur lang meegaat. Dit fenomeen wordt ook wel een associatie tussen de twee klassen genoemd. Associatie = contract van onbepaalde duur 2.1 Een object als argument In Secties en introduceerden we een klasse Cirkel en een klasse Rechthoek. Laat ons nu de klasse Cirkel uitbreiden met een methode die kan nagaan of de cirkel in een gegeven Rechthoek past. class Cirkel : #... de klasse zoals voorheen def pastin ( z e l f, rechthoek ) : return ( z e l f. straal <= rechthoek. hoogte and z e l f. straal <= rechthoek. breedte ) Een voorbeeldje van het gebruik van deze methode: 23

24 >>> cirkel = Cirkel(5) >>> rh = Rechthoek(6,7) >>> cirkel.pastin(rh) True Een object dat als argument wordt meegegeven, gedraagt zich zoals een woordenboek of een lijst, in die zin dat als er in de methode wijzigingen gebeuren aan een attribuut van dit object, deze wijzigingen ook buiten de methode zichtbaar zullen zijn. Laat ons dit illustreren met een methode die een rechthoek inkrimpt totdat hij in een cirkel past. class Cirkel : #... de klasse zoals voorheen def maakingesloten ( z e l f, rechthoek ) : i f rechthoek. hoogte > z e l f. straal : rechthoek. hoogte = z e l f. straal i f rechthoek. breedte > z e l f. straal : rechthoek. breedte = z e l f. straal >>> cirkel = Cirkel(5) >>> rh = Rechthoek(3,7) >>> print rh Rechthoek van 3x7 >>> cirkel.maakingesloten(rh) >>> print rh Rechthoek van 3x5 2.2 Een object als resultaat Als we in bovenstaand voorbeeld nog andere plannen hebben met onze originele rechthoek rh, dan kan het lastig zijn dat we deze nu net in place veranderd hebben. Een alternatief is om in onze methode een nieuwe Rechthoek aan te maken, en deze terug te geven als resultaat. De oorspronkelijke rechthoek kan dan onveranderd blijven. Vergelijk onderstaande code met de versie uit de vorige sectie: class Cirkel : #... de klasse zoals voorheen def maakingesloten ( z e l f, rechthoek ) : i f rechthoek. breedte > z e l f. straal : nieuwebreedte = z e l f. straal else : nieuwebreedte = rechthoek. breedte i f rechthoek. hoogte > z e l f. straal : nieuwehoogte = z e l f. straal else : nieuwehoogte = rechthoek. hoogte return Rechthoek ( nieuwebreedte, nieuwehoogte ) 24

25 Het gebruik van deze methode moet dan natuurlijk ook anders: >>> cirkel = Cirkel(5) >>> rh = Rechthoek(3,7) >>> rh2 = cirkel.maakingesloten(rh) >>> print rh Rechthoek van 3x7 >>> print rh2 Rechthoek van 3x5 Welk van beide stijlen te verkiezen valt, is vaak een kwestie van persoonlijke smaak. In de Python gemeenschap, is men vaak nogal gewonnen voor een functionele stijl van programmeren, waarin functies en methodes zich gedragen zoals wiskundige functies. Dit betekent dat, net zoals een wiskundige functie f, een methode wel een resultaat y = f(x) zal berekenen, maar geen veranderingen zal aanbrengen aan x. Aanhangers van deze programmeerstijl zouden dus waarschijnlijk onze tweede variant van de methode maakingesloten verkiezen. De achterliggende motivatie is dezelfde als altijd: ze geloven dat er op deze manier gemakkelijker onafhankelijkheden tussen verschillende stukken code gerealiseerd kunnen worden, wat uiteindelijk aanleiding zou moeten geven tot programma s die gemakkelijker te ontwikkelen en te onderhouden zijn. 2.3 Associaties tussen klassen Een associatie tussen twee klassen betekent dat elk object van de ene klasse een attribuut heeft waarin een object van de andere klasse wordt bijgehouden. Om dit te illustreren verlaten we even onze rechthoeken en cirkels voor een belangrijkere toepassing, namelijk het bijhouden van onze drankvoorraad. We zagen in het vorige hoofdstuk (Sectie 1.7.1) al een klasse Drank, waarmee we kunnen bijhouden welke dranken we in voorraad hebben. Dit breiden we nu zodanig uit, dat we ook kunnen bijhouden hoeveel er van een bepaalde drank in voorraad is. Hiervoor introduceren we volgende klasse: Klasse Fles Attr. drank : Drank inhoud (in cl) : N Meth. haaluit(zelf, hoeveelheid) voegtoe(zelf, hoeveelheid) waarde(zelf) : R Elk object van de klasse Fles heeft dus een attribuut waarin het een object van de klasse Drank gaat bijhouden. Er is dus, maw., een associatie tussen Fles en Drank. Vaak is het inzichtelijker om in plaats van bovenstaand info-kaartje een zogenaamd klassendiagramma te tekenen. Dit is één van verschillende Hierop worden associates aanduid met een pijl tussen de twee klassen in kwestie. Deze pijl vertrekt bij de klasse die het attribuut heeft waarmee deze associatie wordt voorgesteld, en dit attribuut wordt dan niet meer opgenomen in diens lijst met attributen. Eventueel kan de 25

26 naam van het attribuut wel nog vermeld worden als label bij de pijl. Om het geheel overzichtelijk te houden, wordt er vaak ook voor gekozen om de methodes of zelfs de attributen van een klasse niet te vermelden. Fles inhoud : N drank Drank naam alcoholpercentage prijs : string : R : R class Fles : def i n i t ( z e l f, drank, inhoud ) : z e l f. drank = drank z e l f. inhoud = inhoud def haaluit ( z e l f, hoeveelheid ) : z e l f. inhoud = z e l f. inhoud hoeveelheid def voegtoe ( z e l f, hoeveelheid ) : z e l f. inhoud = z e l f. inhoud + hoeveelheid def waarde ( z e l f ) : prijspercl = z e l f. drank. p r i j s / return z e l f. inhoud * prijspercl De interessantste methode is hier de laatste, waarin een Fles object zijn eigen inhoud moet combineren met de prijs van zijn drank om zijn eigen waarde te bepalen. De deling door 100 is nodig omdat de klasse Drank zijn prijs bijhoudt in e/l, terwijl de inhoud van een Fles in cl wordt bijgehouden. Op zich kan dit riskant zijn (denk aan de ontplofte Mars Observator): als we later zouden besluiten om de prijs van een Drank ook in e/cl te zetten, moeten we eraan denken om de deling door 100 weg te halen, of anders krijgen we natuurlijk foute resultaten. Het is in dit geval waarschijnlijk veiliger om voor een andere strategie te kiezen, waarbij het doen van berekeningen met het attribuut drank.prijs zoveel mogelijk in de klasse Drank gebeurt. class Fles :... def waarde ( z e l f ) : return z e l f. drank. prijspercl ( ) * z e l f. inhoud class Drank:... def prijspercl ( z e l f ) : return z e l f. p r i j s /

27 2.4 Lijsten en objecten Ook in onze eigen objecten kunnen we vaak op nuttige wijze gebruik maken van de functionaliteit van Pythons ingebouwde lijsten. In de cursus van het eerste jaar, heb je gezien hoe je met deze lijsten moet omgaan. Een kort voorbeeldje ter herinnering: >>> lijst = [1,2,3] >>> print lijst [1, 2, 3] >>> lijst.append(4) >>> print lijst [1, 2, 3, 4] >>> for element in lijst:... print element >>> print lijst[1] 2 >>> for i in range(len(lijst)):... print i, "->", lijst[i] > 1 1 -> 2 2 -> 3 3 -> 4 Herinner je bij de uitvoer van het laatste commando ook dat de index van een lijst altijd begint te tellen vanaf 0; het element dat bij index 1 hoort, is dus niet het eerste, maar wel het tweede element van de lijst Objecten met lijsten Een lijst kan net zoals bijvoorbeeld een getal, een string of een object gebruikt worden als een attribuut van een object. Als een object zo n attribuut heeft, zal het vaak methodes aanbieden waarmee elementen kunnen worden toegevoegd aan en/of weggehaald uit de lijst. Als de constructor van zo n object al geen lijst meekrijgt als argument, dan zal hier typisch een nieuwe, lege lijst worden aangemaakt. Het volgende voorbeeld toont een klasse waarmee een rij van getallen kan worden bijgehouden om hiervan het gemiddelde te berekenen. class GetallenRij : def i n i t ( z e l f ) : z e l f. r i j = [ ] def voegtoe ( z e l f, getal ) : z e l f. r i j. append ( getal ) 27

28 def str ( z e l f ) : return str ( z e l f. r i j ) def som( z e l f ) : som = 0 for getal in z e l f. r i j : som += getal return som def gemiddelde ( z e l f ) : som = z e l f.som ( ) return float (som) / len ( z e l f. r i j ) # ^^^^^ anders krijgen we een gehele deling Deze klasse kunnen we dan bijvoorbeeld als volgt gebruiken. >>> rij = GetallenRij() >>> rij.voegtoe(3) >>> rij.voegtoe(6) >>> rij.voegtoe(7) >>> print rij [3, 6, 7] >>> rij.gemiddelde() Lijsten van objecten Naast waardes van een primitief type, zoals getallen of strings, kunnen ook objecten in een lijst gestoken worden. Zo zal onderstaande code een lijst van twee objecten maken, en daar dan nadien nog een geheel getal en een derde object aan toevoegen. gin = Drank (... ) p i l s = Drank (... ) wijn = Drank (... ) f1 = Fles ( gin,50) f2 = Fles ( pils,25) f3 = Fles ( wijn,75) l i j s t = [ f1, f2 ] l i j s t. append ( 4 ) l i j s t. append ( f3 ) De lijst die door deze code geproduceerd wordt, ziet er als volgt uit: >>> print lijst [< main.fles instance at 0x50d580>, < main.fles instance at 0x50d5a8>, 4, < main.fles instance at 0x50d5d0>] Een grafische voorstelling hiervan is te zien in Figuur 2.1. Het belangrijkste punt hiervan, is dat lijst[0] dus eigenlijk gewoon een andere naam is voor hetzelfde object dat ook al de naam f1 heeft. Het effect hiervan zien we bijvoorbeeld in volgende interactie: 28

29 lijst 4 Fles drank gin inhoud 50 Fles drank pils inhoud 25 Fles drank wijn inhoud 75 f1 f2 f3 Figuur 2.1: Een grafische voorstelling van het resultaat van de code lijst = [f1,f2,4,f3]. >>> print f1.inhoud 50 >>> lijst[0].inhoud = 25 >>> print f1.inhoud Objecten met lijsten van objecten Als we objecten in een lijst kunnen steken, is het natuurlijk ook mogelijk om zo n lijst van objecten te gebruiken als een attribuut van een andere object. Op deze manier kunnen we, met andere woorden, een associatie tot stand brengen van een object van één klasse met een verzameling van objecten van een andere klasse. Hiervan kunnen we bijvoorbeeld gebruik maken om gegevens over een DrankVoorraad bij te houden, door middel van een lijst van flessen waaruit deze voorraad bestaat. Klasse DrankVoorraad Attr. flessen : lijst<fles> Meth. waarde(zelf) : R Met de notatie lijst<fles> bedoelen we een lijst met daarin een aantal Fles objecten. In een klassendiagramma, kunnen we zo n lijst aanduiden met een sterretje, zoals te zien in Figuur 2.2. Veel van het gedrag dat een object van een klasse zoals DrankVoorraad zal aanbieden, wordt typisch gerealizeerd door middel van een iteratie over de Fles-objecten die in zijn lijst zitten. Bijvoorbeeld de methode om de volledige waarde van een drankkast te berekenen, gegeven de waarde van de individuele flessen, valt op deze manier te implementeren. class DrankVoorraad : 29

30 DrankVoorraad waarde(zelf) : R flessen Fles inhoud : N waarde(zelf) : R voegtoe(zelf, hoeveelheid) haaluit(zelf, hoeveelheid) drank Drank naam alcoholpercentage prijs : string : R : R Figuur 2.2: Een klassendiagramma waarbij een drankvoorraad uit een verzameling flessen bestaat. def i n i t ( z e l f ) : z e l f. flessen = [ ] def voegtoe ( z e l f, f l e s ) : z e l f. flessen. append ( f l e s ) def waarde ( z e l f ) : resultaat = 0 for f in z e l f. flessen : resultaat += f. waarde ( ) return resultaat Laten we even stilstaan bij het resultaat dat deze methode berekent. Als de drankvoorraad n flessen {f 1,..., f n } bevat, waarbij V i de inhoud is van fles i en p i de prijs van de drank d i die in fles f i zit, dan berekent deze methode volgende som w: w = V i p i. 1 i n De methode waarde uit de klasse DrankVoorraad berekent heel deze som, gebruikmakend van de gelijknamige methode uit de klasse Fles, die één term ervan berekent. Sommige getalletjes leggen dus een hele weg af, voordat ze uiteindelijk in deze som belanden: Drank Fles DrankVoorraad p 1 d 1 f 1 V 1 p 1 i V i p i d n p n f n V n p n 30

31 GetallenRij rij som(zelf) gemiddelde(zelf) : lijst<z> : Z : R Veelhoek omtrek(zelf) : R voegtoe(zelf,punt) hoekpunten Punt x y afstandnaar(zelf,punt) : R : R : R Figuur 2.3: Klassendiagramma van het voorbeeld in Sectie Voorbeelden Punten en veelhoeken In dit voorbeeld gebruiken we drie klassen: De klasse Punt stelt een punt in het Euclidisch vlak voor. De klasse Veelhoek stelt veelhoek voor als een lijst van hoekpunten. Bij het berekenen van de omtrek van een Veelhoek, maken we gebruik van de klasse GetallenRij, die we eerder in dit hoofdstuk (Sectie 2.4.1) gebruikt hebben. Het bijhorende klassendiagramma is te zien in Figuur 2.3. Er is dus een associatie tussen een Veelhoek en een lijst van Punten. De klasse GetallenRij wordt enkel maar binnenin één bepaalde methode gebruikt, dus hiermee is er geen associatie. Voor de volledigheid herhalen we eerst de definitie van de klasse GetallenRij nog eens. class GetallenRij : def i n i t ( z e l f ) : z e l f. r i j = [ ] def voegtoe ( z e l f, getal ) : z e l f. r i j. append ( getal ) def str ( z e l f ) : return str ( z e l f. r i j ) def som( z e l f ) : som = 0 for getal in z e l f. r i j : 31

32 som += getal return som def gemiddelde ( z e l f ) : som = z e l f.som ( ) return float (som) / len ( z e l f. r i j ) # ^^^^^ anders krijgen we een gehele deling De klasse Punt bevat niet veel meer dan een Euclidische afstandsberekening. class Punt : def i n i t ( z e l f, x, y ) : z e l f. x = x z e l f. y = y def afstandnaar ( z e l f, ander ) : import math # om vierkantswortels te nemen return math. sqrt ( ( z e l f. x ander. x ) ** 2 + ( z e l f. y ander. y ) ** 2) def str ( z e l f ) : return "Punt(" + str ( z e l f. x ) + ", " + str ( z e l f. y ) + ")" De klasse Veelhoek is nu als volgt. class Veelhoek : def i n i t ( z e l f, punten ) : z e l f. hoekpunten = punten def voegtoe ( z e l f, punt ) : z e l f. hoekpunten. append ( punt ) def lijstvanzijdes ( z e l f ) : r i j = GetallenRij ( ) for i in range ( len ( z e l f. hoekpunten ) ) : van = z e l f. hoekpunten [ i ] i f i +1 < len ( z e l f. hoekpunten ) : naar = z e l f. hoekpunten [ i +1] else : naar = z e l f. hoekpunten [ 0 ] r i j. voegtoe ( van. afstandnaar ( naar ) ) return r i j def omtrek ( z e l f ) : return z e l f. lijstvanzijdes ( ).som ( ) Hiermee kunnen we nu als volgt de omtrekt berekenen van de veelhoek in Figuur 2.4. a = Punt (2,2) 32

33 1 b 1 c 2 a 2 d Figuur 2.4: Een veelhoek in het Euclidisch vlak. b = Punt (2,3) c = Punt (3,3) d = Punt (4,2) veelhoek = Veelhoek ( [ a, b, c, d ] ) print veelhoek. omtrek ( ) Cocktails en dranken We herschrijven nu de klasse Cocktail om gebruik te maken van onze klasse Drank, op de manier getoond in onderstaand klassendiagramma. Cocktail naam : String ingrediënten Ingredient percentage * Drank drank naam : R alcoholpercentage prijs : string : R : R De klasse Drank is nog steeds dezelfde als in Sectie De klasse Ingredient en Cocktail zijn nu als volgt: class Ingredient : """ Een klasse om de ingredienten van een Cocktail voor te stellen Elk ingredient bestaat uit: - een Drank - het percentage van de cocktail dat uit deze drank bestaat 33

34 """ def i n i t ( z e l f, drank, percentage ) : z e l f. drank = drank z e l f. percentage = percentage class Cocktail : """ Een cocktail met een: - naam - een lijst van Ingredient objecten, die de samenstelling aangeven """ def i n i t ( z e l f, naam, ingredienten ) : z e l f.naam = naam z e l f. ingredienten = ingredienten def ingredientpercocktail ( z e l f, drank, hoeveelheid ) : for ingr in z e l f. ingredienten : i f ingr. drank == drank : return hoeveelheid * ingr. percentage def ingredientperingredient ( z e l f, hoeveelheid, gegeven, gezocht ) : for ingr in z e l f. ingredienten : i f ingr. drank == gegeven : gegeveningr = ingr i f ingr. drank == gezocht : gezochtingr = ingr verhouding = gezochtingr. percentage / gegeveningr. percentage return hoeveelheid * verhouding def alcoholpercentage ( z e l f ) : r i j = GetallenRij ( ) for ingr in z e l f. ingredienten : r i j. voegtoe ( ingr. percentage * ingr. drank. alcoholpercentage ) return r i j.som ( ) def isstrafferdan ( z e l f, andere ) : return z e l f. alcoholpercentage ( ) > andere. alcoholpercentage ( ) def str ( z e l f ) : return z e l f.naam Gebruik maken van deze klassen kan dan bijvoorbeeld als volgt: gin = Drank ( "gin", 45, 12) tonic = Drank ( "tonic", 0, 7) 34

35 gintonic = Cocktail ( "Gin-tonic", [ Ingredient ( gin, 0.34), Ingredient ( tonic,0.66) ] ) print "Voor 10cl gin heb je " + str ( gintonic. ingredientperingredient (10, gin, tonic ) ) + "cl tonic nodig" tomatensap = Drank ( "tomatensap", 0, 2) vodka = Drank ( "vodka", 0. 4, 10) bloodymary = Cocktail ( "Bloody Mary", [ Ingredient ( tomatensap, 0.75), Ingredient ( vodka,0.25) ] ) i f bloodymary. isstrafferdan ( gintonic ) : s t r a f s t e = bloodymary else : s t r a f s t e = gintonic print "Doe mij maar een " + str ( s t r a f s t e ) 2.6 Nog wat terminologie In dit hoofdstuk zijn we verschillende soorten van verbanden tegengekomen die objecten van verschillende klassen met elkaar kunnen hebben. Om hierover vlot te kunnen communiceren, bestaat er wat specifieke terminologie. We zijn de term associatie al tegengekomen. Dit is de meest algemene term: een associatie tussen twee klassen betekent simpelweg dat objecten van de ene klasse op de een of andere manier gebruik maken van objecten van de andere klassen. Zoals we ook reeds gezien hebben, wordt een associatie in een klassendiagramma getekend met een pijl. Een aggregatie is een speciaal geval van een associatie, waarbij er een deel-geheel relatie bestaat tussen de twee klassen. We hebben ook hiervan al voorbeelden gezien, zoals bijvoorbeeld de associatie tussen Veelhoek (geheel) en Punt (deel), aangezien een veelhoek bestaat uit een aantal hoekpunten. Ook de associatie tussen DrankVoorraad en Fles is een voorbeeld van een aggregatie. In een klassendiagramma wordt dit getekend door een pijl die begint met een diamand:. Het voorbeeld van veelhoeken en punten kunnen we dus eigenlijk beter tekenen zoals in Figuur 2.5. Een compositie is een speciaal geval van een aggregatie, waarbij de delen worden aangemaakt door het geheel, en de delen niet kunnen bestaan zonder het geheel. In een klassendiagramma wordt dit getekend door een pijl die begint met een volle diamand:. 35

36 Veelhoek omtrek(zelf) : R voegtoe(zelf,punt) hoekpunten Punt x y afstandnaar(zelf,punt) : R : R : R Figuur 2.5: Alternatieve versie van het klassendiagram in Figuur

37 Overerving 3 Een belangrijk streefdoel van objectgericht programmeren is herbruikbaarheid van programma-code. In het vorig hoofdstuk hebben we bijvoorbeeld een klasse Veelhoek gedefinieerd. Moesten we nu tien jaar later een programma aan het schrijven zijn waarin we opeens nood blijken te hebben aan een klasse waarmee we een veelhoek kunnen voorstellen, dan zouden we graag hebben dat we gewoon deze klasse uit het vorige hoofdstuk terug kunnen opzoeken en deze zonder verdere moeite herbruiken in ons nieuwe programma. In de praktijk blijkt dit echter vaak toch niet zo eenvoudig te zijn. Een veel voorkomend fenomeen is dat er in het nieuwe programma net iets meer functionaliteit vereist is dan in het oude. Zo zou het bijvoorbeeld kunnen zijn dat we in het nieuwe programma opeens ook de oppervlakte van een veelhoek moeten kunnen berekenen, terwijl dit in het oude nog niet zo was. We zouden dan natuurlijk de oude klasse kunnen gaan aanpassen, maar dan lopen we het risico dat we uiteindelijk een moeilijk te beheren warboel van allemaal verschillende versies van dezelfde klasse zullen overhouden. Om dit te voorkomen is er nood een mechanisme waarmee we uitbreidingen van een bestaande klasse kunnen maken, zonder deze klasse zelf echter te moeten veranderen. Dit gebeurt dmv. het mechanisme van overerving. We introduceren eerst wat terminologie. De bestaande klasse waarvan ze vertrekken wordt de superklasse genoemd. Uit deze superklasse gaan we dan een nieuwe klasse afleiden, die de subklasse genoemd wordt. Objecten van deze subklasse zullen alles kunnen wat objecten van de superklasse ook kunnen, maar daarnaast ook nog iets méér. Op deze manier zouden we dus zelfs kunnen zeggen dat elk object dat tot de subklasse behoort eigenlijk óók tot de superklasse behoort. Met andere woorden, we kunnen de subklasse zien als een deelverzameling van de superklasse. Hierin ligt ook meteen de oorsprong van deze twee termen. Dit wordt nog eens grafisch geïllustreerd in Figuur 3.1. De notatie voor overerving in Python is heel eenvoudig: het volstaat om bij de declaratie van de subklasse de naam van de superklasse tussen haakjes te zetten. Onderstaand code-fragmentje definieert bijvoorbeeld een subklasse CocktailMetGarnituur van de klasse Cocktail uit Sectie class CocktailMetGarnituur ( Cocktail ) : pass Overerving dient om bestaande klassen uit te breiden Subklasse = superklasse + extra s 37

38 superklasse subklasse Figuur 3.1: Een superklasse (zoals Cocktail) in rode streepjeslijn ( ) met een subklasse (zoals CocktailMetGarnituur) in blauwe puntjeslijn ( ). Het effect hiervan is dat we nu objectjes van de klasse CocktailMetGarnituur kunnen aanmaken, die zich volledig zullen gedragen zoals onze Cocktail objectjes. We kunnen dus, bijvoorbeeld, het volgende doen: tomatensap = Drank ( "tomatensap", 0, 2) vodka = Drank ( "vodka", 0. 4, 10) bloodymary = CocktailMetGarnituur ( "Bloody Mary", [ Ingredient ( tomatensap, 0.75), Ingredient ( vodka,0.25) ] ) print bloodymary. alcoholpercentage ( ) Het object bloodymary dat we hier hebben aangemaakt is een object van de klasse CocktailMetGarnituur. In termen van Figuur 3.1, is het dus één van de objectjes in de blauwe verzameling. Dit komt enkel en alleen door het feit dat we de functie met deze naam gebruikt hebben bij het aanmaken van dit object. Met andere woorden, de klasse waartoe een object behoort, ligt al vast van bij het aanmaken van dit object. Tijdens de levensloop van het object, zal dit ook niet meer veranderen. Zoals we in bovenstaand fragmentje kunnen zien, erft elk objectje van een subklasse dus al de methodes van zijn superklasse. Dit verklaart waarom we op ons object bloodymary toch de methode alcoholpercentage kunnen oproepen, ook al komt deze niet voor in onze definitie van de klasse CocktailMetGarnituur. Overerving wordt natuurlijk pas interessant van zodra we aan de subklasse wat bijkomende functionaliteit gaan toevoegen, die in de superklasse nog niet aanwezig was. class CocktailMetGarnituur ( Cocktail ) : def voeggarnituurtoe ( z e l f, gar ) : z e l f. garnituur = gar 38

39 Cocktail CocktailMetGarnituur Figuur 3.2: Overerving in een klassendiagramma. Nu hebben objecten van de klasse CocktailMetGarnituur dus zowel alle methodes uit de oorspronkelijke klasse Cocktail, als deze nieuwe methode voeggarnituurtoe. bloodymary = CocktailMetGarnituur ( "Bloody Mary", [ Ingredient ( tomatensap, 0.75), Ingredient ( vodka,0.25) ] ) bloodymary. voeggarnituurtoe ( "selder" ) In een klassendiagramma tekenen we de overervingsrelatie met behulp van een driehoek, zoals getoond in Figuur Het overschrijven van methodes Dankzij deze extra methode in de klasse CocktailMetGarnituur kunnen we nu al een takje selder toevoegen aan onze bloody mary. Er gebeurt natuurlijk nog niets met deze bijkomende informatie. We zouden bijvoorbeeld onze garnituur ook kunnen willen opnemen in de string-voorstelling van de cocktail. Hiervoor moeten we dan, zoals steeds, een str methode schrijven. Merk eerst op dat onze CocktailMetGarnituur objecten natuurlijk al zo n methode hebben, namelijk, diegene die ze overerven van hun superklasse Cocktail. Om nog even het globale plaatje samen te vatten: class Cocktail :... def str ( z e l f ) : return z e l f.naam class CocktailMetGarnituur ( Cocktail ) : def voeggarnituurtoe ( z e l f, gar ) : z e l f. garnituur = gar En dit laat ons toe om het volgende te doen: bloodymary = CocktailMetGarnituur ( "Bloody Mary", [ Ingredient ( tomatensap, 0.75), Ingredient ( vodka,0.25) ] ) bloodymary. voeggarnituurtoe ( "selder" ) 39

40 En als we dan de string-voorstelling van dit object opvragen, wordt dankzij de overerving het resultaat berekend door de methode str uit de klasse Cocktail. >>> print bloodymary Bloody Mary Nu willen we ervoor zorgen dat objecten van de klasse CocktailMetGarnituur een andere string-voorstelling krijgen dan objecten van de klasse Cocktail. Dit doen we door deze klasse zijn eigen str methode te geven. class Cocktail :... def str ( z e l f ) : return z e l f.naam class CocktailMetGarnituur ( Cocktail ) : def voeggarnituurtoe ( z e l f, gar ) : z e l f. garnituur = gar def str ( z e l f ) : return z e l f.naam + " met " + z e l f. garnituur De meest specifieke methode wordt opgeroepen Als we nu proberen om op onze Bloody Mary de methode str op te roepen (door ofwel bloodymary. str (), ofwel str(bloodymary), ofwel print bloodymary te doen), dan zijn er dus eigenlijk twee verschillende methodes die in aanmerking komen. Er is de methode met deze naam die wordt overgeërfd uit de klasse Cocktail en er is de methode met deze naam die in de klasse CocktailMetGarnituur zelf gedefiniëerd wordt. Wat Python in zo n geval zal doen en het is trouwens eenvoudig in te zien dat dit ook de enige zinvolle optie is is de meest specifieke methode kiezen die van toepassing is, in dit geval dus de methode uit CocktailMetGarnituur. >>> print bloodymary Bloody Mary met selder In dit geval zeggen we dat de methode uit de subklasse de gelijknamige methode uit de superklasse overschrijft; in het Engels spreken we van overriding. Ook constructoren kunnen op deze manier natuurlijk overschreven worden. In bovenstaand voorbeeld hadden we het object bloodymary als volgt aangemaakt: bloodymary = CocktailMetGarnituur ( "Bloody Mary", [ Ingredient ( tomatensap, 0.75), Ingredient ( vodka,0.25) ] ) Wat deze code doet, is natuurlijk de overgeërfde methode init uit de superklasse Cocktail oproepen: 40

41 class Cocktail :... def i n i t ( z e l f, naam, ingredienten ) : z e l f.naam = naam z e l f. ingredienten = ingredienten Moesten we nu graag de klasse CocktailMetGarnituur zijn eigen, specifieke constructor geven, zodat de garnituur al meteen als argument hiermee kan worden meegegeven, dan kan dit zo: class CocktailMetGarnituur ( Cocktail ) :... def i n i t ( z e l f, naam, ingredienten, garnituur ) : z e l f.naam = naam z e l f. ingredienten = ingredienten z e l f. garnituur = garnituur Ook kunnen natuurlijk niet-magische methodes overschreven worden. Veronderstel bijvoorbeeld dat de aanwezigheid van de garnituur ervoor zorgt dat het alcoholpercentage van de cocktail een procentje lager ligt dan normaal. class Cocktail :... def alcoholpercentage ( z e l f ) : r i j = GetallenRij ( ) for ingr in z e l f. ingredienten : r i j. voegtoe ( ingr. percentage * ingr. drank. alcoholpercentage ) return r i j.som ( ) def isstrafferdan ( z e l f, andere ) : return z e l f. alcoholpercentage ( ) > andere. alcoholpercentage ( ) class CocktailMetGarnituur ( Cocktail ) :... def alcoholpercentage ( z e l f ) : r i j = GetallenRij ( ) for ingr in z e l f. ingredienten : r i j. voegtoe ( ingr. percentage * ingr. drank. alcoholpercentage ) return r i j.som ( ) 0.01 Om het globale effect hiervan te zien, is het de moeite waard om even stil te staan bij wat er juist gebeurt in volgend code-fragmentje: 41

42 bloodymary = CocktailMetGarnituur(...) whiskycola = Cocktail(...) bloodymary.isstrafferdan(whiskycola) De methode isstrafferdan is niet overschreven geweest in de subklasse CocktailMetGarnituur, dus hier wordt gewoon de versie uit de klasse Cocktail opgeroepen. In deze methode gebeuren nu twee andere methode-oproepen: zelf.alcoholpercentage() wordt opgeroepen, waarbij zelf verwijst naar het object bloodymary. Aangezien dit een object is van de klasse CocktailMetGarnituur en deze klasse de methode alcoholpercentage overschrijft, wordt hiermee dus de methode opgeroepen die 0.01 aftrekt van het gemiddelde alcoholpercentage van de ingrediënten. ander.alcoholpercentage() wordt opgeroepen, waarbij ander verwijst naar het object whiskycola. Aangezien dit een object is van de klasse Cocktail, speelt de subklasse CocktailMetGarnituur hierbij geen enkele rol, en wordt dus de methode opgeroepen die gewoon het gemiddelde alcoholpercentage van de ingrediënten teruggeeft. Samengevat zien we dus dat een subklasse alle functionaliteit overerft van zijn superklasse. Door bijkomende methodes toe te voegen, kan deze subklasse de functionaliteit van de superklasse dan gaan uitbreiden, en door reeds bestaande methodes te overschrijven, kan de subklasse de implementatie van sommige functionaliteiten naar wens gaan aanpassen. 3.2 Overervingshiërarchieën Het is natuurlijk ook mogelijk om subklassen te maken van een klasse die zelf al een subklasse is van een andere klasse. Op deze manier kunnen hele klassenhiërarchieën tot stand komen. Als voorbeeld kunnen we de classificatie van veelhoeken nemen, die in Figuur 3.3 getoond wordt. Onderstaande code toont hoe we deze hiërarchie in Python klassen kunnen gieten. Omdat we hier enkel geïnteresseerd zijn in de structuur van deze hiërarchie, hebben we al deze klassen voorlopig leeg gelaten. In een echt programma, zouden hier natuurlijk verschillende methodes in aanwezig zijn. class Veelhoek : pass class Driehoek ( Veelhoek ) : pass class Vierhoek ( Veelhoek ) : pass class RechthoekigeDriehoek ( Driehoek ) : pass 42

43 Figuur 3.3: Classificatie/overervingshiërarchie van veelhoeken. class GelijkzijdigeDriehoek ( Driehoek ) : pass class Trapezium ( Vierhoek ) : pass class Parallellogram ( Trapezium ) : pass class Ruit ( Parallellogram ) : pass class Rechthoek ( Parallellogram ) : pass class Vierkant ( Rechthoek, Ruit ) : pass De klasse Vierkant toont iets wat we nog niet eerder gezien hebben, namelijk een klasse die meer dan één superklasse heeft. Dit is een fenomeen dat meervoudige overerving genoemd wordt. Hoewel Python meervoudige overerving wel degelijk toelaat, kan het gebruik hiervan ervoor zorgen dat programma s moeilijker te begrijpen vallen, doordat het minder evident wordt om te zien welke methodes juist vanuit welke klasse worden overgeërfd. Om deze reden zijn er verschillende experts die het gebruik van meervoudige overerving afraden. Deze cursus volgt dit standpunt, en we zullen verder dan ook geen meervoudige overerving meer gebruiken. In een overervingshiërarchie kunnen we een onderscheid maken tussen een klasse die een directe subklasse is van een andere (zoals bv. de klasse Vierkant is van Rechthoek) en een klasse die een 43

44 De klasse object is de voorouder van alle klassen indirecte subklasse is (zoals bv. Vierkant van Vierhoek). Alle klassen in bovenstaand voorbeeld zijn dus een directe of indirecte subklasse van Veelhoek. We noemen deze klasse daarom ook wel de wortel van deze overervingshiërarchie. In het algemeen zullen we de klassen in een programma dus kunnen ordenen in een aantal overervingshiërarchieën, waarvan er sommigen mogelijk veel klassen en niveaus bevatten, en anderen misschien maar één. In het extreme geval, zouden we zelfs één enkele wortel-klasse in ons programma kunnen hebben, waarvan alle andere klassen (direct of indirect) overerven. Python bevat reeds een klasse object (met kleine eerste letter), die hiervoor specifiek bedoeld is. Dit is een relatief nieuwe toevoeging aan de taal, en om geen oude programma s stuk te maken, is het gebruik ervan (nog) niet verplicht. Het is echter wel aan te raden om dit toch te doen, maw. om te zorgen dat elke klasse die je definiëert (direct of indirect) overerft van object. In Python terminologie noemt men dit een klasse in de nieuwe stijl (new-style class). We zullen in deze cursus vanaf nu alleen nog maar klassen in de nieuwe stijl maken. 3.3 Methodes uit een superklasse oproepen super dient om een overschreven methode toch nog op te roepen Een vaak voorkomend fenomeen als we methodes gaan overschrijven, is dat we eigenlijk maar een heel kleine aanpassing willen doen aan het gedrag van de methode uit de superklasse. Dit zagen we bijvoorbeeld bij de methode alcoholpercentage, waarbij we in de subklasse eerst heel de berekening van het gemiddelde alcoholpercentage uit de superklasse herhaald hebben, om er dan daarna nog even een procent van af te trekken. We hadden dit toen gewoon gedaan door een paar regels code uit de superklasse te kopiëren, maar zoals we intussen weten is het dupliceren van code iets wat een goede programmeur altijd zoveel mogelijk tracht te vermijden. Python biedt daarom de mogelijkheid aan om vanuit een methode in een subklasse de overschreven gelijknamige methode uit de superklasse toch nog op te roepen. Hiervoor dient de functie super. Deze functie neemt twee argumenten: super(klasse, zelf). Het resultaat van deze functie is eigenlijk gewoon opnieuw het object zelf, maar dan met de eigenschap dat als je er methodes op oproept, niet de meest specifieke methodes uit Klasse zullen genomen worden, zoals normaal zou gebeuren, maar wel de overschreven methode uit de superklasse van Klasse. Laat ons dit illustreren met een voorbeeld. class Cocktail ( object ) :... def alcoholpercentage ( z e l f ) : r i j = GetallenRij ( ) for ingr in z e l f. ingredienten : r i j. voegtoe ( ingr. percentage * ingr. drank. alcoholpercentage ) 44

45 return r i j.som ( ) class CocktailMetGarnituur ( Cocktail ) :... def alcoholpercentage ( z e l f ) : return super ( CocktailMetGarnituur, z e l f ). alcoholpercentage ( ) 0.01 Merk op dat het natuurlijk niet zou werken, moesten we de laatste lijn van dit voorbeeld vervangen door gewoon: return z e l f. alcoholpercentage ( ) 0.01 Dit zou immers zorgen voor een nooit eindigend programma, waarin de methode alcoholpercentage uit CocktailMetGarnituur zichzelf altijd zou blijven oproepen. Het gebruik van de functie super dient dus precies om ervoor te zorgen dat deze methode niet zichzelf oproept, maar wel de gelijknamige methode uit de klasse Cocktail. Een waarschuwingswoordje: het gebruik van de functie super werkt enkel maar bij klassen in de nieuwe stijl, maw. die een (directe of indirecte) subklasse zijn van object. 3.4 Voorbeelden Vierkanten We definiëren een klasse Vierkant als subklasse van de klasse Veelhoek uit Sectie Deze klasse overschrijft de constructor van de klasse Veelhoek om ervoor te zorgen dat we een Vierkant kunnen aanmaken op basis van één hoekpunt en een lengte, in plaats van door het opsommen van al de vier hoekpunten. Daarnaast voegt deze constructor ook een extra attribuut toe (de klasse Veelhoek heeft alleen maar een attribuut punten), namelijk de lengte van een zijde. De andere methodes van de klasse Veelhoek, in het bijzonder de methode omtrek, worden gewoon overgeërfd. Een bijkomende methode oppervlakte wordt toegevoegd. class Vierkant ( Veelhoek ) : def i n i t ( z e l f, linksonder, lengte ) : z e l f. lengte = lengte linksboven = Punt ( linksonder. x, linksonder. y + lengte ) rechtsboven = Punt ( linksboven. x + lengte, linksboven. y ) rechtsonder = Punt ( rechtsboven. x, linksonder. y ) hoekpunten = [ linksonder, linksboven, rechtsboven, rechtsonder ] super ( Vierkant, z e l f ). i n i t ( hoekpunten ) 45

46 def oppervlakte ( z e l f ) : return z e l f. lengte ** 2 v = Vierkant(Punt(1,1), 2) print v.omtrek() print v.oppervlakte() 46

47 Varia 4 In dit hoofdstuk komen tot slot nog een paar kleinere onderwerpen aan bod. 4.1 Python als server-side scripting taal Python kan gebruikt worden als taal om webpagina s mee te genereren, en om gegevens van webformulieren mee te verwerken. Dit kan via het CGI protocol. De essentie hiervan is eenvoudig: als je jouw Python programma op de juiste plaats zet (en de webserver op de juiste manier geconfigureerd is), dan zal dit worden opgeroepen als de gebruiker naar een bepaalde URL surft. Via gewone print instructies, kan het Python programma dan HTML code genereren, die zal worden teruggestuurd naar de gebruiker. Het is wel aan te bevelen om als eerst de lijn Content-Type: text/html af te printen, die de webbrowser van de gebruiker vertelt dat er een HTML pagina aankomt. Daarna moet er dan nog een lege lijn volgen, vooraleer de HTML pagina zelf mag komen. #!/usr/bin/python print "Content-type: text/html" print "" print "<html> <head> <title> Hoi </title> </head>" print "<body> <p>dit is een pagina.</p> </body>" print "</html>" Dit wordt natuurlijk pas interessant als we ook invoer van de gebruiker kunnen verwerken. Herinner je dat de gegevens die een gebruiker invult in een webformulier worden doorgestuurd in de vorm van paren van een naam en een waarde, waarbij de naam afkomstig is van een attribuut van het HTML element waarin de gebruiker zijn gegevens heeft ingevuld. Python biedt een methode getvalue(naam) waarbij de waarde die bij een naam hoort kan worden opgevraagd. Deze methode behoort bij een object dat je aanmaakt met de functie cgi.fieldstorage() uit de module cgi. Het volgend voorbeeld laat zien hoe dit in zijn werk gaat. Dit is een HTML-formulier met daarop een tekstveldje met als de naam naam. 47

48 <html> <body> <form method="get" action="programma.py"> <input type="text" name="naam" /> <input type="submit /> </form> </body> <html> Als de gebruiker hier bijvoorbeeld de naam Joost invult in het tekstvenster en dan op de submit-knop klikt, zal zijn webbrowser surfen naar de URL:.../programma.py?naam=Joost Hierbij is programma.py de naam van volgend Python programma, dat de naam die de gebruiker heeft ingevuld zal opvragen, en gebruiken om een gepersonaliseerde groet te produceren. #!/usr/bin/python import cgi fs = cgi. FieldStorage ( ) naam = fs. getvalue ( "naam" ) print "Content-type: text/html" print "" print "<html> <head> <title> Hoi </title> </head>" print "<body> <p>hallo, " print naam print ".</p> </body>" print "</html>" 4.2 Methodes met default Laargumenten Het is in Python mogelijk om argumenten van een methode of functie een default waarde te geven. Dit laat dan toe om deze methode op te roepen met minder argumenten dan normaal, waarbij de default waarde dan automatisch wordt ingevuld voor de ontbrekende argumenten. Default argumenten worden dus m.a.w. optioneel. Beschouw bijvoorbeeld volgende functie die de afstand tussen twee punten (x 1, y 1 ) en (x 2, y 2 ) berekent en waarbij de laatste twee argumenten een default waarde 0 hebben. def afstand ( x1, y1, x2=0, y2=0) : import math return math. sqrt ( ( x1 x2 ) **2 + ( y1 y2 ) **2) We kunnen deze methode natuurlijk op de normale manier oproepen, door vier argumenten mee te geven: >>> afstand(2,2,1,1)

49 Dankzij de default argumenten, is het echter ook mogelijk om het laatste argument weg te laten. Hiervoor zal dan de overeenkomstige default waarde gebruikt worden, zodat volgende code de afstand tussen punten (2, 2) en (1, 0) zal berekenen. >>> afstand(2,2,1) Aangezien het derde argument van de methode ook een default waarde heeft, mag dit eveneens worden weggelaten. Volgende code berekent dus de afstand tussen punten (2, 2) en (0, 0). >>> afstand(2,2) Zoals aan deze voorbeelden te zien is, gebeurt het gebruik van default argumenten altijd van achter naar voor: dit wilt zeggen dat als je één argument weglaat, dit altijd het laatste argument is; als je twee argumenten weglaat, zijn dit de laatste twee argumenten; enzovoort. Om toch bv. enkel het laatste optionele argument te kunnen opgeven, kan je gebruik maken van zogenaamde keyword arguments. Hierbij maak je duidelijk welk van de optionele argumenten je een waarde wilt geven door middel van de naam van dit argument (zoals opgegeven bij de definitie van de methode/functie). Bijvoorbeeld: >>> afstand(1,1,y2=5) >>> afstand(1,1,0,5) Het is enkel toegestaan om bij de definitie van de functie een default waarde voor een argument te voorzien als ook alle verdere argumenten een default waarde hebben. Dit mag dus bijvoorbeeld niet, omdat y2 geen default waarde heeft: def afstand ( x1, y1, x2 =0, y2 ) : Vergelijken van objecten In een objectgeöriënteerde programmeertaal heeft elke object zijn eigen, unieke identiteit. We kunnen ons dit voorstellen zoals bijvoorbeeld ons eigen rijksregisternummer: een getal dat van bij onze geboorte aan ons wordt toegekend, en dat ook tot onze dood van ons zal blijven. We kunnen dit eenvoudig zien in de Python interpreter: >>> vodka = Drank("vodka", 0.4, 10) >>> vodka < main.drank instance at 0x50d788> Hier is 0x50d788 het unieke nummer 1 dat bij dit object hoort. Als we twee verschillende objecten aanmaken, hebben deze altijd verschillende nummers: 1 Dit nummer is trouwens niet zomaar een nummer, maar is eigenlijk het geheugenadres waarop het object in kwestie zit opgeslagen in het RAM geheugen van de computer. Elk object is uniek 49

50 >>> vodka2 = Drank("vodka", 0.4, 10) >>> vodka2 < main.drank instance at 0x50d760> Ondanks het feit dat alle attributen van de objecten vodka en vodka2 dus hetzelfde zijn, zijn deze twee objecten zélf toch verschillend. In het algemeen heeft elk object een unieke identiteit en daarnaast ook een aantal verschillende namen. In bovenstaande voorbeeldjes heeft het object met nummer 0x50d788 de naam vodka, en het object met nummer 0x50d760 de naam vodka2. We kunnen een object zoveel verschillende namen geven als we willen, en we kunnen ook de betekenis van een naam (dwz. het object waarnaar een naam verwijst) naar believen veranderen. De identiteit van een object blijft echter altijd dezelfde. In onderstaand code-fragmentje geven we bijvoorbeeld het object 0x50d760 een tweede naam: >>> vodka3 = vodka2 >>> vodka3 < main.drank instance at 0x50d760> >>> vodka2 < main.drank instance at 0x50d760> En in dit fragmentje wisselen we de betekenis van de namen vodka en vodka2 om: >>> vodka < main.drank instance at 0x50d788> >>> vodka2 < main.drank instance at 0x50d760> >>> tmp = vodka >>> vodka = vodka2 >>> vodka2 = tmp >>> vodka < main.drank instance at 0x50d760> >>> vodka2 < main.drank instance at 0x50d788> Nu we dit allemaal weten, is er nu een voor de hand liggende vraag: wat gebeurt er eigenlijk als we Python vragen of, bijvoorbeeld, vodka2 == vodka3? Er zijn op zijn minst twee verschillende dingen die zouden kunnen gebeuren: Python zou kunnen kijken of deze twee namen verwijzen naar hetzelfde object; Of Python zou kunnen kijken of deze twee namen verwijzen naar twee (potentieel verschillende) objecten die gelijk zijn aan elkaar, bijvoorbeeld omdat ze dezelfde waarde hebben voor al hun attributen. Het antwoord op deze vraag is een beetje complex. Een eerste deel van het antwoord is het feit dat Python niet één, maar twee verschillende manieren aanbiedt om dingen met elkaar te vergelijken: naast het 50

51 dubbele is-gelijk-aan-teken ==, kent Python ook het sleutelwoord is. Laten we om te beginnen eens kijken wat deze twee vergelijkingsoperatoren doen met twee verschillende strings, die allebei uit dezelfde opeenvolging van letters bestaan. >>> "hal" + "lo" hallo >>> "hallo" hallo >>> "hal" + "lo" == "hallo" True >>> "hal" + "lo" is "hallo" False Op basis hiervan, zouden we geneigd zijn om te denken dat Python het volgende doet: Met x is y kijkt Python of x en y twee namen zijn die naar hetzelfde object verwijzen; Met x == y kijkt Python of x en y namen zijn voor twee (potentieel verschillende) objecten die gelijk zijn aan elkaar. Voor onze eigen Drank objecten, blijkt dit echter niet waar te zijn: >>> Drank("cola",0,3) is Drank("cola",0,3) False >>> Drank("cola",0,3) == Drank("cola",0,3) False Wat is er hier nu aan de hand? Als we zelf een klasse definiëren, dan zal Python ons ook zelf laten kiezen wanneer we twee verschillende objecten van deze klasse als gelijk wensen te beschouwen. We kunnen dit doen door in onze klasse de magische methode eq (zelf, ander) te definiëren, waarbij de naam eq een afkorting is van het Engelse equals. Zolang we echter zelf geen zo n methode voorzien, zal Python gewoon uitgaan van deze implementatie: def eq ( z e l f, ander ) : return z e l f is ander Met andere woorden, Python zal in zo n geval == gewoon beschouwen als een synoniem voor is. Hieronder zijn twee pathologische voorbeelden van klassen die wel een eq methode hebben. class KlasseA : def eq ( z e l f, ander ) : return True class KlasseB : def eq ( z e l f, ander ) : return False 51

52 Het effect hiervan is natuurlijk als volgt: >>> KlasseA() == KlasseA() True >>> KlasseB() == KlasseB() False Een meer zinvolle eq zou bijvoorbeeld de volgende toevoeging aan onze klasse Drank kunnen zijn: class Drank:... def eq ( z e l f, ander ) : return ( z e l f.naam == ander.naam and z e l f. alcoholpercentage == ander. alcoholpercentage and z e l f. p r i j s == ander. p r i j s ) Hiermee bekomen we nu immers dit resultaat: >>> Drank("cola",0,3) is Drank("cola",0,3) False >>> Drank("cola",0,3) == Drank("cola",0,3) True Als we zouden willen hebben dat twee Drank-objecten als gelijk beschouwd worden vanaf dat, bijvoorbeeld, alleen al maar hun naam dezelfde is, dan volstaat het natuurlijk om de eq methode in die zin aan te passen. 4.4* Nog meer vergelijkingen Naast de == operator, kent Python nog een aantal andere vergelijkingsoperatoren. Ook van deze operatoren kan het gedrag aangepast worden door de juiste magische methode te implementeren. Hieronder een overzicht: Operator Magische methode == eq!= ne < lt <= le > gt >= ge Al deze methodes nemen twee argumenten: het zelf object en het andere object waarmee dit vergeleken moet worden. Het is ook mogelijk om al de verschillende vergelijkingsoperatoren in één klap te definiëren, zonder bovenstaande 6 verschillende methodes te moeten implementeren. Hiervoor dient de methode cmp (van compare). Deze methode geeft een getal terug dat aangeeft hoe zelf en het andere object zich tot elkaar verhouden: als de twee objecten gelijk 52

53 zijn, moet de methode 0 teruggeven; als zelf strikt kleiner is dan het andere object, moet een negatief getal < 0 worden teruggegeven; en als zelf strikt groter is, dan moet een positief getal > 0 worden teruggegeven. Moesten we bijvoorbeeld onze dranken willen ordenen volgens alcoholpercentage, dan kan dit als volgt: class Drank:... def cmp ( z e l f, ander ) : return z e l f. alcoholpercentage ander. alcoholpercentage >>> cola = Drank("cola", 0, 2) >>> fanta = Drank("fanta", 0, 2) >>> cola is fanta False >>> cola == fanta True >>> cola >= fanta True >>> cola <= fanta True Als we zowel een cmp methode implementeren als één of meer van de meer specifieke methodes uit bovenstaande tabel, dan krijgen de meer specifieke methodes voorrang. Bovenstaande magische methodes kan je dus gebruiken om te zorgen dat de vergelijkingsoperatoren voor je eigen objecten een specifieke betekenis krijgen. Ook van andere operatoren kan je door middel van magische methoden de betekenis aanpassen. Bijvoorbeeld, om aan te passen wat er gebeurt als je de indexatie operator [ ] toepast op een object obj van een bepaalde klasse: >>> print obj[5] kan je in deze klasse een methode get_item (zelf, waarde) implementeren. 4.5 Uitzonderingen Het volgend programma gebruikt de klasse GetallenRij, die we intussen al een paar keer zijn tegengekomen, om het gemiddelde te berekenen van een aantal getallen die de gebruiker ingeeft. class GetallenRij ( object ) : def i n i t ( z e l f ) : z e l f. r i j = [ ] 53

54 def voegtoe ( z e l f, getal ) : z e l f. r i j. append ( getal ) def str ( z e l f ) : return str ( z e l f. r i j ) def som( z e l f ) : som = 0 for getal in z e l f. r i j : som += getal return som def gemiddelde ( z e l f ) : som = z e l f.som ( ) return float (som) / len ( z e l f. r i j ) # ^^^^^ anders krijgen we een gehele deling r i j = GetallenRij ( ) while True : invoer = raw_input ( "Geef een getal in of druk op enter om het gemiddelde te zien. " ) i f ( invoer == "" ) : break else : r i j. voegtoe ( int ( invoer ) ) print r i j. gemiddelde ( ) Er kan zelfs met dit eenvoudige programma al vanalles mis gaan, als de gebruiker niet de gewenste invoer geeft. Eén van de dingen die bijvoorbeeld kan gebeuren, is dat de gebruiker op de entertoets drukt zonder getallen te hebben ingegeven. In dat geval, zal ons programma op een deling door nul stoten en op weinig elegante wijze eindigen. Laten we nu eens nadenken over hoe we dit probleem het beste oplossen. Er zijn hierbij twee vragen die we ons moeten stellen: Wat is de beste plaats om de fout te detecteren? Wat is de beste plaats om de fout op te lossen? Het mag duidelijk zijn dat het antwoord op beide vragen niet noodzakelijk hetzelfde is. Inderdaad, als we hierover nadenken, komen we tot volgende conclusies: Het detecteren van de fout gebeurt best zo laag mogelijk, dwz. vlak voor we effectief de deling door nul zouden gaan doen in de methode gemiddelde van de klasse GetallenRij. Door de foutendetectie op deze plaats te doen, zorgen we er immers voor dat elk gebruik van deze klasse van deze foutendetectie kan profiteren. Als we op twintig verschillende plaatsen in ons programma deze methode oproepen, dan zal dankzij één enkele if-test er bij alle twintig oproepen gecontroleerd worden of er toevallig geen deling door nul zou gebeuren. 54

55 Het oplossen van de fout gebeurt echter liefst op een hoger niveau. Om te weten hoe we deze fout het beste oplossen, moeten we immers weten waar onze GetallenRij vandaan komt. Als we deze zoals in bovenstaand programma hebben aangemaakt op basis van invoer die de gebruiker heeft ingegeven aan de prompt, dan is de beste aanpak waarschijnlijk om middels een print commando de gebruiker te melden dat hij minstens één getal moet ingeven. Het is daarnaast ook best mogelijk dat we ooit een ander programma schrijven dat dezelfde methode zal gebruiken om, bijvoorbeeld, het gemiddelde te berekenen van een rij getallen die we inlezen van een bestand op de harde schijf. In dit laatste geval, zou het natuurlijk niet zinvol zijn om de gebruiker te zeggen dat hij te weinig getallen heeft ingegeven, aangezien de gebruiker van niets weet. Aangezien de klasse GetallenRij zelf geen flauw idee heeft waar de getallen wiens gemiddelde ze moet berekenen eigenlijk vandaan komen, kan deze klasse dus de fout niet oplossen en moet dit op een hoger niveau gebeuren. Samengevat merken we dus dat we de fout zullen moeten oplossen op een andere plaats dan waar we ze detecteren. Hiervoor is er nood aan een mechanisme waarmee we de informatie dat er een fout is opgetreden kunnen doorgeven van de ene naar de andere plaats. Dit gebeurt in Python dmv. uitzonderingen. Op de plaats waar de fout gedetecteerd wordt, wordt de uitzondering opgeworpen door middel van het sleutelwoord raise. Op de plaats waar de fout afgehandeld moet worden, wordt de uitzondering afgehandeld door middel van een try-except blok Uitzonderingen opwerpen Laten we eerst eens kijken naar een voorbeeld van het opwerpen van een uitzondering. 1 class GetallenRij ( object ) : 2 3 def gemiddelde ( z e l f ) : 4 lengte = len ( z e l f. r i j ) 5 i f lengte == 0: 6 raise ValueError ( "Rij is leeg!" ) 7 som = z e l f.som ( ) 8 return float (som) / len ( z e l f. r i j ) Het opwerpen van de uitzondering gebeurt in lijn 6 van dit voorbeeld. Zoals hier te zien is, gebeurt dit door middel van het sleutelwoord raise gevolgd door een nieuw aangemaakt object, in dit geval van de klasse ValueError. Deze klasse is één van de vele uitzonderingsklassen die ingebouwd zitten in Python. Een overzicht van een aantal van de meest gebruikten is te zien in Figuur 4.1. Al deze klassen hebben een constructor die als argument een string neemt die de opgetreden fout beschrijft. 55

56 BaseException +-- Exception +-- StopIteration +-- StandardError +-- BufferError +-- ArithmeticError +-- FloatingPointError +-- OverflowError +-- ZeroDivisionError +-- EOFError +-- LookupError +-- IndexError +-- KeyError +-- RuntimeError +-- NotImplementedError +-- TypeError Figuur 4.1: Een aantal ingebouwde uitzonderingsklassen in een overervingshiërarchie. Het effect van onze uitzondering kunnen we als volgt zien: >>> rij = GetallenRij() >>> rij.gemiddelde() Traceback (most recent call last): File "<stdin>", line 1, in? File "GetallenRij.py", line 6, in gemiddelde raise ValueError("Rij is leeg!") ValueError: Rij is leeg! De laatste lijn toont de uitzondering die is opgetreden, en de lijnen daarvoor geven een traceback van de uitvoeringsstack die aangeeft waar deze uitzondering is opgetreden. Dit vertelt ons dat er vanop standaard-invoer (<stdin>) een commando binnenkwam, waarvan de eerste lijn de methode gemiddelde heeft opgeroepen, die zich bevond in het bestand GetallenRij. Bij het uitvoeren van de instructie van de methode gemiddelde die op lijn 6 van dit bestand staat, is er dan een uitzondering opgeworpen. Om eens een wat meer complexe traceback te zien te krijgen, voegen we wat functies toe aan het programma: def gemiddelde ( r i j ) : gr = GetallenRij ( ) for x in r i j : gr. voegtoe ( x ) return gem( gr ) def gem( g e t a l l e n r i j ) : return g e t a l l e n r i j. gemiddelde ( ) >>> gemiddelde([]) 56

57 Traceback (most recent call last): File "<stdin>", line 1, in? File "functies.py", line 5, in gemiddelde return gem(gr) File "functies.py", line 7, in gem return getallenrij.gemiddelde() File "GetallenRij.py", line 6, in gemiddelde raise ValueError("Rij is leeg!") ValueError: Rij is leeg! Hier zien we dus dat de uitzondering optrad in de methode gemiddelde, die was opgeroepen vanuit de functie gem, die was opgeroepen vanuit de functie gemiddelde, die was opgeroepen vanop <stdin>. Bovenstaande uitzondering hebben we zelf geraised, maar ook ingebouwde Python functies doen dit wel eens. Bijvoorbeeld: >>> rij = [1] >>> rij[2] Traceback (most recent call last): File "<stdin>", line 1, in? IndexError: list index out of range >>> x = 5 >>> x[2] Traceback (most recent call last): File "<stdin>", line 1, in? TypeError: unsubscriptable object >>> 5 / 0 Traceback (most recent call last): File "<stdin>", line 1, in? ZeroDivisionError: integer division or modulo by zero Uitzonderingen afhandelen In de voorbeelden die we tot dusver gezien hebben, werden de uitzondering die we gooiden altijd gewoon aan de gebruiker getoond. Als dat ons enige doel was, hadden we natuurlijk even goed met print kunnen werken. De kracht van het uitzonderingsmechanisme schuilt in het feit dat deze uitzonderingen ook weer kunnen afgehandeld worden, zodat de gebruiker ze nooit te zien zal krijgen. Met andere woorden, als we een fout of abnormale situatie ontdekken, dan gooien we een uitzondering, en als we er in slagen om de fout weer te herstellen of de situatie weer te normaliseren, dan handelen we deze uitzondering af, zonder dat de gebruiker ooit hoeft te weten dat er iets aan de hand was. Maar hoe werkt dit nu juist? Van zodra een uitzondering gegooid wordt in een methode of functie, wordt de uitvoering hiervan afgebroken en gaan we weer naar boven in de uitvoeringsstack. Als we terugdenken aan het voorbeeld van gemiddelde([]) uit de vorige sectie, dan zal het volgende gebeuren: In de methode gemiddelde van de klasse GetallenRij wordt de instructie raise ValueError(... ) uitgevoerd. 57

58 Op dit moment wordt de uitvoering van deze methode beëindigd, en keren we terug naar de plaats van waaruit deze methode werd opgeroepen, namelijk, de functie gem. Ook de uitvoering van de functie gem wordt beëindigd, en we keren terug naar de plaats van waaruit deze werd opgeroepen, namelijk, de functie gemiddelde. Ook de uitvoering van deze functie wordt beëindigd, en we keren terug naar de plaats van waaruit deze werd opgeroepen, namelijk, de instructie gemiddelde([]), die de gebruiker had ingegeven. De traceback die in de vorige sectie reeds getoond werd, is natuurlijk het resultaat van dit proces. Elk van deze tussenniveaus gaat nu de kans krijgen om de uitzondering die opgeworpen werd weer op te vangen. We kunnen ons dit voorstellen als een soort van gezagshiërarchie, zoals bijvoorbeeld in het leger. Als een generaal een opdracht moet vervullen, zal hij een aantal taken geven aan zijn kolonels. Een kolonel geeft op zijn beurt taken aan zijn kapiteins. Een kapitein geeft taken aan zijn sergeanten, en deze sergeanten geven taken aan de soldaten. Als nu een soldaat een probleem tegenkomt bij het uitvoeren van zijn taak, zal hij dit signaleren aan zijn sergeant. Deze sergeant kan dan misschien het probleem zelf oplossen (= de uitzondering afhandelen). Als hij dit echter zelf niet kan, dan zal hij het probleem tot bij zijn kapitein moeten brengen. Als de kapitein het ook niet kan oplossen, dan gaat het naar de kolonel. Als de kolonel het al evenmin opgelost krijgt, gaat het naar de generaal, en als die het ook niet kan oplossen, dan zal de taak gewoon niet uitgevoerd kunnen worden. In Python wordt zoiets geïmplementeerd dmv. een try-except blok. Dit ziet er als volgt uit: try : except ValueError : except ArithmeticError : In het try-blok staan een aantal gewone Python instructies. Als tijdens het uitvoeren van één van deze instructies een uitzondering wordt opgeworpen, dan wordt deze mogelijk afgehandeld door één van de except-blokken die bij het try-blok horen. Hiervoor wordt er gezocht naar een except-blok dat een superklasse vermeld van de klasse van de uitzondering die gegooid geweest is, of natuurlijk deze klasse zelf. In bovenstaand code-fragmentje, zullen dus alle uitzonderingen van (een subklasse van) de klasse ValueError en ArithmeticError worden afgehandeld. Andere uitzonderingen worden op deze plaats niet afgehandeld, en zullen dus verder naar boven op de uitvoeringsstack escaleren. 58

59 Als een geschikt except-blok gevonden wordt, dan wordt de code die hierin staat uitgevoerd. Het is natuurlijk de bedoeling dat deze code dan ook maatregelen neemt om de fout die is opgetreden te herstellen. Nadat deze code werd uitgevoerd, wordt de uitzondering dan ook als afgehandeld beschouwd. De uitvoering van het programma zal dan gewoon verder gaan op het einde de try-except constructie. Als dus, bijvoorbeeld, in lijn 4 een uitzondering word opgeworpen, dan zal lijn 5 sowieso niet meer worden uitgevoerd. Als de uitzondering die opgeworpen werd een ValueError (of subklasse daarvan) was, dan wordt eerst lijn 7 uitgevoerd, en gaan we daarna verder vanaf lijn 10. Als het een ArithmeticError (of subklasse daarvan) was, wordt lijn 9 uitgevoerd, en ook dan gaan we daarna verder vanaf lijn 10. Als een ander soort uitzondering gegooid werd, dan wordt niets van bovenstaande code meer uitgevoerd, maar escaleert de uitzondering verder naar de plaats van waarop deze code werd opgeroepen Herwerking van het voorbeeld We kunnen onze kennis van uitzonderingen nu gebruiken om het voorbeeld waarmee we dit hoofdstuk begonnen te verbeteren. class GetallenRij ( object ) : def i n i t ( z e l f ) : z e l f. r i j = [ ] def voegtoe ( z e l f, getal ) : z e l f. r i j. append ( getal ) def str ( z e l f ) : return str ( z e l f. r i j ) def som( z e l f ) : som = 0 for getal in z e l f. r i j : som += getal return som def gemiddelde ( z e l f ) : lengte = len ( z e l f. r i j ) i f lengte == 0: raise ValueError ( "Rij is leeg!" ) som = z e l f.som ( ) return float (som) / len ( z e l f. r i j ) opnieuwofstop = "o" # Om de lus minstens 1 keer te doen while opnieuwofstop == "o" : r i j = GetallenRij ( ) while True : invoer = raw_input ( "Geef een getal in of druk op enter om het gemiddelde te zien. " ) 59

60 i f ( invoer == "" ) : break else : r i j. voegtoe ( int ( invoer ) ) try : print r i j. gemiddelde ( ) except ValueError : print "Gelieve minstens 1 getal in te geven." opnieuwofstop = raw_input ( "Wilt u (o)pnieuw beginnen of (s)toppen? [o/s]" ) print "Vaarwel!" Geef een getal in of druk op enter om het gemiddelde te zien. 4 Geef een getal in of druk op enter om het gemiddelde te zien. 6 Geef een getal in of druk op enter om het gemiddelde te zien. 5.0 Wilt u (o)pnieuw beginnen of (s)toppen? [o/s]o Geef een getal in of druk op enter om het gemiddelde te zien. Gelieve minstens 1 getal in te geven. Wilt u (o)pnieuw beginnen of (s)toppen? [o/s]s Vaarwel! 4.6 Statische methodes Tot dusver hebben we gezien dat een klasse een verzameling van objecten is, waarbij deze objecten gegevens bijhouden in hun attributen en methodes aanbieden om de gegevens in deze attributen te manipuleren. Het is echter ook mogelijk om zowel methodes als attributen te koppelen aan een klasse op zich. Dit is nuttig voor zaken die nodig zijn voor de goede werking van een klasse, maar niet gekoppeld zijn aan een individueel object hiervan. Laten we nog eens een meetkundig voorbeeld nemen. Een cirkel heeft een middelpunt en een straal. De meest voor de hand liggende manier om een cirkel te construeren is daarom ook de volgende, waarbij we gebruik maken van de klasse Punt uit Sectie om het middelpunt voor te stellen. class Cirkel ( object ) : def i n i t ( z e l f, middelpunt, straal ) : z e l f. middelpunt = middelpunt z e l f. straal = straal def str ( z e l f ) : return "Cirkel van " + str ( z e l f. straal ) + " rond " + str ( z e l f. middelpunt ( ) ) c = Cirkel(Punt(1,1),2) 60

61 In meetkunde, kunnen we een cirkel nog op veel andere manieren definiëren dan via zijn middelpunt en straal. Zo kunnen we bijvoorbeeld vertrekken van een vierkant en daarvan ofwel de ingeschreven of de omgeschreven cirkel construeren, zoals getoond in Figuur 4.2. We zouden in de klasse Vierkant methodes kunnen opnemen die deze cirkels construeren: class Vierkant ( Veelhoek ) :... def middelpunt ( z e l f ) : middenx = z e l f. hoekpunten [ 0 ]. x + z e l f. lengte/2 middeny = z e l f. hoekpunten [ 0 ]. y + z e l f. lengte/2 return Punt ( middenx, middeny ) def ingeschreven ( z e l f ) : return Cirkel ( z e l f. middelpunt ( ), z e l f. lengte /2) def omgeschreven ( z e l f ) : import math half = z e l f. lengte/2 return Cirkel ( z e l f. middelpunt ( ), math. sqrt ( 2 ) * half ) Als andere programmeurs willen weten wat de verschillende manieren zijn om een cirkel te construeren, gaan ze echter waarschijnlijk eerder in de klasse Cirkel zelf gaan kijken, dan in de klasse Vierkant. Het kan daarom beter zijn om de twee laatste methodes daar te zetten, in plaats van in de klasse Vierkant. We zouden dit eventueel als volgt kunnen doen: class Cirkel ( object ) :... def ingeschreven ( z e l f, vierkant ) : return Cirkel ( vierkant. middelpunt ( ), vierkant. lengte /2) def omgeschreven ( z e l f, vierkant ) : import math half = vierkant. lengte/2 return Cirkel ( vierkant. middelpunt ( ), math. sqrt ( 2 ) * half ) Zoals altijd, hebben deze twee methodes allebei een argument zelf, maar in dit geval gebeurt daar helemaal niets mee! Hoewel we deze methode dus altijd moeten oproepen op een cirkel, maakt het eigenlijk helemaal niet uit op dewelke: het resultaat zal altijd hetzelfde zijn. Dat is natuurlijk ook logisch, aangezien de ingeschreven/omgeschreven cirkel alleen maar afhangt van het vierkant in kwestie. vierkant = Vierkant(Punt(2,2),2) 61

62 c1 = Cirkel(Punt(1,1),1) c2 = Cirkel(Punt(0,0),6) >>> print c1.omgeschreven(vierkant) Cirkel van rond Punt(3, 3) >>> print c2.omgeschreven(vierkant) Cirkel van rond Punt(3, 3) Een staticmethod heeft geen impliciet eerste argumente Om dit nutteloze eerste argument te vermijden, kunnen we een statische methode gebruiken. Een statische methode wordt niet opgeroepen op een object van een klasse, zoals een normale methode, maar wel op de klasse zelf. Een statische methode heeft geen impliciet eerste argument zelf. Je maakt een statische methode door gebruik te maken van de class Cirkel ( object ) def ingeschreven ( vierkant ) : return Cirkel ( vierkant. middelpunt ( ), vierkant. lengte def omgeschreven ( vierkant ) : import math half = vierkant. lengte/2 return Cirkel ( vierkant. middelpunt ( ), math. sqrt ( 2 ) * half ) We kunnen nu deze methodes gebruiken zonder daarvoor nog een object van de klasse Cirkel nodig te hebben. >>> print Cirkel.omgeschreven(vierkant) Cirkel van rond Punt(3, 3) >>> print Cirkel.ingeschreven(vierkant) Cirkel van 1 rond Punt(3, 3) Een ander voorbeeld is onderstaande statische methode die een eenheidscirkel construeert en teruggeeft. class Cirkel ( object ) def eenheidscirkel ( ) : return Cirkel ( Punt (0,0), 1) Hierdoor kunnen we nu bijvoorbeeld >>> c = Cirkel.eenheidscirkel() schrijven in plaats van >>> c = Cirkel(Punt(0,0), 1) 62

63 Figuur 4.2: Een vierkant met zijn ingeschreven (blauw) en omgeschreven (rood) cirkel. wat de leesbaarheid van onze code vergroot. Naast statische methoden, zijn er ook statische attributen mogelijk. Deze worden gewoon gedefiniëerd in de declaratie van de klasse, op hetzelfde niveau als de declaratie van de methodes. class Cirkel ( object ) :... pi = 22.0/7 Deze waarde kunnen we dan aanspreken als Cirkel.pi. Tussen haakjes, de beste manier om de waarde van π te pakken te krijgen, is natuurlijk nog steeds: import math math. pi Merk wel op dat het niet mogelijk is om de klasse Cirkel bijvoorbeeld als volgt een attribuut eenheidscirkel te geven, ipv. een methode: class Cirkel :... eenheidscirkel = Cirkel ( Punt (0,0),1) De reden hiervoor is dat we de constructor van de klasse Cirkel pas mogen gebruiken nadat de definitie van deze klasse volledig ten einde is. 4.7* Nog een blik achter de schermen Zoals we ons herinneren uit Sectie 1.8, zorgt Python er achter de schermen voor dat al de attributen van een object in een woordenboek belanden dat bij dit object hoort. Daarnaast hebben we ook gezien dat elke klasse eveneens een woordenboek heeft, waarin o.a. al de methodes van deze kasse zitten. Zoals we zouden verwachten, is het speciale aan een statisch attribuut niet meer of niet minder dan dat dit 63

64 attribuut zich in het woordenboek bevindt dat bij de klasse hoort, ipv. bij een individueel object van deze klasse. >>> c = Cirkel(Punt(0,0), 1) >>> print c. dict { middelpunt : < main.punt instance at 0x50dc60>, straal : 1} >>> print c. class <class main.cirkel > >>> print c. class. dict { dict : <attribute dict of Cirkel objects>, module : mai De volledige klasse Cirkel Hieronder nog eens de volledige klasse Cirkel met haar drie statische methodes en haar statische attribuut. class Cirkel ( object ) : def i n i t ( z e l f, middelpunt, straal ) : z e l f. middelpunt = middelpunt z e l f. straal = straal def str ( z e l f ) : return "Cirkel van " + str ( z e l f. straal ) + " rond " + str ( z e l f. middelpunt def ingeschreven ( vierkant ) : return Cirkel ( vierkant. middelpunt ( ), vierkant. lengte def omgeschreven ( vierkant ) : import math half = vierkant. lengte/2 return Cirkel ( vierkant. middelpunt ( ), math. sqrt ( 2 ) * half def eenheidscirkel ( ) : return Cirkel ( Punt (0,0),1) pi = 22/7 def omtrek ( z e l f ) : return 2 * Cirkel. pi * z e l f. straal 64

65 4.8 Private variabelen Zoals we intussen weten, is encapsulatie een belangrijke motivatie voor objectgericht programmeren. Door gegevens binnen een klasse zoveel mogelijk af te schermen, behouden we langs de ene kant zoveel mogelijk vrijheid om later aanpassingen binnenin deze klasse te doen, en maken we anderzijds ook het gebruik van deze klasse in de rest van ons programma zo eenvoudig mogelijk. Om dit te bereiken, is gewoon het gebruik van een objectgerichte programmeertaal op zich natuurlijk niet voldoende. We moeten hiervoor ook de discipline aan de dag leggen om nooit van buiten een klasse rechtstreeks in te grijpen in diens interne werking. Hier is nog eens een voorbeeld om dit te illustreren. We maken een klasse Datum en een klasse Reis die deze Datums gebruikt om zijn begin- en einddatum voor te stellen. class Datum( object ) : def i n i t ( z e l f, dag, maand, jaar ) : z e l f. dag = dag z e l f.maand = maand z e l f. jaar = jaar """ Magische methode om twee datums te vergelijken - Resultaat = 0: ze zijn gelijk - Resultaat < 0: de eerste is kleiner - Resultaat > 0: de tweede is kleiner """ def cmp ( z e l f, ander ) : jaarverschil = z e l f. jaar ander. jaar maandverschil = z e l f.maand ander.maand dagverschil = z e l f. dag ander. dag verschillen = [ jaarverschil, maandverschil, dagverschil ] # We geven nu het eerste verschil!= 0 terug for verschil in verschillen : i f verschil!= 0: return verschil # Tenzij er zo geen is, want dan zijn ze gelijk return def schrikkeljaar ( jaar ) : return jaar % 4 == 0 and ( jaar % 100! = 0 or jaar % 400 == def aantaldagen ( maand, jaar ) : met31 = [1,3,5,7,8,10,12] met30 = [4,6,9,11] i f maand in met31 : 65

66 return 31 i f maand in met30 : return 30 # Als we hier komen, is het februari i f Datum. schrikkeljaar ( jaar ) : return 29 else : return 28 def volgendedag ( z e l f ) : volgende = Datum( z e l f. dag, z e l f.maand, z e l f. jaar ) laatstedag = Datum. aantaldagen ( z e l f. maand, z e l f. jaar ) laatstemaand = 12 i f z e l f. dag < laatstedag : volgende. dag += 1 e l i f z e l f.maand < laatstemaand : volgende.maand += 1 volgende. dag = 1 else : # Gelukkig nieuwjaar! volgende. jaar += 1 volgende. dag = 1 volgende.maand = 1 return volgende class Reis ( object ) : def i n i t ( z e l f, van, tot, naar ) : z e l f. vertrek = van z e l f. terugkeer = tot z e l f. bestemming = naar def overlaptmet ( z e l f, ander ) : return not ( z e l f. terugkeer < ander. vertrek or ander. terugkeer < z e l f. vertrek ) def verleng ( z e l f, aantaldagen = 1) : for i in range ( aantaldagen ) : z e l f. terugkeer = z e l f. terugkeer. volgendedag ( ) >>> d1 = Datum(20, 8, 2013) >>> d2 = Datum(31, 8, 2013) >>> reis1 = Reis(d1, d2, "Istanbul") >>> d3 = Datum(1, 9, 2013) >>> d4 = Datum(5, 9, 2013) >>> reis2 = Reis(d3, d4, "Parijs") >>> reis1.overlaptmet(reis2) False >>> reis1.verleng() >>> reis1.overlaptmet(reis2) True 66

67 In bovenstaande code is de manier waarop we een datum voorstellen netjes ingekapseld in de klasse Datum. Dit betekent dat we deze voorstelling kunnen veranderen zonder code buiten deze klasse te moeten aanpassen. In het bijzonder de klasse Reis kan gewoon blijven zoals ze is. Laten we dit illustreren door een andere voorstelling van een datum te kiezen, waarbij er geen gebruik gemaakt wordt van dagen, maanden en jaren, maar waarbij we (net zoals het Unix besturingssysteem dat doet) een datum zullen voorstellen door middel van het aantal seconden dat verstreken is sinds 00:00:00 op 1 januari class Datum( object ) : secperdag = 60 * 60 * 24 dagenperjaar = 365 def i n i t ( z e l f, dag=1, maand=1, jaar =1970) : # Het aantal dagen sinds 1970 # tot aan het begin van dit jaar dagentotbeginjaar = 0 for j in range(1970, jaar ) : dagentotbeginjaar += dagenperjaar i f Datum. schrikkeljaar ( jaar ) : dagentotbeginjaar += 1 # Het aantal dagen in dit jaar # tot het begin van deze maand dagentotbeginmaand = 0 for m in range ( maand, 1) : dagentotbeginmaand += Datum. aantaldagen (m, jaar ) # Het aantal dagen in deze maand # tot aan deze dag dagentotdag = dag 1 dagentotaal = dagentotbeginjaar + dagentotbeginmaand + dagentotdag z e l f. verstreken = Datum. secperdag * dagentotaal """ Magische methode om twee datums te vergelijken - Resultaat = 0: ze zijn gelijk - Resultaat < 0: de eerste is kleiner - Resultaat > 0: de tweede is kleiner """ def cmp ( z e l f, ander ) : return z e l f. verstreken ander. def schrikkeljaar ( jaar ) : return jaar % 4 == 0 and ( jaar % 100! = 0 or jaar % 400 == 67

68 def aantaldagen ( maand, jaar ) : met31 = [1,3,5,7,8,10,12] met30 = [4,6,9,11] i f maand in met31 : return 31 i f maand in met30 : return 30 # Als we hier komen, is het februari i f Datum. schrikkeljaar ( jaar ) : return 29 return 28 def volgendedag ( z e l f ) : volgende = Datum ( ) # Maakt niet uit welke datum volgende. verstreken = z e l f. verstreken + 1 return volgende Je kan gemakkelijk nakijken dat, ook met deze nieuwe versie van de klasse Datum, de klasse Reis nog steeds correct zal werken. Dit is dus precies wat we met encapsulatie hopen te bereiken. Dit zou echter niet gelukt zijn, moesten we in onze klasse Reis bijvoorbeeld volgende methode gehad hebben: class Reis ( object ) :... Leidende underscore = privaat def str ( z e l f ) : r1 = "Reis naar " + z e l f. bestemming r2 = "(van " + str ( z e l f. vertrek. dag ) + "," + str ( z e l f. vertrek.maand) r2 = " tot " + str ( z e l f. terugkeer. dag ) + "," + str ( z e l f. terugkeer.maand) + ")" return r1 + r2 + r3 Deze methode zou met onze oorspronkelijke versie van de klasse Datum goed gewerkt hebben, maar met onze nieuwe versie niet meer. De reden hiervoor is dat ze de encapsulatie doorbreekt door rechtstreeks de attributen van een datum aan te spreken. Als we bij het maken de klasse Datum al het vermoeden hadden dat we misschien ooit de voorstelling van een datum zouden willen wijzigen, dan hadden we misschien beter geprobeerd om dit te voorkomen. In Python wordt een underscore _ gebruikt om aan te geven dat een attribuut enkel bedoeld is voor de interne werking van een klasse, en dat het niet de bedoeling is dat er vanuit andere klassen rechtstreeks met dit attribuut gewerkt wordt. We hadden onze eerste versie van de klasse Datum dus misschien beter zo geschreven: class Datum( object ) : def i n i t ( z e l f, dag, maand, jaar ) : z e l f. _dag = dag 68

69 z e l f._maand = maand z e l f. _jaar = jaar... Merk op: _dag, _maand en _jaar zijn nu gewoon de namen van de drie attributen van een Datum. Er is dus helemaal niets speciaals aan de underscore; dit is gewoon een letter die uitmaakt van de naam van het attribuut. De betekenis die eraan gegeven wordt (namelijk dat dit attributen zijn voor intern gebruik), is alleen maar een kwestie van afspraak tussen Python programmeurs. Als de waarde van deze attributen van buiten de klasse toch opgevraagd of gewijzigd moet kunnen worden, kunnen we hiervoor natuurlijk methodes voorzien. Een standaard conventie is om, voor een attribuut _ding, de naam getding te gebruiken voor een methode die de waarde van _ding teruggeeft, en de naam setding voor een methode die de waarde verandert. We spreken ook wel van getters en setters. class Datum( object ) : def i n i t ( z e l f, dag, maand, jaar ) : z e l f. _dag = dag z e l f._maand = maand z e l f. _jaar = jaar def getdag ( z e l f ) : return z e l f. _dag def setdag ( z e l f, dag ) : z e l f. _dag = dag... Als we nu later de dag/maand/jaar-voorstelling zouden willen vervangen door een andere, dan kunnen we door het aanpassen van de getters en setters voor deze attributen ervoor zorgen dat andere klassen toch onveranderd correct blijven werken. 4.9* Eigenschappen (Properties) Het voorbeeld met getters en setters uit de vorige sectie is een beetje on-pythoniaans. De reden hiervoor is dat Python eigenlijk een betere manier aanbiedt om hetzelfde effect te bekomen, namelijk het concept van eigenschappen (of properties in het Engels). Zo n eigenschap bestaat uit een attribuut met een bijhorende getter en/of setter. Bij de getter wordt de geplaatst, en bij de eventuele setter de waarbij de naam van de getter moet worden ingevuld. Zowel getter als setter moeten bovendien dezelfde naam hebben. Onderstaande code definieert drie eigenschappen, namelijk dag, maand en jaar. 69

70 class Datum( object ) : def i n i t ( z e l f, dag, maand, jaar ) : z e l f. _dag = dag z e l f._maand = maand z e l f. _jaar = def dag ( z e l f ) : return z e l f. setter def dag ( z e l f, dag ) : z e l f. _dag = def maand( z e l f ) : return z e l setter def maand( z e l f, maand) : z e l f._maand = def jaar ( z e l f ) : return z e l f. setter def jaar ( z e l f, jaar ) : z e l f. _jaar = jaar Het speciale hieraan is dat we zo n eigenschap nu kunnen gebruiken alsof het een gewoon attribuut betrof. We kunnen dus met andere woorden gewoon dit doen: >>> d = Datum(5,4,2001) >>> d.dag 5 >>> d.jaar = 1999 >>> d.jaar 1999 Merk op dat we hier dus niet de naam van de echte attributen van het Datum object gebruiken (die zijn immers _dag, _maand en _jaar, en dat zouden zelfs ook totaal andere namen kunnen zijn), maar wel de namen van de getters/setters die we geschreven hebben. Telkens als we hieraan een toekenning doen, zal Python de setter oproepen, en telkens als we hiervan de waarde opvragen, roept Python de getter op. In bovenstaand code fragmentje doet de tweede lijn dus eigenlijk een methode-oproep d.dag(), terwijl de 4 e lijn een methode-oproep d.jaar(1999) doet. Het leuke is dat al deze methode-oproepen onzichtbaar zijn, zodat we eigenlijk niet hoeven te weten of dag nu een echt attribuut dan wel een eigenschap is. Dit is een detail waarover 70

71 enkel de persoon die de klasse Datum implementeert zich zorgen moet maken, maar niet de programmeurs die deze klasse gewoon maar gebruiken. 71

72 72

73 Python voor dataanalyse 5 De reden dat deze cursus gebruik maakt van Python is dat deze taal in technisch-wetenschappelijke kringen steeds vaker gebruikt wordt. Vooral voor het visualiseren en analyseren van data is Python vandaag de dag een populaire keuze, die kan wedijveren met statistische pakketten zoals SAS of R. Hierbij heeft Python natuurlijk het voordeel dat het een echte programmeertaal is, zodat je over een veel grotere flexibiliteit beschikt. Eén van de voornaamste sterktes van Python voor dit soort van werk is de (gratis) beschikbaarheid van verschillende geavanceerde objectgeöriënteerde bibliotheken. numpy is een basisbibliotheek voor het doen van numerieke bewerkingen in Python. matplotlib is een bibliotheek om verschillende soorten van grafieken te maken. De gegevens die op deze grafieken getoond worden, worden typisch voorgesteld door numpy objecten. pandas is een bibliotheek voor datamanipulatie. Ook deze bibliotheek maakt gebruik van numpy objecten om data voor te stellen. Om visualisaties te maken, maakt pandas dan weer gebruik van matplotlib. scikit-learn is een Machine Learning bibliotheek, die toelaat om allerlei soorten van datamodellen op te stellen en te gebruiken. Ook hier wordt data voorgesteld door numpy objecten. In dit hoofdstuk bespreken we kort enkele van deze bibliotheken. Meer informatie is natuurlijk beschikbaar in de API 1 documentaties en verschillende online tutorials. Daarnaast is het ook altijd mogelijk om in een interactieve Python sessie gebruik te maken van de help functie om te ontdekken wat je met een bepaald object of met een bepaalde functie juist kan aanvangen. 1 Application Programming Interface: de verzameling van klassen, methodes, functies,... die een bibliotheek aanbiedt aan haar gebruikers. 73

74 5.1 De Pandas bibliotheek Pandas heeft twee klassen om data voor te stellen: één-dimensionale data wordt voorgesteld door een object van de klasse Series, terwijl meer-dimensionale data wordt voorgesteld door een DataFrame. Een DataFrame stelt een tabel voor met een aantal rijen en een aantal kolommen (zoals bv. ook een rekenblad in Excel eruit ziet). Het attribuut columns van zo n dataframe geeft de verschillende kolommen terug (samengevat in een object van de klasse Index). De indices van de verschillende rijen van de tabel zijn op te vragen met het attribuut index (eveneens een object van de klasse Index); meestal zijn de rijen gewoon genummerd met 0,1,2,... Je kan de indexatie operator [] gebruiken om één kolom te selecteren uit een dataframe. Zo selecteert df[ Foo ] bijvoorbeeld de kolom genaamd Foo uit het dataframe df. Ook de notatie df.foo kan je hiervoor gebruiken (elke kolom is dus een attribuut van het dataframe). Elke kolom van een dataframe is zelf een Series object. Je kan de indexatie operator daarnaast ook gebruiken om een aantal rijen uit een dataframe te selecteren: dit doe je door niet de naam van een kolom mee te geven, maar wel een test, waarbij je een kolom vergelijkt met iets anders. Bijvoorbeeld, df[df.foo > 5] selecteert uit het dataframe df alle rijen waarvoor er in de kolom Foo een waarde staat die groter is dan 5. Een dataframe wordt meestal ingevuld met gegevens die afkomstig zijn uit een bestand. Het gemakkelijkst is om te werken met bestanden in CSV (Comma-Separated Values) formaat: een bestand in dit formaat bestaat uit een aantal rijen, waarbij elke rij bestaat uit een aantal waardes die van elkaar gescheiden worden door een separator (scheidingsteken). In de Angelsaksische wereld gebruikt men vaak de komma als scheidingsteken (vandaar de naam Comma-Separated Values), maar ook puntkomma s of tabs worden vaak gebruikt, en zijn in Europese context natuurlijk handiger. Excel kan bijvoorbeeld eenvoudig data exporteren naar dit formaat, waarbij je kan kiezen welk scheidingsteken gebruikt wordt. Pandas heeft een functie read_csv waarmee je zo n CSV bestand kan inlezen. Het resultaat van deze functie is een dataframe. Om een dataframe te bekijken, kan je het gewoon printen. Daarnaast heeft een dataframe ook een methode head(i) die de enkel de eerste i rijen toont (i heeft een default waarde van 5) en kan je met de methode describe() verschillende statistieken berekenen. Om data te visualiseren kan je de methode plot(x,y) gebruiken, waarmee je een plot maakt van kolom y van het dataframe t.o.v. kolom x. Deze methode (die onderliggend gebruik maakt van matplotlib) kan een aantal verschillende soorten grafieken maken; het soort van grafiek dat je wilt, kan je aangeven met een optioneel derde argument kind (= het Engelse woord voor soort ). 74

75 5.2 De SciKit Learn Bibliotheek De scikit-learn bibliotheek wordt gebruikt om data te modelleren. Er zijn hiervoor een heleboel verschillende modellen beschikbaar. Al deze verschillende soorten van modellen zijn subklassen van een generieke klasse BaseEstimator. Een dergelijke schatter heeft een aantal parameters. De normale manier van werken is dat de parameters van de schatter eerst worden geleerd uit de dataset, waarna de getrainde schatter dan gebruikt kan worden om nieuwe waardes te voorspellen. Het eerste kan gebeuren dmv. de methode fit(x,y) van het estimator-object (hierbij is y de afhankelijke variabele die voorspeld moet worden op basis van de variabelen X). Zowel X als y zijn hierbij numpy arrarys: X heeft het formaat aantal voorbeelden aantal variabelen, terwijl y een één-dimensionale vector is van grootte aantal variabelen. Eenmaal de parameters zijn ingevuld, kan de methode predict(x) gebruikt worden om de y-waarde te voorspellen die volgens de schatter bij X hoort. Het is vaak ook interessant om te weten hoe goed de getrainde schatter de data vat. Hiervoor kan de methode score(x,y) gebruikt worden, die de R 2 -waarde teruggeeft. 5.3 Een voorbeeld analyse Als voorbeeld maken we een analyse van een online beschikbare dataset over de druksterkte van beton 2. In deze dataset zijn 1030 verschillende tests opgenomen. In elke test werd de druksterkte van een bepaald beton opgemeten. Elk beton wordt gekarakteriseerd door zeven verschillen eigenschappen: de leeftijd van het beton en de hoeveelheid (in kg/m 3 ) van zes verschillende materialen in het beton. Onderstaand voorbeeld toont hoe we deze dataset kunnen inlezen met behulp van pandas, verschillende grafieken genereren met behulp van pandas en matplotlib, en tenslotte met scikit-learn een aantal lineaire regressiemodellen opstellen om de druksterkte te voorspellen aan de hand van de andere parameters

76 In [79]: %matplotlib inline import pandas as pd # De data staan in CSV formaat, met tabs ( \t ) als separator # Decimale getallen zijn met een komma genoteerd df = pd.read_csv( DataConcrete.txt,sep= \t,decimal=, ) df.head() # We bekijken de eerste vijf rijen Out[79]: Cement Blast furnace slags Fly ashes Water Superplasticizers \ Coarse aggregates Fine aggregates Age Compressive strength In [80]: df.describe() # We bekijken wat statistieken van elke kolom Out[80]: Cement Blast furnace slags Fly ashes Water \ count mean std min % % % max Superplasticizers Coarse aggregates Fine aggregates Age \ count mean std min % % % max Compressive strength count mean std min % % % max In [81]: # De bedoeling is om de laatste kolom te voorspellen # We bekijken eens of er veel variatie in deze waardes zit, # door er een histogram van te maken df[ Compressive strength ].hist() Out[81]: <matplotlib.axes._subplots.axessubplot at 0x11645f410> 76

77 In [82]: # Om een idee te krijgen van welke variabelen een invloed hebben # op de doelkolom, maken we een aantal scatterplots doel = Compressive strength for kolom in df.columns: if kolom!= doel: df.plot(kolom, doel, kind= scatter ) 77

78 78

79 79

80 80

81 In [83]: # Een eerste observatie is dat "jong" beton minder sterk # lijkt dan oud beton. # We controleren of dit klopt d.m.v. een boxplot df.boxplot(column= Compressive strength, by= Age ) Out[83]: <matplotlib.axes._subplots.axessubplot at 0x > In [84]: # Op basis van deze boxplot, besluiten we dat beton # minstens 50 dagen moet uitharden alvorens zijn # maximale sterkte te bereiken. # Voor de verdere analyse beperken we ons dus tot beton # dat al minstens 50 dagen oud is. 81

82 df = df[df.age >= 50] df.describe() # Nu hebben we nog 281 van de 1030 datapunten over Out[84]: Cement Blast furnace slags Fly ashes Water \ count mean std min % % % max Superplasticizers Coarse aggregates Fine aggregates Age \ count mean std min % % % max Compressive strength count mean std min % % % max In [85]: # In onze scatterplots valt te zien dat er een (positief) verband # lijkt tussen de hoeveelheid cement en de sterkte. # We trainen een linear regressie model dat het verband tussen # deze twee variabelen vat from sklearn import linear_model reg_cem = linear_model.linearregression() X = df[[ Cement ]] # Twee paar vierkante haakjes omdat we een # 2-dimensionale 281 x 1 matrix nodig hebben, # in plaats van een 1-dimensionale vector van 281 lang. y = df[doel] reg_cem.fit(x, y) # We bekijken eens hoe de geleerde parameters van het model eruit zien print "Sterkte = "+str(reg_cem.coef_[0])+ * Cement + +str(reg_cem.intercept_) Sterkte = * Cement In [86]: # Als er bv. 100 kg/m^3 cement aanwezig is, # zouden we een sterkte verwachten van: cement = 100 print * cement In [87]: # Dit is ook de waarde die berekend wordt # door de methode "predict" van het model. print reg_cem.predict(cement) 82

83 [ ] In [88]: # We tekenen even opnieuw een scatter df.plot(x= Cement, y=doel, kind= scatter ) # En gebruiken nu matplotlib om hier een blauwe lijn aan te voegen, # die de door het model voorspelde waardes toont import matplotlib.pyplot as plt plt.plot(x, reg.predict(x), color= blue, linewidth=3) Out[88]: [<matplotlib.lines.line2d at 0x11647bdd0>] In [89]: # In de scatterplots leek er ook een licht negatief verband te zijn tussen # de hoeveelheid water en de sterkte. reg_water = linear_model.linearregression() reg_water.fit(df[[ Water ]], df[doel]) # We zien dat dit negatief verband inderdaad gevonden wordt: print "Sterkte = "+str(reg_water.coef_[0])+ * Water + +str(reg_water.intercept_) Sterkte = * Water In [90]: # Ook deze rechte tekenen we op een scatterplot df.plot(x= Water, y=doel, kind= scatter ) X = df[[ Water ]] plt.plot(x, reg_water.predict(x), color= blue, linewidth=3) Out[90]: [<matplotlib.lines.line2d at 0x1170e0e90>] 83

84 In [91]: # Met het blote oog lijkt het erop dat deze lijn # de data iets minder goed vat dan het model dat # cement als onafhankelijke variabele had. # We kunnen eens controleren of dit klopt door # naar de R^2 waarde te kijken X = df[[ Cement ]] y = df[doel] print "R^2 cement: " + str(reg_cem.score(x, y)) X = df[[ Water ]] print "R^2 water: " + str(reg_water.score(x, y)) Rˆ2 cement: Rˆ2 water: In [92]: # We zien dat cement inderdaad beter de data vat # dan water. # Het is natuurlijk ook mogelijk om een model te leren # waarin beide variabelen gebruikt worden. Hiervan # zouden we verwachten dat het beter scoort dan beide # afzonderlijk. reg_beide = linear_model.linearregression() X = df[[ Cement, Water ]] reg_beide.fit(x, y) print ("Sterkte = " + str(reg_beide.coef_[0]) + * Cement + str(reg_beide.coef_[1]) + * Water + str(reg_beide.intercept_)) print "R^2 beide: " + str(reg_beide.score(x, y)) Sterkte = * Cement * Water Rˆ2 beide: In [93]: # Moesten we in het begin de datapunten jonger dan 50 dagen # niet hebben weggelaten, zouden we een minder sterk verband # gevonden hebben. df = pd.read_csv( DataConcrete.txt,sep= \t,decimal=, ) X = df[[ Cement, Water ]] 84

85 y = df[doel] reg_beide.fit(x,y) print "R^2 beide: " + str(reg_beide.score(x, y)) Rˆ2 beide:

86 86

87 Practicum opgaves A Deze opgave is geïnspireerd door een project van de EAVISE onderzoeksgroep, waarin beelden van verkeersstromen zoals hieronder te zien geanalyseerd worden om de verkeersveiligheid te vergroten. Zo kan bijvoorbeeld het effect van verschillende soorten wegmarkeringen in de buurt van zebrapaden op de snelheid van passerende auto s geanalyseerd worden. A.1 Een eenvoudig datamodel: Auto Om bij te houden welke auto s er op welke plaats in het beeld te zien zijn, maken we een eenvoudige klasse Auto. Een auto heeft een bepaalde kleur, bevindt zich op een bepaalde (x, y) coördinaat in het vlak en heeft een lengte en een breedte. We stellen een auto dus gewoon voor als een rechthoek. Oef. 1.1 Maak een klasse Auto. Voorzie een constructor waarmee je een auto kan aanmaken met zijn verschillende eigenschappen (kleur, x-positie, y-positie, lengte, breedte). De (x, y)-coördinaat van een auto is die van 87

NETWERKEN en OBJECTORIËNTATIE

NETWERKEN en OBJECTORIËNTATIE FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN! CAMPUS DE NAYER! NETWERKEN en OBJECTORIËNTATIE Deel 2: Objectoriëntatie in Python Joost Vennekens Inhoudsopgave 1 Objecten en klassen 5 1.1 Een beetje geschiedenis......................

Nadere informatie

Objectgericht Programmeren. (in Python)

Objectgericht Programmeren. (in Python) Objectgericht Programmeren (in Python) Motivatie Programmeren is moeilijk Waarom? Complexiteit 100 200 300 400 500 kloc (1000 lijnen code) g1 = raw_input("eerste getal?") g2 = raw_input("tweede getal?")

Nadere informatie

N&O: Objectgericht Programmeren. (in Python)

N&O: Objectgericht Programmeren. (in Python) N&O: Objectgericht Programmeren (in Python) N&O Twee aparte onderwerpen Internet en websites (50%) Programmeren in Python (50%) Komen samen in dynamische websites Webpagina als user interface voor Python

Nadere informatie

Maak automatisch een geschikte configuratie van een softwaresysteem;

Maak automatisch een geschikte configuratie van een softwaresysteem; Joost Vennekens joost.vennekens@kuleuven.be Technologiecampus De Nayer We zijn geïnteresseerd in het oplossen van combinatorische problemen, zoals bijvoorbeeld: Bereken een lessenrooster die aan een aantal

Nadere informatie

Datatypes Een datatype is de sort van van een waarde van een variabele, veel gebruikte datatypes zijn: String, int, Bool, char en double.

Datatypes Een datatype is de sort van van een waarde van een variabele, veel gebruikte datatypes zijn: String, int, Bool, char en double. Algemeen C# Variabele Een variabele is een willekeurige waarde die word opgeslagen. Een variabele heeft altijd een datetype ( De soort waarde die een variabele bevat). Datatypes Een datatype is de sort

Nadere informatie

Objectgericht programmeren 1.

Objectgericht programmeren 1. Objectgericht programmeren 1 joost.vennekens@kuleuven.be http://www.cs.kuleuven.be/~joost/dn Objectgericht ontwerpen 35% Objectgericht ontwerpen 65% OP1 Informatiesystemen 50% Databanken 50% OP1 Evaluatie

Nadere informatie

Het relaas van de beginnende programmeur. Het hoe en waarom van de assistent

Het relaas van de beginnende programmeur. Het hoe en waarom van de assistent Het relaas van de beginnende programmeur Het hoe en waarom van de assistent 1. Help, mijn code doet niks... Mogelijke oplossingen: Heb je op run geduwd (groene pijltje)? Zolang je niet op 'run' duwt, kent

Nadere informatie

OEFENINGEN PYTHON REEKS 1

OEFENINGEN PYTHON REEKS 1 Vraag 1: Expressies & Types OEFENINGEN PYTHON REEKS 1 Python maakt gebruik van enkele vaak voorkomende (data)types. Zo zijn er integers die behoren tot de gehele getallen (VB: 3), zijn er float s die behoren

Nadere informatie

OEFENINGEN PYTHON REEKS 1

OEFENINGEN PYTHON REEKS 1 OEFENINGEN PYTHON REEKS 1 Vraag 1: Python als een eenvoudige rekenmachine Python maakt gebruik van enkele vaak voorkomende (data)types. Zo zijn er integers die behoren tot de gehele getallen (VB: 3) en

Nadere informatie

Programmeermethoden NA. Week 5: Functies (vervolg)

Programmeermethoden NA. Week 5: Functies (vervolg) Programmeermethoden NA Week 5: Functies (vervolg) Kristian Rietveld http://liacs.leidenuniv.nl/~rietveldkfd/courses/prna/ Bij ons leer je de wereld kennen 1 Functies Vorige week bekeken we functies: def

Nadere informatie

Vakgroep CW KAHO Sint-Lieven

Vakgroep CW KAHO Sint-Lieven Vakgroep CW KAHO Sint-Lieven Objecten Programmeren voor de Sport: Een inleiding tot JAVA objecten Wetenschapsweek 20 November 2012 Tony Wauters en Tim Vermeulen tony.wauters@kahosl.be en tim.vermeulen@kahosl.be

Nadere informatie

Programmeermethoden NA

Programmeermethoden NA Programmeermethoden NA Week 6: Lijsten Kristian Rietveld http://liacs.leidenuniv.nl/~rietveldkfd/courses/prna/ Bij ons leer je de wereld kennen 1 Getal opbouwen Stel je leest losse karakters (waaronder

Nadere informatie

Programmeermethoden NA. Week 6: Lijsten

Programmeermethoden NA. Week 6: Lijsten Programmeermethoden NA Week 6: Lijsten Kristian Rietveld http://liacs.leidenuniv.nl/~rietveldkfd/courses/prna2016/ Getal opbouwen Stel je leest losse karakters (waaronder cijfers) en je moet daar een getal

Nadere informatie

Programmeren (1) Examen NAAM:

Programmeren (1) Examen NAAM: Schrijf al je antwoorden op deze vragenbladen (op de plaats die daarvoor is voorzien) en geef zowel klad als net af. Bij heel wat vragen moet je zelf Java-code schrijven. Hou dit kort en bondig. Je hoeft

Nadere informatie

Programmeermethoden NA. Week 5: Functies (vervolg)

Programmeermethoden NA. Week 5: Functies (vervolg) Programmeermethoden NA Week 5: Functies (vervolg) Kristian Rietveld http://liacs.leidenuniv.nl/~rietveldkfd/courses/prna2016/ Functies Vorige week bekeken we functies: def bereken(a, x): return a * (x

Nadere informatie

[14] Functies. Volg mee via 14_Functies-1.py. We beginnen met een eenvoudig voorbeeldje:

[14] Functies. Volg mee via 14_Functies-1.py. We beginnen met een eenvoudig voorbeeldje: [14] Functies Een goede programmeur doet altijd zijn best om zoveel mogelijk aan hergebruik van code te doen. Je probeert in je programma code te gebruiken die iemand anders heeft gemaakt, of code die

Nadere informatie

[15] Variabelen in functies (of: een woordje over scope)

[15] Variabelen in functies (of: een woordje over scope) [15] Variabelen in functies (of: een woordje over scope) In de vorige leerfiche hebben we geleerd over functies. We leerden dat functies parameters hebben en dat ze return-waarden kunnen teruggeven aan

Nadere informatie

BEGINNER JAVA Inhoudsopgave

BEGINNER JAVA Inhoudsopgave Inhoudsopgave 6 Configuratie Hallo wereld! Praten met de gebruiker Munt opgooien Voorwaarden Lussen......6 Configuratie Met deze Sushi kaarten ga je een simpel spel maken met één van de meest populaire

Nadere informatie

Inleiding Programmeren 2

Inleiding Programmeren 2 Inleiding Programmeren 2 Gertjan van Noord 11 december 2017 Zelle hoofdstuk 10 Stof Overzicht - theorie 1. Zelle hoofdstuk 4 en 5 2. Zelle hoofdstuk 7 en 8, recursie, Brookshear hoofdstuk 5 3. Zelle hoofdstuk

Nadere informatie

Les 3. Gebruik in volledige programma Default argumenten Vergelijken van objecten

Les 3. Gebruik in volledige programma Default argumenten Vergelijken van objecten Les 3 Gebruik in volledige programma Default argumenten Vergelijken van objecten Een Python programma def foo( ):... def bar( ):... def baz( ):... def main():... main() Een Python programma class Drank:...

Nadere informatie

Inleiding Programmeren 2

Inleiding Programmeren 2 Inleiding Programmeren 2 Gertjan van Noord, Leonie Bosveld 12 december 2016 Zelle hoofdstuk 10 Stof Overzicht - theorie 1. Zelle hoofdstuk 4 en 5 2. Zelle hoofdstuk 7 en 8, recursie, Brookshear hoofdstuk

Nadere informatie

Variabelen en statements in ActionScript

Variabelen en statements in ActionScript Ontwikkelen van Apps voor ios en Android Variabelen en statements in ActionScript 6.1 Inleiding Als we het in de informatica over variabelen hebben, bedoelen we een stukje in het geheugen van de computer

Nadere informatie

Een spoedcursus python

Een spoedcursus python Een spoedcursus python Zoals je in de titel misschien al gezien hebt, geven wij een spoedcursus Python. Door deze cursus leer je alle basics, zoals het rekenen met Python en het gebruik van strings. Het

Nadere informatie

INHOUDSOPGAVE. Over de auteur, de illustrator en de technische redacteuren 13

INHOUDSOPGAVE. Over de auteur, de illustrator en de technische redacteuren 13 INHOUDSOPGAVE Over de auteur, de illustrator en de technische redacteuren 13 Dankwoord 14 Inleiding 15 Waarom Python?... 16 Hoe je code leert schrijven... 16 Voor wie is dit boek... 17 Wat staat er in

Nadere informatie

VAN HET PROGRAMMEREN. Inleiding

VAN HET PROGRAMMEREN. Inleiding OVERZICHT VAN HET PROGRAMMEREN Inleiding Als je leert programmeren lijkt het nogal overweldigend om die eerste stappen te doorworstelen. Er zijn dan ook heel wat programmeertalen (Java, Ruby, Python, Perl,

Nadere informatie

Beginselen van programmeren Practicum 1 (Doolhof) : Oplossing

Beginselen van programmeren Practicum 1 (Doolhof) : Oplossing Beginselen van programmeren Practicum 1 (Doolhof) : Oplossing Introductie In dit document geven we een mogelijke oplossing voor het eerste practicum. Deze oplossing gebruikt verschillende klassen en overerving,

Nadere informatie

Zoemzinnen. Algemene info. Functies met een variabel aantal argumenten

Zoemzinnen. Algemene info. Functies met een variabel aantal argumenten Zoemzinnen Functies met een variabel aantal argumenten Bij het definiëren van een functie leg je in principe vast hoeveel argumenten er aan de functie moeten doorgegeven worden. Dit aantal correspondeert

Nadere informatie

VAN HET PROGRAMMEREN. Inleiding. Het spiraalmodel. De programmeertaal. vervolgens de berekening van het totale bedrag, incl. BTW:

VAN HET PROGRAMMEREN. Inleiding. Het spiraalmodel. De programmeertaal. vervolgens de berekening van het totale bedrag, incl. BTW: OVERZICHT VAN HET PROGRAMMEREN Inleiding Als je leert programmeren lijkt het nogal overweldigend om die eerste stappen te doorworstelen. Er zijn dan ook heel wat programmeertalen (Java, Ruby, Python, Perl,

Nadere informatie

Overerving & Polymorfisme

Overerving & Polymorfisme Overerving & Polymorfisme Overerving Sommige klassen zijn speciaal geval van andere klasse Docent is een speciaal geval van werknemer, dwz. elke docent is ook werknemer Functionaliteit van docent = functionaliteit

Nadere informatie

OEFENINGEN PYTHON REEKS 1

OEFENINGEN PYTHON REEKS 1 Vraag 1: Expressies & Types OEFENINGEN PYTHON REEKS 1 Python maakt gebruik van enkele vaak voorkomende (data)types. Zo zijn er integers die behoren tot de gehele getallen (VB: 3), zijn er float s die behoren

Nadere informatie

HOE TEKEN IK EEN OMGEVINGSMODEL

HOE TEKEN IK EEN OMGEVINGSMODEL HOE TEKEN IK EEN OMGEVINGSMODEL MATTIAS DE WAEL 1. Inleiding Om de allereenvoudigste Scheme expressies te begrijpen volstaat het substitutiemodel. Het substitutiemodel verondersteld het bestaan van een

Nadere informatie

Modelleren en Programmeren

Modelleren en Programmeren Modelleren en Programmeren Jeroen Bransen 11 december 2015 Ingebouwde datastructuren Meer boomstructuren Access specifiers Gebruikersinvoer Codestijl Packages SAT-solver Ingebouwde datastructuren Ingebouwde

Nadere informatie

4 ASP.NET MVC. 4.1 Controllers

4 ASP.NET MVC. 4.1 Controllers 4 ASP.NET MVC ASP.NET is het.net raamwerk voor het bouwen van webapplicaties. De MVC variant hiervan is speciaal ontworpen voor het bouwen van dergelijke applicaties volgens het Model-View-Controller paradigma.

Nadere informatie

Arrays. Complexe datastructuren. Waarom arrays. Geen stijlvol programma:

Arrays. Complexe datastructuren. Waarom arrays. Geen stijlvol programma: Geen stijlvol programma: Complexe datastructuren Arrays vijf verschillende variabelen voor iets dat als één rij getallen bestempeld wordt; onbruikbaar wanneer het over meer getallen (bijvoorbeeld ) gaat.

Nadere informatie

Je gaat leren programmeren en een spel bouwen met de programmeertaal Python. Websites zoals YouTube en Instagram zijn gebouwd met Python.

Je gaat leren programmeren en een spel bouwen met de programmeertaal Python. Websites zoals YouTube en Instagram zijn gebouwd met Python. 1 Je gaat leren programmeren en een spel bouwen met de programmeertaal Python. Websites zoals YouTube en Instagram zijn gebouwd met Python. Voordat je leert programmeren, moet je jouw pc zo instellen dat

Nadere informatie

[13] Rondjes draaien (loops)

[13] Rondjes draaien (loops) [13] Rondjes draaien (loops) Met de if else uit de leerfiche [11] hebben we leren werken met één van de belangrijkste programmeerstructuren in Python. Bijna even belangrijk zijn de verschillende mogelijkheden

Nadere informatie

MINICURSUS PHP. Op dit lesmateriaal is een Creative Commons licentie van toepassing Sebastiaan Franken en Rosalie de Klerk Bambara

MINICURSUS PHP. Op dit lesmateriaal is een Creative Commons licentie van toepassing Sebastiaan Franken en Rosalie de Klerk Bambara MINICURSUS PHP Op dit lesmateriaal is een Creative Commons licentie van toepassing. 2017-2018 Sebastiaan Franken en Rosalie de Klerk Bambara PHP Cursus Deze cursus is om de eerste stappen in de wereld

Nadere informatie

PYTHON REEKS 2: FUNCTIES. Mathias Polfliet

PYTHON REEKS 2: FUNCTIES. Mathias Polfliet PYTHON REEKS 2: FUNCTIES Mathias Polfliet mpolflie@etrovub.be TERUG NAAR PYTHON BASICS VRAAG 1: VOLUME BOL Het volume van een bol met straal r is 4 3 πr3 π Wat is het volume in cm³ van een bol met straal

Nadere informatie

Uitleg van de Hough transformatie

Uitleg van de Hough transformatie Uitleg van de Hough transformatie Maarten M. Fokkinga, Joeri van Ruth Database groep, Fac. EWI, Universiteit Twente Versie van 17 mei 2005, 10:59 De Hough transformatie is een wiskundige techniek om een

Nadere informatie

Programmeren: Visual Basic

Programmeren: Visual Basic PETERSTUYVESANT COLLEGE INFORMATICA 2009-2010 Programmeren: Visual Basic Document Afbaking 01. VERSCHILLENDE PROGRAMMEERTALEN 02. PROGRAMMEER PAKKETTEN 03. GUI 03.1 GUI ELEMENTEN 03.2 GUI EIGENSCHAPPEN

Nadere informatie

Inleiding Programmeren 2

Inleiding Programmeren 2 Inleiding Programmeren 2 Gertjan van Noord November 26, 2018 Stof week 3 nogmaals Zelle hoofdstuk 8 en recursie Brookshear hoofdstuk 5: Algoritmes Datastructuren: tuples Een geheel andere manier om te

Nadere informatie

Objectgeoriënteerd programmeren in Java 1

Objectgeoriënteerd programmeren in Java 1 Objectgeoriënteerd programmeren in Java 1 CPP Javaprogrammeur Bijeenkomst 3 Leereenheden 7, 8, 9 De Java API Java bevat een grote bibliotheek standaardklassen: de Java API Voorbeelden java.lang basisklassen

Nadere informatie

Visual Basic.NET. Visual Basic.NET. M. den Besten 0.3 VB. NET

Visual Basic.NET. Visual Basic.NET. M. den Besten 0.3 VB. NET Visual Basic.NET M. den Besten 0.3 VB. NET Inhoud Voorwoord Deel 1 Visual Basic.NET 1.1 Inleiding...13 1.2 De programmeertaal Visual Basic.NET...14 1.3 Microsoft Visual Basic 2010 Express Edition...15

Nadere informatie

II. ZELFGEDEFINIEERDE FUNCTIES

II. ZELFGEDEFINIEERDE FUNCTIES II. ZELFGEDEFINIEERDE FUNCTIES In Excel bestaat reeds een uitgebreide reeks van functies zoals SOM, GEMIDDELDE, AFRONDEN, NU enz. Het is de bedoeling om functies aan deze lijst toe te voegen door in Visual

Nadere informatie

Python. Vraag 1: Expressies en types. Vraag 1 b: Types -Ingebouwde functies- Vraag 1 a 3/10/14

Python. Vraag 1: Expressies en types. Vraag 1 b: Types -Ingebouwde functies- Vraag 1 a 3/10/14 Python Vraag 1: Expressies en types Integrated Development Environment (IDE): Ø Wing 101 (gratis te downloaden op www.wingware.com) Oefeningen in de shell >> noemen we de prompt Python commando s = expressies

Nadere informatie

Informatica. Deel II: les 1. Java versus Python. Jan Lemeire Informatica deel II februari mei 2014. Parallel Systems: Introduction

Informatica. Deel II: les 1. Java versus Python. Jan Lemeire Informatica deel II februari mei 2014. Parallel Systems: Introduction Informatica Deel II: les 1 Java versus Python Jan Lemeire Informatica deel II februari mei 2014 Parallel Systems: Introduction Arabidopsis (zandraket) Arabidopsis (zandraket) MMIQQA Multimodal Microscopic

Nadere informatie

10 Meer over functies

10 Meer over functies 10 Meer over functies In hoofdstuk 5 hebben we functies uitgebreid bestudeerd. In dit hoofdstuk bekijken we drie andere aspecten van functies: recursieve functies dat wil zeggen, functies die zichzelf

Nadere informatie

Uitwerking Tweede deeltentamen Imperatief programmeren - versie 1 Vrijdag 21 oktober 2016, uur

Uitwerking Tweede deeltentamen Imperatief programmeren - versie 1 Vrijdag 21 oktober 2016, uur Uitwerking Tweede deeltentamen Imperatief programmeren - versie 1 Vrijdag 21 oktober 2016, 13.00-15.00 uur 1. De situatie die ontstaat door class A : B C D; kan beschreven worden door (a) B is een A (b)

Nadere informatie

Een eenvoudig algoritme om permutaties te genereren

Een eenvoudig algoritme om permutaties te genereren Een eenvoudig algoritme om permutaties te genereren Daniel von Asmuth Inleiding Er zijn in de vakliteratuur verschillende manieren beschreven om alle permutaties van een verzameling te generen. De methoden

Nadere informatie

Algemeen. Rorschachtest. Algemene info

Algemeen. Rorschachtest. Algemene info Algemeen Als Python de volgende regel moet lezen uit een tekstbestand, dan wordt er gelezen tot en met de eerstvolgende newline ('\n') of tot het einde van het bestand. Het laatste karakter van de regel

Nadere informatie

Als een PSD selecties bevat, deelt de lijn van het programma zich op met de verschillende antwoorden op het vraagstuk.

Als een PSD selecties bevat, deelt de lijn van het programma zich op met de verschillende antwoorden op het vraagstuk. HOOFDSTUK 3 3.1 Stapsgewijs programmeren In de vorige hoofdstukken zijn programmeertalen beschreven die imperatief zijn. is het stapsgewijs in code omschrijven wat een programma moet doen, net als een

Nadere informatie

Computervaardigheden. Universiteit Antwerpen. Computervaardigheden en Programmatie. Grafieken en Rapporten 1. Inhoud. Wat is scripting?

Computervaardigheden. Universiteit Antwerpen. Computervaardigheden en Programmatie. Grafieken en Rapporten 1. Inhoud. Wat is scripting? Inhoud Computervaardigheden Hoofdstuk 4 Scripting (Let op: dit is enkel voor studenten Biologie.) Dit hoofdstuk bekijkt heel kort de basis van scripting. - Opstellen van functies. - Conditionele code.

Nadere informatie

PYTHON REEKS 1: BASICS. Mathias Polfliet

PYTHON REEKS 1: BASICS. Mathias Polfliet PYTHON REEKS 1: BASICS Mathias Polfliet mpolflie@etrovub.be EENVOUDIGE REKENMACHINE 2 soorten getallen Getallen Z -> integers (gehele getallen) Getallen R -> floating points (reële getallen) Door beperkte

Nadere informatie

Dynamiek met VO-Script

Dynamiek met VO-Script Dynamiek met VO-Script Door Bert Dingemans DLA Ontwerp & Software bert@dla-architect.nl Inleiding Op de SDGN nieuwsgroep voor Visual Objects ontstond laatst een draad van berichten over de nieuwe libraries

Nadere informatie

Scala. Korte introductie. Sylvia Stuurman

Scala. Korte introductie. Sylvia Stuurman Korte introductie Sylvia Stuurman Wat is er zo bijzonder aan? Schaalbaar Objectgeoriënteerd (handiger dan Java!) Functioneel Scripts schrijven Gecompileerd: Java bytecode Pagina 2 voor scripts Pagina 3

Nadere informatie

[8] De ene 1 is de andere niet

[8] De ene 1 is de andere niet [8] De ene 1 is de andere niet Volg mee via 08_Types.py In de volgende leerfiche gaan we rekenen met Python. Dat kan je in een programma doen, maar dat kan je ook gewoon vanuit het Shell-venster doen.

Nadere informatie

Ontwerp van Informatiesystemen

Ontwerp van Informatiesystemen 1ste bach HIB Ontwerp van Informatiesystemen Prof. Verelst Q www.quickprinter.be uickprinter Koningstraat 13 2000 Antwerpen 112 2,50 Online samenvattingen kopen via www.quickprintershop.be Table of Contents

Nadere informatie

Labo 2 Programmeren II

Labo 2 Programmeren II Labo 2 Programmeren II L. Schoofs K. van Assche Gebruik Visual Studio 2005 om een programma te ontwikkelen dat eenvoudige grafieken tekent. Deze opgave heb je vorig academiejaar reeds in Java geïmplementeerd.

Nadere informatie

Toets Programmeren, 2YP05 op donderdag 13 november 2008, 09:00-12:00

Toets Programmeren, 2YP05 op donderdag 13 november 2008, 09:00-12:00 Toets Programmeren, 2YP05 op donderdag 13 november 2008, 09:00-12:00 TU/e Technische Universiteit Eindhoven Faculteit Wiskunde en Informatica (Na de toets gecorrigeerde versie) PROBLEEM: Sleutels Lees

Nadere informatie

Les 9: formulier controle met javascript.

Les 9: formulier controle met javascript. Les 9: formulier controle met javascript. Javascript is erg veel gebruikt bij internet toepassingen. In tegenstelling tot PHP, wat een server side scripting is, is java client side scripting. Dwz, niet

Nadere informatie

Instructie voor Docenten. Hoofdstuk 13 OMTREK EN OPPERVLAKTE

Instructie voor Docenten. Hoofdstuk 13 OMTREK EN OPPERVLAKTE Instructie voor Docenten Hoofdstuk 13 OMTREK EN OPPERVLAKTE Instructie voor docenten H13: OMTREK EN OPPERVLAKTE DOELEN VAN DIT HOOFDSTUK: Leerlingen weten wat de begrippen omtrek en oppervlakte betekenen.

Nadere informatie

start -> id (k (f c s) (g s c)) -> k (f c s) (g s c) -> f c s -> s c

start -> id (k (f c s) (g s c)) -> k (f c s) (g s c) -> f c s -> s c Een Minimaal Formalisme om te Programmeren We hebben gezien dat Turing machines beschouwd kunnen worden als universele computers. D.w.z. dat iedere berekening met natuurlijke getallen die met een computer

Nadere informatie

Cursus Programmeren en Dataverwerking.

Cursus Programmeren en Dataverwerking. Cursus Programmeren en Dataverwerking http://hay.github.io/codecourse Vanavond (18.00-21.30) Introductierondje Algemene introductie (60-90m) Iets over bits en bytes Iets over programmeurs en programmeertalen

Nadere informatie

HOOfDsTuk 1 Objecten en klassen

HOOfDsTuk 1 Objecten en klassen HOOfDsTuk 1 Belangrijkste concepten in dit hoofdstuk: objecten klassen methodes parameters We springen meteen in het diepe en maken een begin met onze behandeling van objectgeorienteerd programmeren. Om

Nadere informatie

Niet-numerieke data-types

Niet-numerieke data-types Intern wordt een karakter voorgesteld als een rij van acht bits, Niet-numerieke data-types string de letter a 01100001 0110 0001 0x61 97 Bij interpretatie van de inhoud van een byte als een geheel getal,

Nadere informatie

Disclaimer Het bestand dat voor u ligt, is nog in ontwikkeling. Op verzoek is deze versie digitaal gedeeld. Wij willen de lezer er dan ook op wijzen

Disclaimer Het bestand dat voor u ligt, is nog in ontwikkeling. Op verzoek is deze versie digitaal gedeeld. Wij willen de lezer er dan ook op wijzen Disclaimer Het bestand dat voor u ligt, is nog in ontwikkeling. Op verzoek is deze versie digitaal gedeeld. Wij willen de lezer er dan ook op wijzen dat er zowel typografische als inhoudelijke onvolkomenheden

Nadere informatie

SYNTRA-WEST. Cursus OOP. Deel

SYNTRA-WEST. Cursus OOP. Deel SYNTRA-WEST Cursus OOP Deel Syntra-West voorheen VORMINGSINSTITUUT VOOR KMO Syntra-West Doorniksesteenweg 220 8500 Kortrijk Tel. 056/26.02.00 Fax 056/22.81.07 i Inhoudsopgave SYNTRA-WEST... 0 CURSUS OOP...

Nadere informatie

Een gelinkte lijst in C#

Een gelinkte lijst in C# Een gelinkte lijst in C# In deze tutorial ga demonstreren hoe je een gelinkte lijst kan opstellen in C#. We gaan een klasse schrijven, die een gelijkaardige functionaliteit heeft als een ArrayList, namelijk

Nadere informatie

GEDETAILLEERDE INHOUD

GEDETAILLEERDE INHOUD GEDETAILLEERDE INHOUD dankwoord 17 Inleiding 19 Waarom leer je programmeren?... 19 Waarom Python?... 20 Waarom Minecraft?... 20 Wat staat er in dit boek?.... 20 Online bronnen... 22 Ga nu maar op avontuur...

Nadere informatie

VISUALISATIE VAN KROMMEN EN OPPERVLAKKEN. 1. Inleiding

VISUALISATIE VAN KROMMEN EN OPPERVLAKKEN. 1. Inleiding VISUALISATIE VAN KROMMEN EN OPPERVLAKKEN IGNACE VAN DE WOESTNE. Inleiding In diverse wetenschappelijke disciplines maakt men gebruik van functies om fenomenen of processen te beschrijven. Hiervoor biedt

Nadere informatie

return an ; } private I L i s t l i j s t ;

return an ; } private I L i s t l i j s t ; In bovenstaande code werd de binding t e k s t. DataBindings. Add(new Binding ( Text, l i j s t, ) ) ; gebruikt om de eigenschap Text van het object tekst (dwz. tekst.text) te binden aan het object lijst.

Nadere informatie

Programmeermethoden NA

Programmeermethoden NA Programmeermethoden NA Week 7: OOP & Modules Kristian Rietveld http://liacs.leidenuniv.nl/~rietveldkfd/courses/prna/ Bij ons leer je de wereld kennen 1 Tweede programmeeropdracht Uiteraard verwachten we

Nadere informatie

Een inleiding in de Unified Modeling Language 79

Een inleiding in de Unified Modeling Language 79 Een inleiding in de Unified Modeling Language 79 2. Het objectdiagram Soms hebben we behoefte om in de plaats van een klasse een instantie van deze klasse weer te geven. Figuur 3.22. toont als voorbeeld

Nadere informatie

Lessen Java: Reeks pag. 1

Lessen Java: Reeks pag. 1 Lessen Java: Reeks 2 1-3-2016 pag. 1 Primitieve types type grootte waardes byte 8 bits 128, 127 short 16 bits 32768, 32767 int 32 bits 2 31, 2 31 1 long 64 bits 2 63, 2 63 1 type grootte waardes float

Nadere informatie

Zelftest Inleiding Programmeren

Zelftest Inleiding Programmeren Zelftest Inleiding Programmeren Document: n0824test.fm 22/01/2013 ABIS Training & Consulting P.O. Box 220 B-3000 Leuven Belgium TRAINING & CONSULTING INLEIDING BIJ DE ZELFTEST INLEIDING PROGRAMMEREN Deze

Nadere informatie

Design principes.

Design principes. Design principes joost.vennekens@kuleuven.be Motivatie Software projecten mislukken vaker Vaker dan bouwkunde Vaker dan EM Vaker dan Oorzaak? Omgaan met verandering Vereisten Technologie Externe systemen

Nadere informatie

[7] Variabelen en constanten

[7] Variabelen en constanten [7] Variabelen en constanten We gaan een eenvoudig programma schrijven waarbij we reclame maken voor CoderDojo Dendermonde. Volg mee via 07_VariabelenConstanten.py Dit is wat er moet verschijnen op het

Nadere informatie

HOOFDSTUK 3. Imperatief programmeren. 3.1 Stapsgewijs programmeren. 3.2 If Then Else. Module 4 Programmeren

HOOFDSTUK 3. Imperatief programmeren. 3.1 Stapsgewijs programmeren. 3.2 If Then Else. Module 4 Programmeren HOOFDSTUK 3 3.1 Stapsgewijs programmeren De programmeertalen die tot nu toe genoemd zijn, zijn imperatieve of procedurele programmeertalen. is het stapsgewijs in code omschrijven wat een programma moet

Nadere informatie

OEFENINGEN PYTHON REEKS 5

OEFENINGEN PYTHON REEKS 5 Vraag 1: Interpoleren (vervolg) OEFENINGEN PYTHON REEKS 5 Bouw verder op je code van Reeks 3, vraag 4. Voeg vier constanten toe aan je code: X0 = 280, Y0 = 0, Z0 = 50 en SIZE = 8. a) Teken een kubus met

Nadere informatie

Programmeren in C++ Efficiënte zoekfunctie in een boek

Programmeren in C++ Efficiënte zoekfunctie in een boek Examen Software Ontwikkeling I 2e Bachelor Informatica Faculteit Wetenschappen Academiejaar 2010-2011 21 januari, 2011 **BELANGRIJK** 1. Lees eerst de volledige opgave (inclusief de hints/opmerkingen)!

Nadere informatie

Getallen 1 is een computerprogramma voor het aanleren van de basis rekenvaardigheden (getalbegrip).

Getallen 1 is een computerprogramma voor het aanleren van de basis rekenvaardigheden (getalbegrip). Getallen 1 Getallen 1 is een computerprogramma voor het aanleren van de basis rekenvaardigheden (getalbegrip). Doelgroep Rekenen en Wiskunde Getallen 1 Getallen 1 is geschikt voor groep 7 en 8 van de basisschool

Nadere informatie

Constanten. Variabelen. Expressies. Variabelen. Constanten. Voorbeeld : varid.py. een symbolische naam voor een object.

Constanten. Variabelen. Expressies. Variabelen. Constanten. Voorbeeld : varid.py. een symbolische naam voor een object. een symbolische naam voor een object. Variabelen Constanten Variabelen Expressies naam : geeft de plaats in het geheugen aan waarde : de inhoud van het object identifier : een rij van letters en/of cijfers

Nadere informatie

Abstracte klassen & Interfaces

Abstracte klassen & Interfaces Abstracte klassen & Interfaces Overerving public class Vierhoek {... Vierhoek public class Rechthoek extends Vierhoek {... public class Ruit extends Vierhoek {... Rechthoek Ruit Elke rechthoek is een vierhoek.

Nadere informatie

Modelleren en Programmeren

Modelleren en Programmeren Modelleren en Programmeren Jeroen Bransen 13 november 2015 Expressies Functies Ingebouwde functies Variabelenbereik Inleveropgave 1 Terugblik Programma is een lijst van opdrachten Terugblik Programma is

Nadere informatie

Verder zijn er de nodige websites waarbij voorbeelden van objectgeoriënteerd PHP (of Objec Oriented PHP, OO PHP) te vinden zijn.

Verder zijn er de nodige websites waarbij voorbeelden van objectgeoriënteerd PHP (of Objec Oriented PHP, OO PHP) te vinden zijn. Objectgeoriënteerd PHP (versie 5) Kennisvereisten: Ervaring met programmeren in PHP met MySQL Je weet wat een class of klasse is Je weet wat een instantie van een klasse (een object) is Je weet wat een

Nadere informatie

Programmeermethoden. Recursie. week 11: november kosterswa/pm/

Programmeermethoden. Recursie. week 11: november kosterswa/pm/ Programmeermethoden Recursie week 11: 21 25 november 2016 www.liacs.leidenuniv.nl/ kosterswa/pm/ 1 Pointers Derde programmeeropgave 1 Het spel Gomoku programmeren we als volgt: week 1: pointerpracticum,

Nadere informatie

OEFENINGEN PYTHON REEKS 4

OEFENINGEN PYTHON REEKS 4 Vraag 1: Introductie Tekenen OEFENINGEN PYTHON REEKS 4 Vanaf deze les gaan we gebruik maken van het pakket VPython om de objecten te tekenen en weer te geven. Om aan alle functies te kunnen die VPython

Nadere informatie

Controle structuren. Keuze. Herhaling. Het if statement. even1.c : testen of getal even of oneven is. statement1 statement2

Controle structuren. Keuze. Herhaling. Het if statement. even1.c : testen of getal even of oneven is. statement1 statement2 Controle structuren De algemene vorm: 1 bloks door middel van indentatie Keuze Herhaling if expressie :...... In de volgende vorm is het else gedeelte weggelaten: if expressie :... Het if keuze- of conditioneel

Nadere informatie

College Introductie

College Introductie College 2016-2017 Introductie Doaitse Swierstra (Jeroen Bransen) Utrecht University September 13, 2016 Waarom is FP anders? in plaats van opdrachten die na elkaar moeten worden uitgevoerd, definiëren we

Nadere informatie

Opgaven. Python Assessment

Opgaven. Python Assessment Opgaven Python Assessment Nijmegen - Utrecht www.atcomputing.nl Copyright 2015,2016 Versie: 1a Inleiding Met dit assessment kun je controleren of je voldoende parate kennis over Python hebt om te beginnen

Nadere informatie

Project network. Gebaseerd op paragrafen , uit het boek. We simuleren een sociaal netwerk

Project network. Gebaseerd op paragrafen , uit het boek. We simuleren een sociaal netwerk Project network Gebaseerd op paragrafen 10.1-10.7, 11.1-11.6 uit het boek. We simuleren een sociaal netwerk Er zijn twee soorten berichten: tekstberichten en fotoberichten,... voorgesteld door de klassen

Nadere informatie

Practicum Programmeerprincipes

Practicum Programmeerprincipes OPLOSSINGEN REEKS 1 KENNISMAKING MET PICO Evaluatie van expressies Practicum Programmeerprincipes 2009-2010 fvdbergh@vub.ac.be Oefening 1. Oplossing van deze kennismakingsoefening gegeven in de les. Oefening

Nadere informatie

Programmeren. a. 0, 0, 0 b. 0, 0, 27 c. 15, 12, 0 d. 15, 12, 27

Programmeren. a. 0, 0, 0 b. 0, 0, 27 c. 15, 12, 0 d. 15, 12, 27 Programmeren 0. (1 punt.) Stel, een "afhankelijk kind" is een persoon is die jonger is dan 18 jaar, en hooguit 8.000 euro verdient. Welke van de onderstaande expressies definieert een afhankelijk kind?

Nadere informatie

Informatica: C# WPO 9

Informatica: C# WPO 9 Informatica: C# WPO 9 1. Inhoud Functies (functies met return-waarde) 2. Oefeningen Demo 1: Som Demo 2: Min en max of array Demo 3: Retourneer array van randomwaarden A: Absolute waarde A: Afstand A: Aantrekkingskracht

Nadere informatie

Examencursus. wiskunde A. Rekenregels voor vereenvoudigen. Voorbereidende opgaven VWO kan niet korter

Examencursus. wiskunde A. Rekenregels voor vereenvoudigen. Voorbereidende opgaven VWO kan niet korter Voorbereidende opgaven VWO Examencursus wiskunde A Tips: Maak de voorbereidende opgaven voorin in een van de A4-schriften die je gaat gebruiken tijdens de cursus. Als een opdracht niet lukt, werk hem dan

Nadere informatie

Java Les 3 Theorie Herhaal structuren

Java Les 3 Theorie Herhaal structuren Java Les 3 Theorie Herhaal structuren Algemeen Een herhaal structuur een is programmeertechniek waarbij bepaalde Java instructies worden herhaald net zo lang tot een bepaalde voorwaarde is bereikt. Een

Nadere informatie

De sheets zijn gebaseerd op met name hoofdstuk. D. Bell en M. Parr, Java voor studenten, Prentice Hall,

De sheets zijn gebaseerd op met name hoofdstuk. D. Bell en M. Parr, Java voor studenten, Prentice Hall, sheets Programmeren 1 Java college 4, Walter Kosters De sheets zijn gebaseerd op met name hoofdstuk 8 van: D. Bell en M. Parr, Java voor studenten, Prentice Hall, 2002 http://www.liacs.nl/home/kosters/java/

Nadere informatie

Stoomcursus. wiskunde A. Rekenregels voor vereenvoudigen. Voorbereidende opgaven VWO ( ) = = ( ) ( ) ( ) = ( ) ( ) = ( ) = = ( )

Stoomcursus. wiskunde A. Rekenregels voor vereenvoudigen. Voorbereidende opgaven VWO ( ) = = ( ) ( ) ( ) = ( ) ( ) = ( ) = = ( ) Voorbereidende opgaven VWO Stoomcursus wiskunde A Tips: Maak de voorbereidende opgaven voorin in een van de A4-schriften die je gaat gebruiken tijdens de cursus. Als een opdracht niet lukt, werk hem dan

Nadere informatie

Instructies zijn niet alleen visueel, maar ook auditief, met hoogkwalitatief ingesproken geluid (geen computerstem).

Instructies zijn niet alleen visueel, maar ook auditief, met hoogkwalitatief ingesproken geluid (geen computerstem). Getallen 3 Doelgroep Getallen 3 is bedoeld voor leerlingen in klas 3-5 van de havo, klas 3-6 van het vwo en in mbo 3&4. Het programma is bijzonder geschikt voor groepen waarin niveauverschillen bestaan.

Nadere informatie