NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT...

96
FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE Deel 2: Objectoriëntatie in Python Joost Vennekens

Transcript of NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT...

Page 1: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

!

FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN !

CAMPUS DE NAYER

NETWERKEN enOBJECTORIËNTATIE

Deel 2: Objectoriëntatie in Python

Joost Vennekens

Page 2: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE
Page 3: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Inhoudsopgave

1 Objecten en klassen 51.1 Een beetje geschiedenis . . . . . . . . . . . . . . . . . . . . . 51.2 Objecten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.3 Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111.4 Constructoren . . . . . . . . . . . . . . . . . . . . . . . . . . . 141.5 Magische methodes . . . . . . . . . . . . . . . . . . . . . . . . 171.6 Wijzigen van attributen . . . . . . . . . . . . . . . . . . . . . . 181.7 Voorbeelden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

1.7.1 De klasse ’Drank’ . . . . . . . . . . . . . . . . . . . . . 191.7.2 De klasse ’Rechthoek’ . . . . . . . . . . . . . . . . . . 201.7.3 De klasse ’Cirkel’ . . . . . . . . . . . . . . . . . . . . . 21

1.8 Een blik achter de schermen . . . . . . . . . . . . . . . . . . 21

2 Samenwerkende objecten 232.1 Een object als argument . . . . . . . . . . . . . . . . . . . . . 232.2 Een object als resultaat . . . . . . . . . . . . . . . . . . . . . 242.3 Associaties tussen klassen . . . . . . . . . . . . . . . . . . . 252.4 Lijsten en objecten . . . . . . . . . . . . . . . . . . . . . . . . . 27

2.4.1 Objecten met lijsten . . . . . . . . . . . . . . . . . . . . 272.4.2 Lijsten van objecten . . . . . . . . . . . . . . . . . . . . 282.4.3 Objecten met lijsten van objecten . . . . . . . . . . . 29

2.5 Voorbeelden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312.5.1 Punten en veelhoeken . . . . . . . . . . . . . . . . . . 312.5.2 Cocktails en dranken . . . . . . . . . . . . . . . . . . . 33

2.6 Nog wat terminologie . . . . . . . . . . . . . . . . . . . . . . . 35

3 Overerving 373.1 Het overschrijven van methodes . . . . . . . . . . . . . . . . 393.2 Overervingshiërarchieën . . . . . . . . . . . . . . . . . . . . . 423.3 Methodes uit een superklasse oproepen . . . . . . . . . . . 443.4 Voorbeelden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

3.4.1 Vierkanten . . . . . . . . . . . . . . . . . . . . . . . . . 45

4 Varia 474.1 Python als server-side scripting taal . . . . . . . . . . . . . 474.2 Methodes met default Laargumenten . . . . . . . . . . . . . 484.3 Vergelijken van objecten . . . . . . . . . . . . . . . . . . . . . 49

3

Page 4: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

4.4 Nog meer vergelijkingen . . . . . . . . . . . . . . . . . . . . . 524.5 Uitzonderingen . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

4.5.1 Uitzonderingen opwerpen . . . . . . . . . . . . . . . . 554.5.2 Uitzonderingen afhandelen . . . . . . . . . . . . . . . 574.5.3 Herwerking van het voorbeeld . . . . . . . . . . . . . 59

4.6 Statische methodes . . . . . . . . . . . . . . . . . . . . . . . . 604.7 Nog een blik achter de schermen . . . . . . . . . . . . . . . . 63

4.7.1 De volledige klasse ‘Cirkel’ . . . . . . . . . . . . . . . 644.8 Private variabelen . . . . . . . . . . . . . . . . . . . . . . . . . 654.9 Eigenschappen (Properties) . . . . . . . . . . . . . . . . . . . 69

5 Python voor data-analyse 735.1 De Pandas bibliotheek . . . . . . . . . . . . . . . . . . . . . . 745.2 De SciKit Learn Bibliotheek . . . . . . . . . . . . . . . . . . . 755.3 Een voorbeeld analyse . . . . . . . . . . . . . . . . . . . . . . 75

A Practicum opgaves 87A.1 Een eenvoudig datamodel: Auto . . . . . . . . . . . . . . . . 87A.2 Een eenvoudig datamodel: Punt . . . . . . . . . . . . . . . . 88A.3 Een eenvoudig datamodel: Ruimte . . . . . . . . . . . . . . . 89A.4 Visualisatie van een ruimte . . . . . . . . . . . . . . . . . . . 90

A.4.1 Installatie op de server . . . . . . . . . . . . . . . . . . 90A.4.2 Genereren van SVG code . . . . . . . . . . . . . . . . 91

A.5 Overerving: bewegende auto’s . . . . . . . . . . . . . . . . . . 92A.6 Visualisatie van oude toestanden . . . . . . . . . . . . . . . 93A.7 Dynamische visualisatie van een ruimte . . . . . . . . . . . 94A.8 Regio’s in het beeld . . . . . . . . . . . . . . . . . . . . . . . . 95

4

Page 5: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

1Objecten en klassen

Dit hoofdstuk introduceert de basisconcepten van het objectgerichtprogrammeren (OP). Deze manier van programmeren werdoorspronkelijk ontwikkeld om een antwoord te bieden op problemenmet voorgaande programmeerstijlen. We schetsen in dit hoofdstuk kortover welke problemen het juist gaat en hoe OP deze problemenprobeert aan te pakken.In deze cursus wordt gebruik gemaakt van de programmeertaal Python.De concepten van OP die worden aangebracht zijn echter niet beperkttot Python alleen. We vinden ze even goed terug in andereobjectgerichte 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 codeschrijft een team van 10 programmeurs dan op 1 dag? Het is—vooralvoor IT managers—verleidelijk om op deze vragen het antwoord 1000 tegeven. De praktijk wijst echter uit dat echte antwoord veel minder is.De verklaring daarvoor ligt in het feit dat lijnen code niet onafhankelijkzijn 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. Metandere woorden, om te kunnen begrijpen wat de tweede lijn juist doet,moeten we ons bewust zijn van het bestaan van de eerste lijn. Eenprogramma van 200 regels is daarom niet gewoon maar dubbel zocomplex als een programma van 100 regels, maar veel méér: elke lijnvan de 200 regels kan immers potentieel relevant zijn voor elke anderelijn. Er zijn dus 2002 mogelijke interacties tussen lijnen code, dieallemaal relevant kunnen zijn voor de programmeur, ten opzichte van1002 mogelijke interacties in het kortere programma. De complexiteit

5

Page 6: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

van een programma—hoe moeilijk het is om dit programma tebegrijpen—stijgt dus veel sneller dan het aantal lijnen code.Programmeertalen hebben sinds de begindagen van de computer eengrote ontwikkeling doorgemaakt. Deze evoluties kunnen best begrepenworden als een voortdurende zoektocht naar manieren om ditfenomeen tegen te gaan. Globaal gesproken is het de bedoeling omgrote programma’s zoveel mogelijk uiteen te trekken in kleinere stukjes.Als we ons programma van 200 lijnen code kunnen opdelen in tweestukken van elk 100 lijnen én we kunnen dit zodanig doen dat we erzeker van zijn dat geen enkele lijn code uit het ene deel relevant is voorhet andere deel, dan blijven en slechts 2 × 1002 mogelijke interactiesover, wat een pak minder is dan 2002. Maar hoe kunnen we dat nuverwezenlijken?In het begin zocht men vooral zijn heil in het opsplitsen van de grotetaak die een programma moest vervullen in kleinere deeltaken. Ditheeft geleid tot het invoeren van functies. De hoop hierbij was dat meneen 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 dezelfdefunctie relevant zouden zijn voor elkaar, zodat het zou volstaan om eenprogramma op te splitsen in functies die klein genoeg zijn. Als we onze200 lijnen code opsplitsen in 10 functies van 20 lijnen, dan zijn ermaar 10 × 202 mogelijke interacties, wat een grootte-orde minder is dande oorspronkelijke 2002: eureka!Helaas bleek al snel dat dit in de praktijk toch niet zo goed werkte. Dereden hiervoor ligt zelfs nogal voor de hand: als we ons programmagaan opsplitsen in deeltaken, dan moeten we deze deeltaken natuurlijkna elkaar uitvoeren. Maar dat betekent dat de invoer van taak 2natuurlijk de uitvoer van taak 1 zal zijn! En de invoer van taak 20 is deuitvoer van taak 19, wiens invoer de uitvoer van taak 18 was, wiensinvoer de uitvoer was van taak 17, wiens invoer . . . . We zien al snel dateen taak helemaal niet onafhankelijk is van de taken die ervoorkwamen, maar dat ze hier juist heel erg van afhangt. Dit betekent ookdat als we in taak 1 een aanpassing doen, deze aanpassing mogelijkeen 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 eigenlijkalle 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 nietwat we willen.In het begin van de jaren ’90 is men dan op zoek gegaan naar eenalternatief. Merk op dat men hier dus niet gewoon maar op zoek wasnaar een nieuwe programmeertaal, maar naar een nieuw wereldbeeld.Er was nood aan een andere manier van kijken: een programma moestniet langer gezien worden als een verzameling van taken die kondenworden opgesplitst in deeltaken, maar als. . . iets anders? De grotedoorbraak die er dan gekomen is, is dat men beseft heeft dat een oudconcept, bedacht door academici in de jaren ’60, eigenlijk perfect hetantwoord op deze vraag kon geven. Het concept was dat vanobjectgericht programmeren, en het antwoord is simpelweg: beschouween programma niet langer als een verzameling van (deel-)taken, maarals een verzameling van samenwerkende objecten. Hoewel dit antwoord

6

Page 7: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

dus al bestaat sinds de jaren ’60, heeft het tot de jaren ’90 geduurdvoor men eindelijk de bijhorende vraag bedacht heeft, namelijk: hoekunnen we onze programma’s zodanig structureren dat zoveel mogelijkregels code onafhankelijk zijn van elkaar?

1.2 Objecten

Om echt te kunnen begrijpen hoe het objectgerichte wereldbeeld ertoekan leiden dat programma’s beter (dwz. met meer onafhankelijkheden)gestructureerd worden, is het nodig om eerst wat dieper in te gaan opde betekenis die de term object in deze context heeft. We doen dit dooreven 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 specifiekeverhouding door elkaar gemengd moeten worden. Een vodka-orangebestaat bijvoorbeeld voor een derde uit vodka, en voor de rest uitfruitsap. Dit betekent dat om een glas van 25cl te vullen metvodka-orange, er 8,3cl vodka nodig is en 16,7cl fruitsap. En als webijvoorbeeld al 10cl vodka hebben ingeschonken, is er nog 20cl fruitsapnodig. Dergelijke berekeningen worden natuurlijk des te uitdagender,naarmate er meer vodka-oranges geconsumeerd worden. Laten wedaarom een computerprogramma maken dat ons kan helpen.

verhouding = 1.0 / 3.0 # aandeel vodka per eenheid ⤦Ç cocktail

def vodkaVoorCocktail ( cocktai l ) :return verhouding * cocktai l

def fruitsapVoorCocktail ( cocktai l ) :return (1 − verhouding ) * cocktai l

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)8.3333333333333321>>> fruitsapVoorVodka(10)20.000000000000004

In traditionele terminologie hebben we hier gebruik gemaakt van eendatastructuur, waarin we de gegevens die we nodig hebben kunnenbijhouden, en daarbij horen een aantal functies, die op basis van dezedatastructuur de gewenste uitvoer produceren. Natuurlijk is onzedatastructuur hier heel eenvoudig, aangezien hij enkel maar bestaatuit de waarde 1/3. Zelfs voor zo’n eenvoudige datastructuur zijn erechter tal van alternatieven te bedenken. Bijvoorbeeld, we hadden in

7

Page 8: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

plaats van de hoeveelheid vodka per eenheid cocktail, ook deverhouding tussen de hoeveelheid fruitsap en de hoeveelheid vodkakunnen gebruiken.

verhouding = 2.0 # eenheden fruitsap per eenheid vodka

def vodkaVoorFruitsap2 ( fruitsap ) :return fruitsap / verhouding

def fruitsapVoorVodka2 ( vodka ) :return verhouding * vodka

def vodkaVoorCocktail2 ( cocktai l ) :return cocktai l / ( verhouding + 1)

def fruitsapVoorCocktail2 ( cocktai l ) :vodka = vodkaVoorCocktail2 ( cocktai l )return fruitsapVoorVodka2 ( vodka )

Deze functies berekenen natuurlijk net dezelfde resultaten alsvoorheen:

>>> vodkaVoorCocktail2(25)8.3333333333333339>>> 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 ookalle functies veranderen die deze data gebruiken. Wat zou er gebeurenals we dit zouden vergeten? Dan zouden we bijvoorbeeld per ongelukonze oude definitie van de functie fruitsapVoorVodka(vodka) samenkunnen gebruiken met onze nieuwe datavoorstellingverhouding = 2.0. Het is duidelijk dat dit een fout resultaat zalopleveren, en een cocktail die veel te licht is.Historisch weetje: In 1999 verloor de NASA $327.600.000 toen de MarsClimate Orbiter missie mislukte. De oorzaak van het feit dat dezesatelliet opbrandde in de atmosfeer van Mars, zonder ook maar éénzinvol resultaat te produceren, was een software-fout: de ene functiedacht 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 strafgenoeg zijn, de les is dezelfde: het is gevaarlijk om een datastructuurlos te zien van de functies die hem moeten gebruiken. Deze les ismeteen de belangrijkste motivatie voor objectgericht programmeren.Laat ons, voor we verder gaan, eerst eens een meer realistische versievan ons programma bekijken. Het is natuurlijk een beetje belachelijkdat we code geschreven hebben die enkel maar werkt voorvodka-oranges. Met een kleine beetje extra moeite, zouden we codekunnen schrijven die werkt voor alle cocktails. Hiervoor zullen wenatuurlijk onze datastructuur iets ingewikkelder moeten maken, enonze functies daaraan aanpassen.

# ingredienten per eenheid cocktail

8

Page 9: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

vodkaOrange = {’vodka’ : 0.33 , ’fruitsap’ : 0.67}

def ingredientPerCocktail ( cocktail , hoeveelheid ,ingredient ) :

return hoeveelheid * cocktai l [ ingredient ]

def ingredientPerIngredient ( cocktail , hoeveelheid ,gegeven , gezocht ) :

return hoeveelheid * ( cocktai l [ gezocht ]/cocktai l [ gegeven ] )

Als we nu bijvoorbeeld willen weten hoeveel vodka er nodig is voor 20clfruitsap, doen we:

>>> ingredientPerIngredient(vodkaOrange, 20,’fruitsap’, ’vodka’)

9.8507462686567155

We gebruiken nu als datastructuur een woordenboek (dictionary) enhebben twee functies geschreven die gebruik maken van dezedatastructuur. Hier is nu eens een idee: aangezien we toch netbesloten hebben dat de functies die een datastructuur gebruikenonafscheidelijk met deze datastructuur verbonden zijn, waarom stekenwe die functies dan niet gewoon bij in dat woordenboek? In Pythonkan dit immers perfect: we kunnen met een functie alles doen wat wemet bijvoorbeeld een getal of een string kunnen doen. Iets als dit kanbijvoorbeeld perfect:

def dubbel ( x ) :return x * 2

print dubbel (3 )functie = dubbelprint functie (3 )

De toekenning in de voorlaatste lijn geeft de functie dubbel in wezengewoon een tweede naam, die we—zoals de laatste lijn laatzien—daarna eveneens kunnen gebruiken om de functie op te roepen.We kunnen dus even goed onze functie nemen en deze bij in degegevensstructuur van onze cocktail plaatsen.

def ingredientPerCocktail ( cocktail , hoeveelheid ,ingredient ) :

return hoeveelheid * cocktai l [ ingredient ]def ingredientPerIngredient ( cocktail , hoeveelheid ,

gegeven , gezocht ) :verhouding = cocktai l [ gezocht ] / cocktai l [ 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

Page 10: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

woordenboek te kijken. Het berekenen van een hoeveelheid fruitsap perhoeveelheid vodka, kan nu bijvoorbeeld zo:

vodkaOrange [’iPI’ ] ( vodkaOrange , 20, ’fruitsap’ , ’vodka’ )

Hier komen dus zowel de functie die we toepassen als de data waaropwe ze toepassen uit hetzelfde woordenboek.Een groot voordeel van deze aanpak is dat gelijk welke datastructuurwe nu kiezen om onze gegevens in voor te stellen, we altijd de juistefuncties erbij zullen hebben. Veronderstel bijvoorbeeld dat we, inplaats van te zeggen dat een Bloody Mary voor 0.25 uit vodka bestaat envoor 0.75 uit tomatensap (zoals we hierboven deden voor onzevodka-orange), liever zeggen dat hij één deel vodka moet bevatten per 3delen tomatensap.

bloodyMary = {’vodka’ : 1.0 , ’tomatensap’ : 3.0}

Bij deze voorstelling horen nu natuurlijk andere functies dan bij onzevodka-orange, namelijk:

def ingredientPerCocktail2 ( cocktail , hoeveelheid ,ingredient ) :

som = 0for drank in cocktai l :

som += cocktai l [ drank ]return hoeveelheid * cocktai l [ 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’ ] = ingredientPerIngredientbloodyMary [’iPC’ ] = ingredientPerCocktail2

Nu lopen we dus nooit het risico dat we ons zullen vergissen tusseningredientPerCocktail en ingredientPerCocktail2!(Opmerking voor de aandachtige lezer: Moest je bovenstaande codeeffectief proberen uit te voeren, zou je merken dat er nog een fout in ditprogramma zit. Het is instructief om eens na te denken over hoe wedeze fout in het algemeen zouden kunnen vermijden.)Er is nog een tweede voordeel, dat mooi geïllustreerd wordt doorvolgend fragmentje, dat berekent hoeveel vodka we nodig hebben voor25cl Vodka-orange en 25cl Bloody Mary samen.

cocktai ls = [ vodkaOrange , bloodyMary ]vodkaNodig = 0for c in cocktai ls :

vodkaNodig += c [’iPC’ ] ( c , 25, ’vodka’ )

Hoewel dat aan dit fragmentje helemaal niet te zien is, zal er hier vooronze twee verschillende cocktails een verschillende functie wordenopgeroepen. Bovendien zal dit ook telkens de juiste functie zijn! Het feitdat de twee cocktails achter de schermen een verschillende voorstellingvoor hun gegevens gebruiken is nu—vanuit het oogpunt vanbovenstaand fragmentje—niet meer relevant. We hebben hier dus eenmooie onafhankelijkheid tussen verschillende delen van onze codekunnen realiseren.

10

Page 11: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Deze manier van werken is nu de essentie van het objectgeöriënteerdprogrammeren. Met de term object bedoelt men immers niet meer of

object = gegevens + ge-drag

niet minder dan een gegevensstructuur waar alle functies die nodig zijnom deze gegevensstructuur te manipuleren bij inzitten. Samengevat:een gegevensstructuur wéét iets, een functie kán iets, en een objectweet niet alleen iets, maar kan daar ook iets mee.Het voornaamste voordeel van de objectgeöriënteerde manier vanwerken is encapsulatie. Dit betekent dat, zolang we de gegevens die ineen object vervat zitten enkel maar manipuleren door middel van defuncties die in het object zitten, we helemaal niet hoeven te weten hoedit object deze gegevens juist voorstelt. Al deze details zitten immersnetjes ingekapseld in het object. Hierdoor kunnen we ook op elkmoment de gegevensvoorstelling veranderen—bijvoorbeeld van onzevodkaOrange voorstelling naar de bloodyMary—zonder dat we aan derest van ons programma iets hoeven te veranderen.De essentie van objectgeöriënteerd programmeren zit hem dus in defuncties die bij in het object zitten. Aangezien dit concept van “eenfunctie die bij in een object zit” zodanig belangrijk is, heeft men daardan ook maar meteen een woord voor verzonnen: dit noemt men eenmethode.

Een methode is eenfunctie in een object

1.3 Klassen

In de vorige sectie hebben we de essentie van objectgerichtprogrammeren uit de doeken gedaan, zonder daarvoor iets meer overPython te zien dan er in de cursus van het 1e jaar reeds besprokenwerd. Python is echter een objectgerichte programmeertaal, watbetekent dat deze taal een aantal speciale voorzieningen—zogenaamdesyntactische suiker—aanbiedt om programmeren op de objectgerichtemanier aangenamer te maken.Een eerste ding dat al meteen opvalt als we onze cocktailbar verderwillen 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. Aangezienluiheid een grote deugd is voor een programmeur, zouden we lievergewoon één keer zeggen dat alle cocktail-objectjes deze methodesmoeten hebben, in plaats van dit elke keer opnieuw te moeten tikken.

11

Page 12: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Hiervoor bestaat het concept van een klasse: een klasse is eenverzameling van objecten die allemaal dezelfde methodes hebben. Inhet geval van ons voorbeeld, gaan we dus een klasse Cocktailinvoeren, de methodes ingredientPerCocktail eningredientPerIngredient koppelen aan deze klasse, en tot slotzeggen dat bloodyMary, vodkaOrange en ginTonic allemaalCocktails 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 eenbepaalde naam, waarbij we dan alle methodes van deze klasseopsommen. Nadien kunnen we de naam van deze klasse gebruiken alseen functie die een nieuw objectje aanmaakt, dat tot deze bepaaldeklasse behoort. We zeggen dan ook wel dat dit object een instantiatievan deze klasse is. Het netto-effect is dus dat aan elk object dat doormiddel van de uitdrukking Cocktail() wordt aangemaakt, de tweemethodes worden toegevoegd die in de declaratie van deze klasse zijnopgenomen.In de voorgaan sectie hebben we een woordenboek gebruikt om eenobject voor te stellen. In Python zijn echte objecten (dwz. objecten diezijn aangemaakt op basis van een class) een klein beetje verschillendvan woordenboeken. Daar waar we in een woordenboek volgendenotatie gebruiken om aan een nieuw sleutel-waarde paar toe te voegen:

woordenboek [’sleutel’ ] = waarde

doen we dat met een object als volgt:

object . s leute l = waarde

Ook hier is een beetje terminologie voor: we noemen sleutel in ditgeval een attribuut van het object. Nadat we dus bovenstaandeCocktails hebben aangemaakt, kunnen we er als volgt ingrediëntenaan toevoegen:

ginTonic . gin = 0.34ginTonic . tonic = 0.66

Nu heeft het object ginTonic dus twee methodes (namelijk demethodes ingredientPerCocktail en ingredientPerIngredientvan zijn klasse) en twee attributen (gin en tonic). De notatie ommethodes en attributen van een object aan te spreken is trouwens

12

Page 13: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

identiek dezelfde, alleen zijn er natuurlijk ook haakjes en argumentennodig om een methode op te roepen.

De gegevens in een ob-ject heten attributen

ginTonic . ingredientPerCocktail (argumenten )

Zonder de haakjes en argumenten, zouden we de methode nietoproepen, maar krijgen we gewoon deze methode zelf terug, zoals tezien is in de Python interpreter:

>>> ginTonic.ingredientPerCocktail<bound method Cocktail.ingredientPerCocktail

of <__main__.Cocktail instance at 0x50da08>>

Laten we nu deze methode ook eens implementeren. In de vorige sectiehadden we deze functie:

def ingredientPerCocktail ( cocktail , hoeveelheid ,ingredient ) :

return hoeveelheid * cocktai l [ ingredient ]

Nu we van de cocktail een object gemaakt hebben in plaats van eenwoordenboek, moeten we deze functie natuurlijk aanpassen. Het ideeis dat we eigenlijk dit zouden willen doen:

def ingredientPerCocktail ( cocktail , hoeveelheid ,ingredient ) :

return hoeveelheid * cocktai l . ingredient

Maar dit zal helaas niet werken! Deze code zal immers op zoek gaannaar een (niet-bestaand) attribuut ingredient van het objectginTonic. Terwijl we eigenlijk willen dat als we deze functie oproepenmet als laatste argument bv. de string ’gin’, dat dan het attribuutcocktail.gin gezocht zou worden. Gelukkig kent Python speciaalvoor dit probleem een functie getattr(object,naam), waarvan hettweede argument een string moet zijn. Met andere woorden, als wegetattr(ginTonic,’gin’) doen, dan zal het attribuutginTonic.gin worden opgehaald. Hiermee wordt de definitie van onzeklasse dan:

class Cocktail :

def ingredientPerCocktail ( ze l f , hoeveelheid ,ingredient ) :

return hoeveelheid *getattr ( ze l f , ingredient )

def ingredientPerIngredient ( ze l f , hoeveelheid ,gegeven , gezocht ) :

verhouding = getattr ( ze l f , ⤦Ç gezocht ) /getattr ( ze l f , gegeven )

return hoeveelheid * verhouding

We hebben nu ook nog een tweede, kleine wijziging gedaan tov. onzevorige code. We hebben het eerste argument van onze methodeshernoemd naar zelf. In Python is het eerste argument van eenmethode altijd het object waarbij de methode hoort. De conventie is omdit argument altijd deze naam te geven (of self in Engelstalige code).

13

Page 14: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

We zouden verwachten dat we deze methode nu als volgt kunnenHet eerste argumentvan een methode iszelfoproepen:

ginTonic . ingredientPerCocktail (:::::::::ginTonic, 20, ’gin’ )

Dit is echter niet helemaal juist. In werkelijkheid is het namelijk nognet iets eenvoudiger. Het eerste argument van deze methode-oproep zalimmers toch altijd hetzelfde zijn als het object waarin de methode zelfzit. Meer nog, het feit dat de methode gegevens manipuleert die in haareigen object zitten is net de essentie van objectgericht programmeren!Daarom zal Python dit eerste argument impliciet achter de schermenzelf doorgeven, zonder dat wij dit zelf hoeven te doen

ginTonic . ingredientPerCocktail (20 , ’gin’ )zelf wordt implicietmeegegeven Hoewel er dus in de definitie van deze methode drie argumenten waren,

moeten we er bij de oproep van deze methode slechts twee zelf explicietdoorgeven. Het ontbrekende argument is het object waarop de methodewordt opgeroepen (ginTonic, in dit geval), dat als impliciet eersteargument wordt doorgegeven.

1.4 Constructoren

We hebben tot dusver de ingrediënten van onze cocktails gewoonvoorgesteld door een string (’gin’, ’vodka’,. . . ). Als we in ons programmameer moeten weten over een drank dan enkel maar zijn naam, dan zaldeze voorstelling ontoereikend zijn en hebben we nood aan eengegevensstructuur waarin we al de relevante informatie over een drankkunnen bijhouden. Hiervoor kunnen we natuurlijk ook weer objectengaan gebruiken. We hebben dan een klasse nodig, die we hier Drankgaan noemen. Het is vaak nuttig om, vooraleer we effectief Python codegaan 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 ditin de vorm van een info-kaartje, dat er zo uitziet:

Klasse DrankAttr. naam : string

alcoholpercentage : Rprijs : R

Meth.

Op dit ogenblik, zijn we dus niet van plan om methodes te voorzien inonze Drank-objecten. Dit betekent dat we eigenlijk evengoed eenwoordenboek zouden kunnen gebruiken om deze gegevens in voor testellen, als een object. Er zijn twee goede redenen om toch voor eenobject te kiezen. Ten eerste is het verwarrend om in hetzelfdeprogramma sommige gegevens voor te stellen door een object ensommige door een woordenboek; in een objectgericht programmakiezen we dus best zoveel mogelijk voor objecten. Ten tweede zou hetaltijd nog kunnen dat we later alsnog methodes blijken nodig tehebben. Als we van in het begin voor objecten gekozen hebben, is diteen veel eenvoudigere operatie, dan wanneer we een woordenboekzouden moeten gaan omvormen tot een object.

14

Page 15: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Wegens het gebrek aan methodes, krijgen we dus een klasse definitiedie leeg is.

class Drank:pass

Deze lege klasse dient vooral om ons nu alvast een plaats te geven waarwe later—als we het programma verder gaan uitbreiden—eventuelemethodes van de klasse Drank kunnen gaan toevoegen. Het feit dat dedefinitie van deze klasse leeg is, belet ons natuurlijk niet om objectenhiervan aan te maken.

gin = Drank ( )gin .naam = ’gin’gin . alcoholpercentage = 0.15gin . pr i j s = 12

vodka = Drank ( )vodka .naam = ’vodka’vodka . alcoholprecentage = 0.40vodka . pr i j s = 20

We zien hier opnieuw een hoop tikwerk opduiken, met bovendien hetrisico op moeilijk te vinden fouten. Zo staat er in het voorbeeldhierboven een tikfout, die op dit moment nog ongemerkt voorbij zalgaan, maar ongetwijfeld later voor problemen gaat zal zorgen als we hetalcoholpercentage van onze dranken willen raadplegen. Beter is hetdus om een methode te definiëren die de verschillende attributen vaneen Drank invult.

class Drank:

def vulAttributenIn ( ze l f , naam, perc , pr i j s ) :z e l f .naam = naamze l f . alcoholpercentage = percz e l f . pr i j s = pr 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 hieropgaan oproepen. Dit zal bovendien in heel ons programmawaarschijnlijk altijd zo zijn. Python laat ons toe om ons programmanog wat compacter te maken door deze twee stappen—het aanmakenvan een object en het initializeren ervan—in één instructie uit te voeren.Het enige dat we hiervoor moeten doen, is onze initializatie-methodeeen speciale naam geven: __init__.De naam van deze functie bestaat dus uit het woordje init (alsafkorting van initializatie), voorafgegaan en gevolgd door telkens tweeunderscores _. De reden voor de underscores is dat Python deze

15

Page 16: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

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 dingenmee zal doen. Deze notatie oogt een beetje vreemd, maar dat is eigenlijkprecies de bedoeling: de underscores dienen als een waarschuwingvoor mensen die de code zouden lezen zonder de speciale functie tekennen. Als ze de underscores zien, dan weten ze dat deze functie ietsspeciaals doet, en dat ze best eens de Python documentatie eropzouden naslaan om te weten te komen wat dit speciale juist is. Dezespeciale functies worden ook wel magische functies genoemd, omdat zeeen effect kunnen hebben op het gedrag van een programma in delendie er op het eerste zicht helemaal niets mee te maken hebben.

class Drank:

def __ in i t__ ( ze l f , naam, perc , pr i j s ) :z e l f .naam = naamze l f . alcoholpercentage = percz e l f . pr i j s = pr 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 denaam __init__, dan zal Python dus voor ons een functie definiërenmet 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 aantalargumenten van de __init__ methode.

Wat deze functie zal doen is:

1. Eerst maakt de functie een nieuw object aan van de klasse, enkoppelt hieraan alle methodes die bij de klasse horen;

2. Daarna roept de functie de initializatie-methode __init__ vandeze klasse op op het object dat ze net heeft aangemaakt, met alsargumenten de argumenten die ze zelf gekregen heeft;

3. Tot slot geeft deze functie het nieuw aangemaakte engeïnitializeerde object terug als haar resultaat.

Deze functie wordt de constructor van de klasse genoemd.(Opmerking terzijde: Sommige objectgerichte programmeertalen biedende mogelijkheid aan om per klasse meerdere constructoren te voorzien,die bijvoorbeeld objecten initializeren op basis van verschillendeparameters. In Python is dit niet mogelijk, aangezien het gedrag van deconstructor volledig bepaald wordt door hetgeen er in de__init__-methode staat, en er maar één methode met deze naam inelke klasse kan zijn. Wel is het mogelijk om sommige argumenten vandeze methode een default waarde mee te geven, waarmee een deel vande functionaliteit waarvoor het hebben van meerdere constructors inandere programmeertalen gebruikt wordt, toch gerealizeerd kanworden.)

16

Page 17: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

1.5 Magische methodes

Naast __init__ zijn er in Python nog een hele hoop andere specialefuncties en methodes. Een greep uit het gamma.Python biedt een aantal functies aan die objecten van één datatypeomzetten 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—eencorresponderende magische methode op te roepen, die dezelfde naamheeft maar dan aangevuld met de nodige underscores. Door in onzeeigen klassen deze methodes te implementeren, kunnen we dusbepalen hoe onze eigen objecten zullen worden omgezet naar anderedatatypes:

class Drank:

def __ in i t__ ( ze l f , naam, perc , pr i j s ) :z e l f .naam = naamze l f . alcoholpercentage = percz e l f . pr i j s = pr i j s

def __str__ ( z e l f ) :return z e l f .naam + ’ (’ + ⤦

Ç str ( z e l f . alcoholpercentage ) + ’%)’

def __ f loat__ ( 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)0.40000000000000002>>> 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 eenmethode __float__ te hebben. Er zijn immers weinig situaties tebedenken waarin we op het idee zouden komen om een drank als een

17

Page 18: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

kommagetal te gebruiken. De methode __str__ lijkt daarentegen welzinvol. Telkens als we een drank zouden willen afprinten, moeten wedeze immers transformeren naar een string. Sterker nog: als we eendrank meegeven aan een print opdracht, dan zal Python achter deschermen deze conversie uitvoeren.Normaalgezien krijgen we iets als dit te zien, als we een Drank-objectproberen 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 klasseDrank, ziet het resultaat er anders uit:

>>> print Drank(’pils’, 0.4, 2)pils (0.4%)

Van de verschillende conversie-methodes die hierboven werdenaangehaald, 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 winnenover 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” eenstring opgeven, en deze zal dan afgebeeld worden als gebruikers omhulp vragen over deze klasse. Deze string wordt per conventie tussendriedubbele aanhalingstekens geplaatst, ook als hij op één lijn past, enwordt 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 zijninitializatie. Het is natuurlijk mogelijk om achteraf de waarde van deze

18

Page 19: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

attributen nog te gaan wijzigen. Als we bijvoorbeeld plots 2,5e kortingkrijgen op gin, dan kan dit als volgt verwerkt worden:

class Drank:

def __ in i t__ ( ze l f , naam, perc , pr i j s ) :z e l f .naam = naamze l f . alcoholpercentage = percz e l f . pr i j s = pr i j s

. . . # Nog wat andere methodes

gin = Drank(’gin’ , 0.35 , 10)gin . pr i j s = gin . pr i j s − 2.5

Hierbij wordt de waarde van attribuut prijs van het object gin dusaangepast van buiten deze klasse. Een alternatief is dat we de klasseDrank een extra methode geven, waarmee we deze aanpassing binnende klasse doen.

class Drank:

def __ in i t__ ( ze l f , naam, perc , pr i j s ) :z e l f .naam = naamze l f . alcoholpercentage = percz e l f . pr i j s = pr i j s

. . . # Nog wat andere methodes

def kri jgKorting ( ze l f , bedrag ) :z e l f . pr i j s = z e l f . pr i j s − bedrag

gin = Drank(’gin’ , 0.35 , 10)gin . kri jgKorting (2 .5 )

Aangezien het de bedoeling van objectgericht programmeren is datklassen zoveel mogelijk hun eigen gegevens inkapselen, is de tweedeoptie vaak de beste.

1.7 Voorbeelden

Tot slot van dit hoofdstuk, nog een aantal voorbeelden van volledigeklassen.

1.7.1 De klasse ‘Drank’Het info-kaartje van onze klasse Drank is intussen dit geworden:

Klasse DrankAttr. naam : string

alcoholpercentage : Rprijs : R

Meth. __init__(zelf, naam, perc, prijs)__str__(zelf)

19

Page 20: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

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 __ in i t__ ( ze l f , naam, perc , pr i j s ) :z e l f .naam = naamze l f . alcoholpercentage = percz e l f . pr i j s = pr i j s

def __str__ ( z e l f ) :return z e l f .naam + ’ (’ + ⤦

Ç str ( z e l f . alcoholpercentage ) + ’%)’

1.7.2 De klasse ’Rechthoek’

Volgende klasse laat ons toe om rechthoeken voor te stellen, af teprinten, en hun oppervlakte en omtrek te berekenen.

Klasse RechthoekAttr. hoogte : R

breedte : RMeth. oppervlakte(zelf) : R

omtrek(zelf) : R

class Rechthoek :

""" Objecten van deze klasse stellen een ⤦Ç meetkundige rechthoek voor met een hoogte en ⤦Ç breedte. """

def __ in i t__ ( ze l f , b , h) :z e l f . breedte = bze 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

Page 21: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Deze klasse gebruiken we dan bijvoorbeeld zo:

>>> r = Rechthoek(2,3)>>> print rRechthoek van 2x3>>> r.oppervlakte()6>>> r.omtrek()10

1.7.3 De klasse ’Cirkel’

Andere meetkundige vormen kunnen natuurlijk op een gelijkaardigemanier worden voorgesteld. Voor een cirkel hebben we het getal πnodig, dat we kunnen aanspreken als math.pi, nadat we eerst dezemodule math geïmporteerd hebben.

Klasse CirkelAttr. straal : RMeth. oppervlakte(zelf) : R

omtrek(zelf) : R

class Cirkel :

def __ in i t__ ( ze 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 mathreturn z e l f . straal * 2 * math. pi

def oppervlakte ( z e l f ) :import mathreturn ( z e l f . straal ** 2) * math. pi

1.8* Een blik achter de schermen

Veel programmeertalen hebben de filosofie dat ze programmeurs tegenzichzelf of tegen hun collega’s moeten beschermen. Python heeft dezefilosofie niet. Dit is natuurlijk slecht nieuws voor programmeurs diedeze bescherming nodig hebben, maar goed nieuws voor de anderen.We hebben eerder gezien dat er een grote gelijkenis bestaat tussenattributen van een object en sleutel-waarde paren in een woordenboek.Dit hoeft geen verwondering te wekken, want achter de schermenworden de attributen van een object gewoon in een woordenboekgestoken. Dit magische woordenboek heeft de naam __dict__ en iszelf een attribuut van het object.

21

Page 22: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

>>> 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 klassewordt bijgehouden in het attribuut __class__.

>>> d.__class__<class __main__.Drank at 0x505b40>

Op basis van de inleiding van dit hoofdstuk, had je misschien verwachtdat de methodes van de klasse Drank bij in het woordenboekd.__dict__ zouden zitten. Dit is echter niet het geval. De redenhiervoor is gewoon zuinigheid: aangezien alle objecten van de klassetoch dezelfde methodes delen, hoeven ze die niet allemaal afzonderlijkbij te houden. Het is voldoende als gewoon de klasse d.__class__ demethodes bijhoudt. Dit doet ze in haar eigen __dict__, waar weo.a. ook de docstring van de klasse terugvinden.

>>> d.__class__.__dict__{’__module__’: ’__main__’, ’__doc__’: ’Objectenvan deze klasse stellen een drank voor met:\n\n- een naam,\n - een alcoholpercentage, \n - eenprijs (in euro per liter). \n ’,’__str__’: <function __str__ at 0x50a230>,’__init__’: <function __init__ at 0x50a630>}

22

Page 23: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

2Samenwerkendeobjecten

In het vorige hoofdstuk hebben we enkel maar naar geïsoleerde klassenen objecten gekeken. In een echt programma zullen verschillendeklassen normaalgezien moeten samenwerken. Er zijn drie manierenwaarop één klasse een andere kan gebruiken:

• Een klasse kan een methode hebben, waarin een object van eenandere klasse als argument voorkomt;

• Een klasse kan een methode hebben, die een object van eenandere klasse teruggeeft;

• Een klasse kan een attribuut hebben waarin ze een object van eenandere klasse bijhoudt.

Er is duidelijk verschil tussen de eerste twee mogelijkheden en dederde, namelijk de duurtijd van de samenwerking. In de eerste tweegevallen is dit een tijdelijke samenwerking, waarbij de ene klasse deandere enkel maar nodig heeft tijdens één enkel methode oproep. Hetlaatste geval, daarentegen, beschrijft een duurzame binding tussentwee objecten, die mogelijk hun hele levensduur lang meegaat. Dit

Associatie = contractvan onbepaalde duur

fenomeen wordt ook wel een associatie tussen de twee klassengenoemd.

2.1 Een object als argument

In Secties 1.7.2 en 1.7.3 introduceerden we een klasse Cirkel en eenklasse Rechthoek. Laat ons nu de klasse Cirkel uitbreiden met eenmethode die kan nagaan of de cirkel in een gegeven Rechthoek past.

class Cirkel :

# ... de klasse zoals voorheen

def pastIn ( ze 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

Page 24: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

>>> cirkel = Cirkel(5)>>> rh = Rechthoek(6,7)>>> cirkel.pastIn(rh)True

Een object dat als argument wordt meegegeven, gedraagt zich zoals eenwoordenboek of een lijst, in die zin dat als er in de methode wijzigingengebeuren aan een attribuut van dit object, deze wijzigingen ook buitende methode zichtbaar zullen zijn. Laat ons dit illustreren met eenmethode die een rechthoek inkrimpt totdat hij in een cirkel past.

class Cirkel :

# ... de klasse zoals voorheen

def maakIngesloten ( ze l f , rechthoek ) :i f rechthoek . hoogte > z e l f . straal :

rechthoek . hoogte = z e l f . straali f rechthoek . breedte > z e l f . straal :

rechthoek . breedte = z e l f . straal

>>> cirkel = Cirkel(5)>>> rh = Rechthoek(3,7)>>> print rhRechthoek van 3x7>>> cirkel.maakIngesloten(rh)>>> print rhRechthoek van 3x5

2.2 Een object als resultaat

Als we in bovenstaand voorbeeld nog andere plannen hebben met onzeoriginele rechthoek rh, dan kan het lastig zijn dat we deze nu net “inplace” veranderd hebben. Een alternatief is om in onze methode eennieuwe Rechthoek aan te maken, en deze terug te geven als resultaat.De oorspronkelijke rechthoek kan dan onveranderd blijven. Vergelijkonderstaande code met de versie uit de vorige sectie:

class Cirkel :

# ... de klasse zoals voorheen

def maakIngesloten ( ze l f , rechthoek ) :i f rechthoek . breedte > z e l f . straal :

nieuweBreedte = z e l f . straalelse :

nieuweBreedte = rechthoek . breedtei f rechthoek . hoogte > z e l f . straal :

nieuweHoogte = z e l f . straalelse :

nieuweHoogte = rechthoek . hoogtereturn Rechthoek ( nieuweBreedte , nieuweHoogte )

24

Page 25: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Het gebruik van deze methode moet dan natuurlijk ook anders:

>>> cirkel = Cirkel(5)>>> rh = Rechthoek(3,7)>>> rh2 = cirkel.maakIngesloten(rh)>>> print rhRechthoek van 3x7>>> print rh2Rechthoek van 3x5

Welk van beide stijlen te verkiezen valt, is vaak een kwestie vanpersoonlijke smaak. In de Python gemeenschap, is men vaak nogalgewonnen voor een functionele stijl van programmeren, waarin functiesen methodes zich gedragen zoals wiskundige functies. Dit betekent dat,net zoals een wiskundige functie f , een methode wel een resultaaty = f(x) zal berekenen, maar geen veranderingen zal aanbrengen aan x.Aanhangers van deze programmeerstijl zouden dus waarschijnlijk onzetweede variant van de methode maakIngesloten verkiezen. Deachterliggende motivatie is dezelfde als altijd: ze geloven dat er op dezemanier gemakkelijker onafhankelijkheden tussen verschillendestukken code gerealiseerd kunnen worden, wat uiteindelijk aanleidingzou moeten geven tot programma’s die gemakkelijker te ontwikkelen ente onderhouden zijn.

2.3 Associaties tussen klassen

Een associatie tussen twee klassen betekent dat elk object van de eneklasse een attribuut heeft waarin een object van de andere klassewordt bijgehouden. Om dit te illustreren verlaten we even onzerechthoeken en cirkels voor een belangrijkere toepassing, namelijk hetbijhouden van onze drankvoorraad. We zagen in het vorige hoofdstuk(Sectie 1.7.1) al een klasse Drank, waarmee we kunnen bijhoudenwelke dranken we in voorraad hebben. Dit breiden we nu zodanig uit,dat we ook kunnen bijhouden hoeveel er van een bepaalde drank invoorraad is. Hiervoor introduceren we volgende klasse:

Klasse FlesAttr. drank : Drank

inhoud (in cl) : NMeth. haalUit(zelf, hoeveelheid)

voegToe(zelf, hoeveelheid)waarde(zelf) : R

Elk object van de klasse Fles heeft dus een attribuut waarin het eenobject van de klasse Drank gaat bijhouden. Er is dus, maw., eenassociatie tussen Fles en Drank.Vaak is het inzichtelijker om in plaats van bovenstaand info-kaartje eenzogenaamd klassendiagramma te tekenen. Dit is één van verschillendeHierop worden associates aanduid met een pijl tussen de twee klassenin kwestie. Deze pijl vertrekt bij de klasse die het attribuut heeftwaarmee deze associatie wordt voorgesteld, en dit attribuut wordt danniet meer opgenomen in diens lijst met attributen. Eventueel kan de

25

Page 26: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

naam van het attribuut wel nog vermeld worden als label bij de pijl.Om het geheel overzichtelijk te houden, wordt er vaak ook voor gekozenom de methodes of zelfs de attributen van een klasse niet te vermelden.

Flesinhoud : N

drankDranknaam : stringalcoholpercentage : Rprijs : R

class Fles :

def __ in i t__ ( ze l f , drank , inhoud ) :z e l f . drank = drankze l f . inhoud = inhoud

def haalUit ( ze l f , hoeveelheid ) :z e l f . inhoud = ze l f . inhoud − hoeveelheid

def voegToe ( ze l f , hoeveelheid ) :z e l f . inhoud = ze l f . inhoud + hoeveelheid

def waarde ( z e l f ) :pri jsPerCl = z e l f . drank . pr i j s / 100.0return z e l f . inhoud * pri jsPerCl

De interessantste methode is hier de laatste, waarin een Fles objectzijn eigen inhoud moet combineren met de prijs van zijn drank omzijn eigen waarde te bepalen. De deling door 100 is nodig omdat deklasse Drank zijn prijs bijhoudt in e/l, terwijl de inhoud van een Flesin cl wordt bijgehouden. Op zich kan dit riskant zijn (denk aan deontplofte Mars Observator): als we later zouden besluiten om de prijsvan een Drank ook in e/cl te zetten, moeten we eraan denken om dedeling door 100 weg te halen, of anders krijgen we natuurlijk fouteresultaten. Het is in dit geval waarschijnlijk veiliger om voor eenandere strategie te kiezen, waarbij het doen van berekeningen met hetattribuut drank.prijs zoveel mogelijk in de klasse Drank gebeurt.

class Fles :

. . .

def waarde ( z e l f ) :return z e l f . drank . pri jsPerCl ( ) * z e l f . inhoud

class Drank:

. . .

def pri jsPerCl ( z e l f ) :return z e l f . pr i j s / 100

26

Page 27: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

2.4 Lijsten en objecten

Ook in onze eigen objecten kunnen we vaak op nuttige wijze gebruikmaken van de functionaliteit van Pythons ingebouwde lijsten. In decursus van het eerste jaar, heb je gezien hoe je met deze lijsten moetomgaan. 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...1234>>> print lijst[1]2>>> for i in range(len(lijst)):... print i, "->", lijst[i]...0 -> 11 -> 22 -> 33 -> 4

Herinner je bij de uitvoer van het laatste commando ook dat de indexvan een lijst altijd begint te tellen vanaf 0; het element dat bij index 1hoort, is dus niet het eerste, maar wel het tweede element van de lijst.

2.4.1 Objecten met lijsten

Een lijst kan—net zoals bijvoorbeeld een getal, een string of eenobject—gebruikt worden als een attribuut van een object. Als eenobject zo’n attribuut heeft, zal het vaak methodes aanbieden waarmeeelementen 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 getallenkan worden bijgehouden om hiervan het gemiddelde te berekenen.

class GetallenRij :

def __ in i t__ ( z e l f ) :z e l f . r i j = [ ]

def voegToe ( ze l f , getal ) :z e l f . r i j . append ( getal )

27

Page 28: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

def __str__ ( z e l f ) :return str ( z e l f . r i j )

def som( z e l f ) :som = 0for getal in z e l f . r i j :

som += getalreturn som

def gemiddelde ( z e l f ) :som = ze 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()5.333333333333333

2.4.2 Lijsten van objectenNaast waardes van een primitief type, zoals getallen of strings, kunnenook objecten in een lijst gestoken worden. Zo zal onderstaande codeeen lijst van twee objecten maken, en daar dan nadien nog een geheelgetal en een derde object aan toevoegen.

gin = Drank ( . . . )p i l s = Drank ( . . . )wijn = Drank ( . . . )f1 = Fles ( gin ,50)f2 = Fles ( pi ls ,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. Hetbelangrijkste punt hiervan, is dat lijst[0] dus eigenlijk gewoon eenandere naam is voor hetzelfde object dat ook al de naam f1 heeft. Heteffect hiervan zien we bijvoorbeeld in volgende interactie:

28

Page 29: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

lijst

0 1 2 3

4

Flesdrank gininhoud 50

Flesdrank pilsinhoud 25

Flesdrank wijninhoud 75

f1 f2 f3

Figuur 2.1: Een grafische voorstelling van het resultaat van decode lijst = [f1,f2,4,f3].

>>> print f1.inhoud50>>> lijst[0].inhoud = 25>>> print f1.inhoud25

2.4.3 Objecten met lijsten van objecten

Als we objecten in een lijst kunnen steken, is het natuurlijk ookmogelijk om zo’n lijst van objecten te gebruiken als een attribuut vaneen andere object. Op deze manier kunnen we, met andere woorden,een associatie tot stand brengen van een object van één klasse met eenverzameling van objecten van een andere klasse. Hiervan kunnen webijvoorbeeld gebruik maken om gegevens over een DrankVoorraad bijte houden, door middel van een lijst van flessen waaruit deze voorraadbestaat.

Klasse DrankVoorraadAttr. flessen : lijst<Fles>Meth. waarde(zelf) : R

Met de notatie lijst<Fles> bedoelen we een lijst met daarin eenaantal Fles objecten. In een klassendiagramma, kunnen we zo’n lijstaanduiden met een sterretje, zoals te zien in Figuur 2.2.Veel van het gedrag dat een object van een klasse zoalsDrankVoorraad zal aanbieden, wordt typisch gerealizeerd door middelvan een iteratie over de Fles-objecten die in zijn lijst zitten.Bijvoorbeeld de methode om de volledige waarde van een drankkast teberekenen, gegeven de waarde van de individuele flessen, valt op dezemanier te implementeren.

class DrankVoorraad :

29

Page 30: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

DrankVoorraad

waarde(zelf) : R

Flesinhoud : Nwaarde(zelf) : RvoegToe(zelf, hoeveelheid)haalUit(zelf, hoeveelheid)

flessen

drankDranknaam : stringalcoholpercentage : Rprijs : R

Figuur 2.2: Een klassendiagramma waarbij een drankvoorraaduit een verzameling flessen bestaat.

def __ in i t__ ( z e l f ) :z e l f . f lessen = [ ]

def voegToe ( ze l f , f l e s ) :z e l f . f lessen .append ( f l e s )

def waarde ( z e l f ) :resultaat = 0for f in z e l f . f lessen :

resultaat += f .waarde ( )return resultaat

Laten we even stilstaan bij het resultaat dat deze methode berekent.Als de drankvoorraad n flessen {f1, . . . , fn} bevat, waarbij Vi de inhoudis van fles i en pi de prijs van de drank di die in fles fi zit, dan berekentdeze methode volgende som w:

w = ∑1≤i≤n

Vi ⋅ pi.

De methode waarde uit de klasse DrankVoorraad berekent heel dezesom, gebruikmakend van de gelijknamige methode uit de klasse Fles,die één term ervan berekent. Sommige getalletjes leggen dus een heleweg af, voordat ze uiteindelijk in deze som belanden:

Drank Fles DrankVoorraad

d1p1 // f1

V1⋅p1

**⋮ ⋮ ∑i Vi ⋅ pi

dnpn // fn

Vn⋅pn

44

30

Page 31: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

GetallenRijrij : lijst<Z>som(zelf) : Zgemiddelde(zelf) : R

Veelhoek

omtrek(zelf) : RvoegToe(zelf,punt)

Puntx : Ry : RafstandNaar(zelf,punt) : R

hoekpunten ∗

Figuur 2.3: Klassendiagramma van het voorbeeld in Sectie 2.5.

2.5 Voorbeelden

2.5.1 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 vanhoekpunten.

• Bij het berekenen van de omtrek van een Veelhoek, maken wegebruik van de klasse GetallenRij, die we eerder in dithoofdstuk (Sectie 2.4.1) gebruikt hebben.

Het bijhorende klassendiagramma is te zien in Figuur 2.3. Er is duseen associatie tussen een Veelhoek en een lijst van Punten. De klasseGetallenRij wordt enkel maar binnenin één bepaalde methodegebruikt, dus hiermee is er geen associatie.Voor de volledigheid herhalen we eerst de definitie van de klasseGetallenRij nog eens.

class GetallenRij :

def __ in i t__ ( z e l f ) :z e l f . r i j = [ ]

def voegToe ( ze 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 = 0for getal in z e l f . r i j :

31

Page 32: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

som += getalreturn som

def gemiddelde ( z e l f ) :som = ze 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 Euclidischeafstandsberekening.

class Punt :

def __ in i t__ ( ze l f , x , y ) :z e l f . x = xz e l f . y = y

def afstandNaar ( ze l f , ander ) :import math # om vierkantswortels te nemenreturn 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 __ in i t__ ( ze l f , punten ) :z e l f . hoekpunten = punten

def voegToe ( ze l f , punt ) :z e l f . hoekpunten .append ( punt )

def l i jstVanZi jdes ( z e l f ) :r i j = GetallenRij ( )for i in range ( len ( z e l f . hoekpunten ) ) :

van = ze 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 . l i jstVanZi jdes ( ) .som( )

Hiermee kunnen we nu als volgt de omtrekt berekenen van de veelhoekin Figuur 2.4.

a = Punt (2 ,2)

32

Page 33: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

1

1√2

2a

b c

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 ( )

2.5.2 Cocktails en dranken

We herschrijven nu de klasse Cocktail om gebruik te maken van onzeklasse Drank, op de manier getoond in onderstaandklassendiagramma.

Cocktailnaam : String

ingrediënten

Ingredientpercentage : R

drank* Drank

naam : stringalcoholpercentage : Rprijs : R

De klasse Drank is nog steeds dezelfde als in Sectie 1.7.1. De klasseIngredient en Cocktail zijn nu als volgt:

class Ingredient :

""" Een klasse om de ingredienten van een Cocktail ⤦Ç voor te stellenElk ingredient bestaat uit:

- een Drank- het percentage van de cocktail dat uit

deze drank bestaat

33

Page 34: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

"""

def __ in i t__ ( ze l f , drank , percentage ) :z e l f . drank = drankze l f . percentage = percentage

class Cocktail :

""" Een cocktail met een:- naam- een lijst van Ingredient objecten, die de ⤦

Ç samenstelling aangeven"""

def __ in i t__ ( ze l f , naam, ingredienten ) :z e l f .naam = naamze l f . ingredienten = ingredienten

def ingredientPerCocktail ( ze l f , drank , hoeveelheid ) :for ingr in z e l f . ingredienten :

i f ingr . drank == drank :return hoeveelheid * ingr . percentage

def ingredientPerIngredient ( ze l f , hoeveelheid ,gegeven , gezocht ) :

for ingr in z e l f . ingredienten :i f ingr . drank == gegeven :

gegevenIngr = ingri f ingr . drank == gezocht :

gezochtIngr = ingrverhouding = gezochtIngr . percentage / ⤦

Ç gegevenIngr . percentagereturn 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 ( ze 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

Page 35: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

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 tra fs te = bloodyMary

else :s tra fs te = ginTonic

print "Doe mij maar een " + str ( s t ra fs te )

2.6 Nog wat terminologie

In dit hoofdstuk zijn we verschillende soorten van verbandentegengekomen die objecten van verschillende klassen met elkaarkunnen hebben. Om hierover vlot te kunnen communiceren, bestaat erwat specifieke terminologie.

• We zijn de term associatie al tegengekomen. Dit is de meestalgemene term: een associatie tussen twee klassen betekentsimpelweg dat objecten van de ene klasse op de een of anderemanier gebruik maken van objecten van de andere klassen. Zoalswe ook reeds gezien hebben, wordt een associatie in eenklassendiagramma getekend met een pijl.

• Een aggregatie is een speciaal geval van een associatie, waarbij ereen deel-geheel relatie bestaat tussen de twee klassen. We hebbenook hiervan al voorbeelden gezien, zoals bijvoorbeeld de associatietussen Veelhoek (geheel) en Punt (deel), aangezien een veelhoekbestaat uit een aantal hoekpunten. Ook de associatie tussenDrankVoorraad en Fles is een voorbeeld van een aggregatie. Ineen klassendiagramma wordt dit getekend door een pijl die begintmet een diamand: ◇→. Het voorbeeld van veelhoeken en puntenkunnen we dus eigenlijk beter tekenen zoals in Figuur 2.5.

• Een compositie is een speciaal geval van een aggregatie, waarbijde delen worden aangemaakt door het geheel, en de delen nietkunnen bestaan zonder het geheel. In een klassendiagrammawordt dit getekend door een pijl die begint met een volle diamand:⧫→.

35

Page 36: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Veelhoek

omtrek(zelf) : RvoegToe(zelf,punt)

Puntx : Ry : RafstandNaar(zelf,punt) : R

hoekpunten ∗

Figuur 2.5: Alternatieve versie van het klassendiagram in Figuur2.3.

36

Page 37: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

3Overerving

Een belangrijk streefdoel van objectgericht programmeren isherbruikbaarheid van programma-code. In het vorig hoofdstuk hebbenwe bijvoorbeeld een klasse Veelhoek gedefinieerd. Moesten we nu tienjaar later een programma aan het schrijven zijn waarin we opeens noodblijken te hebben aan een klasse waarmee we een veelhoek kunnenvoorstellen, dan zouden we graag hebben dat we gewoon deze klasseuit het vorige hoofdstuk terug kunnen opzoeken en deze zonder verderemoeite herbruiken in ons nieuwe programma.

Overerving dient om be-staande klassen uit tebreidenIn de praktijk blijkt dit echter vaak toch niet zo eenvoudig te zijn. Een

veel voorkomend fenomeen is dat er in het nieuwe programma net ietsmeer functionaliteit vereist is dan in het oude. Zo zou het bijvoorbeeldkunnen zijn dat we in het nieuwe programma opeens ook deoppervlakte van een veelhoek moeten kunnen berekenen, terwijl dit inhet oude nog niet zo was. We zouden dan natuurlijk de oude klassekunnen gaan aanpassen, maar dan lopen we het risico dat weuiteindelijk een moeilijk te beheren warboel van allemaal verschillendeversies van “dezelfde” klasse zullen overhouden. Om dit te voorkomenis er nood een mechanisme waarmee we uitbreidingen van eenbestaande klasse kunnen maken, zonder deze klasse zelf echter temoeten veranderen. Dit gebeurt dmv. het mechanisme van overerving. Subklasse = super-

klasse + extra’sWe introduceren eerst wat terminologie. De bestaande klasse waarvanze vertrekken wordt de superklasse genoemd. Uit deze superklassegaan we dan een nieuwe klasse afleiden, die de subklasse genoemdwordt. Objecten van deze subklasse zullen alles kunnen wat objectenvan de superklasse ook kunnen, maar daarnaast ook nog iets méér. Opdeze manier zouden we dus zelfs kunnen zeggen dat elk object dat totde subklasse behoort eigenlijk óók tot de superklasse behoort. Metandere woorden, we kunnen de subklasse zien als een deelverzamelingvan de superklasse. Hierin ligt ook meteen de oorsprong van deze tweetermen. Dit wordt nog eens grafisch geïllustreerd in Figuur 3.1.De notatie voor overerving in Python is heel eenvoudig: het volstaat ombij de declaratie van de subklasse de naam van de superklasse tussenhaakjes te zetten. Onderstaand code-fragmentje definieert bijvoorbeeldeen subklasse CocktailMetGarnituur van de klasse Cocktail uitSectie 2.5.2.

class CocktailMetGarnituur ( Cocktail ) :pass

37

Page 38: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

superklasse

subklasse

Figuur 3.1: Een superklasse (zoals Cocktail) in rode streepjes-lijn (– – –) met een subklasse (zoals CocktailMetGarnituur) inblauwe puntjeslijn (⋯).

Het effect hiervan is dat we nu objectjes van de klasseCocktailMetGarnituur kunnen aanmaken, die zich volledig zullengedragen 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 objectvan de klasse CocktailMetGarnituur. In termen van Figuur 3.1, ishet dus één van de objectjes in de blauwe verzameling. Dit komt enkelen alleen door het feit dat we de functie met deze naam gebruikthebben bij het aanmaken van dit object. Met andere woorden, deklasse waartoe een object behoort, ligt al vast van bij het aanmakenvan dit object. Tijdens de levensloop van het object, zal dit ook nietmeer veranderen.Zoals we in bovenstaand fragmentje kunnen zien, erft elk objectje vaneen subklasse dus al de methodes van zijn superklasse. Dit verklaartwaarom we op ons object bloodyMary toch de methodealcoholpercentage kunnen oproepen, ook al komt deze niet voor inonze definitie van de klasse CocktailMetGarnituur.Overerving wordt natuurlijk pas interessant van zodra we aan desubklasse wat bijkomende functionaliteit gaan toevoegen, die in desuperklasse nog niet aanwezig was.

class CocktailMetGarnituur ( Cocktail ) :

def voegGarnituurToe ( ze l f , gar ) :z e l f . garnituur = gar

38

Page 39: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Cocktail

CocktailMetGarnituur

Figuur 3.2: Overerving in een klassendiagramma.

Nu hebben objecten van de klasse CocktailMetGarnituur dus zowelalle methodes uit de oorspronkelijke klasse Cocktail, als deze nieuwemethode voegGarnituurToe.

bloodyMary = CocktailMetGarnituur ("Bloody Mary" , ⤦Ç [ Ingredient ( tomatensap , 0.75) , ⤦Ç Ingredient ( vodka ,0.25) ] )

bloodyMary . voegGarnituurToe ("selder" )

In een klassendiagramma tekenen we de overervingsrelatie met behulpvan een driehoek, zoals getoond in Figuur 3.2.

3.1 Het overschrijven van methodes

Dankzij deze extra methode in de klasse CocktailMetGarnituurkunnen we nu al een takje selder toevoegen aan onze bloody mary. Ergebeurt natuurlijk nog niets met deze bijkomende informatie. Wezouden bijvoorbeeld onze garnituur ook kunnen willen opnemen in destring-voorstelling van de cocktail. Hiervoor moeten we dan, zoalssteeds, een __str__ methode schrijven. Merk eerst op dat onzeCocktailMetGarnituur objecten natuurlijk al zo’n methode hebben,namelijk, diegene die ze overerven van hun superklasse Cocktail. Omnog 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 ( ze 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

Page 40: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

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 bloodyMaryBloody Mary

Nu willen we ervoor zorgen dat objecten van de klasseCocktailMetGarnituur een andere string-voorstelling krijgen danobjecten van de klasse Cocktail. Dit doen we door deze klasse zijneigen __str__ methode te geven.

class Cocktail :

. . .

def __str__ ( z e l f ) :return z e l f .naam

class CocktailMetGarnituur ( Cocktail ) :

def voegGarnituurToe ( ze l f , gar ) :z e l f . garnituur = gar

def __str__ ( z e l f ) :return z e l f .naam + " met " + ze l f . garnituur

Als we nu proberen om op onze Bloody Mary de methode __str__ op teroepen (door ofwel bloodyMary.__str__(), ofwel str(bloodyMary),ofwel print bloodyMary te doen), dan zijn er dus eigenlijk tweeverschillende methodes die in aanmerking komen. Er is de methodemet deze naam die wordt overgeërfd uit de klasse Cocktail en er is demethode met deze naam die in de klasse CocktailMetGarnituur zelfgedefiniëerd wordt. Wat Python in zo’n geval zal doen—en het istrouwens eenvoudig in te zien dat dit ook de enige zinvolle optie is—isde meest specifieke methode kiezen die van toepassing is, in dit gevaldus de methode uit CocktailMetGarnituur.

De meest specifieke me-thode wordt opgeroe-pen

>>> print bloodyMaryBloody Mary met selder

In dit geval zeggen we dat de methode uit de subklasse de gelijknamigemethode uit de superklasse overschrijft; in het Engels spreken we vanoverriding.Ook constructoren kunnen op deze manier natuurlijk overschrevenworden. In bovenstaand voorbeeld hadden we het object bloodyMaryals 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__ uitde superklasse Cocktail oproepen:

40

Page 41: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

class Cocktail :

. . .

def __ in i t__ ( ze l f , naam, ingredienten ) :z e l f .naam = naamze l f . ingredienten = ingredienten

Moesten we nu graag de klasse CocktailMetGarnituur zijn eigen,specifieke constructor geven, zodat de garnituur al meteen alsargument hiermee kan worden meegegeven, dan kan dit zo:

class CocktailMetGarnituur ( Cocktail ) :

. . .

def __ in i t__ ( ze l f , naam, ingredienten , garnituur ) :z e l f .naam = naamze l f . ingredienten = ingredientenze l f . garnituur = garnituur

Ook kunnen natuurlijk niet-magische methodes overschreven worden.Veronderstel bijvoorbeeld dat de aanwezigheid van de garnituur ervoorzorgt dat het alcoholpercentage van de cocktail een procentje lager ligtdan 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 ( ze 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 evenstil te staan bij wat er juist gebeurt in volgend code-fragmentje:

41

Page 42: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

bloodyMary = CocktailMetGarnituur(...)whiskyCola = Cocktail(...)bloodyMary.isStrafferDan(whiskyCola)

De methode isStrafferDan is niet overschreven geweest in desubklasse CocktailMetGarnituur, dus hier wordt gewoon de versieuit de klasse Cocktail opgeroepen. In deze methode gebeuren nu tweeandere methode-oproepen:

• zelf.alcoholpercentage() wordt opgeroepen, waarbij zelfverwijst naar het object bloodyMary. Aangezien dit een object isvan de klasse CocktailMetGarnituur en deze klasse de methodealcoholpercentage overschrijft, wordt hiermee dus de methodeopgeroepen die 0.01 aftrekt van het gemiddelde alcoholpercentagevan de ingrediënten.

• ander.alcoholpercentage() wordt opgeroepen, waarbij anderverwijst naar het object whiskyCola. Aangezien dit een object isvan de klasse Cocktail, speelt de subklasseCocktailMetGarnituur hierbij geen enkele rol, en wordt dus demethode opgeroepen die gewoon het gemiddeldealcoholpercentage van de ingrediënten teruggeeft.

Samengevat zien we dus dat een subklasse alle functionaliteit overerftvan zijn superklasse. Door bijkomende methodes toe te voegen, kandeze subklasse de functionaliteit van de superklasse dan gaanuitbreiden, en door reeds bestaande methodes te overschrijven, kan desubklasse de implementatie van sommige functionaliteiten naar wensgaan aanpassen.

3.2 Overervingshiërarchieën

Het is natuurlijk ook mogelijk om subklassen te maken van een klassedie zelf al een subklasse is van een andere klasse. Op deze manierkunnen hele klassenhiërarchieën tot stand komen. Als voorbeeldkunnen we de classificatie van veelhoeken nemen, die in Figuur 3.3getoond wordt. Onderstaande code toont hoe we deze hiërarchie inPython klassen kunnen gieten. Omdat we hier enkel geïnteresseerdzijn in de structuur van deze hiërarchie, hebben we al deze klassenvoorlopig leeg gelaten. In een echt programma, zouden hier natuurlijkverschillende methodes in aanwezig zijn.

class Veelhoek :pass

class Driehoek ( Veelhoek ) :pass

class Vierhoek ( Veelhoek ) :pass

class RechthoekigeDriehoek ( Driehoek ) :pass

42

Page 43: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

xx��

yy�� ��

��

$$xx

&& zz

Figuur 3.3: Classificatie/overervingshiërarchie van veelhoeken.

class Geli jkzi jdigeDriehoek ( 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 eenfenomeen dat meervoudige overerving genoemd wordt. Hoewel Pythonmeervoudige overerving wel degelijk toelaat, kan het gebruik hiervanervoor zorgen dat programma’s moeilijker te begrijpen vallen, doordathet minder evident wordt om te zien welke methodes juist vanuit welkeklasse worden overgeërfd. Om deze reden zijn er verschillende expertsdie het gebruik van meervoudige overerving afraden. Deze cursus volgtdit standpunt, en we zullen verder dan ook geen meervoudigeovererving meer gebruiken.In een overervingshiërarchie kunnen we een onderscheid makentussen een klasse die een directe subklasse is van een andere (zoalsbv. de klasse Vierkant is van Rechthoek) en een klasse die een

43

Page 44: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

indirecte subklasse is (zoals bv. Vierkant van Vierhoek). Alle klassenin bovenstaand voorbeeld zijn dus een directe of indirecte subklassevan Veelhoek. We noemen deze klasse daarom ook wel de wortel vandeze overervingshiërarchie.In het algemeen zullen we de klassen in een programma dus kunnenordenen in een aantal overervingshiërarchieën, waarvan er sommigenmogelijk veel klassen en niveaus bevatten, en anderen misschien maaréén. In het extreme geval, zouden we zelfs één enkele wortel-klasse inons programma kunnen hebben, waarvan alle andere klassen (direct ofindirect) overerven. Python bevat reeds een klasse object (met kleine

De klasse object is devoorouder van alle klas-sen eerste letter), die hiervoor specifiek bedoeld is. Dit is een relatief

nieuwe toevoeging aan de taal, en om geen oude programma’s stuk temaken, is het gebruik ervan (nog) niet verplicht. Het is echter wel aante raden om dit toch te doen, maw. om te zorgen dat elke klasse die jedefiniëert (direct of indirect) overerft van object. In Pythonterminologie noemt men dit een klasse in de nieuwe stijl (new-styleclass). We zullen in deze cursus vanaf nu alleen nog maar klassen inde nieuwe stijl maken.

3.3 Methodes uit een superklasse oproepen

Een vaak voorkomend fenomeen als we methodes gaan overschrijven,is dat we eigenlijk maar een heel kleine aanpassing willen doen aan hetgedrag van de methode uit de superklasse. Dit zagen we bijvoorbeeldbij de methode alcoholpercentage, waarbij we in de subklasse eerstheel de berekening van het gemiddelde alcoholpercentage uit desuperklasse herhaald hebben, om er dan daarna nog even een procentvan af te trekken. We hadden dit toen gewoon gedaan door een paarregels code uit de superklasse te kopiëren, maar—zoals we intussenweten—is het dupliceren van code iets wat een goede programmeuraltijd zoveel mogelijk tracht te vermijden. Python biedt daarom demogelijkheid aan om vanuit een methode in een subklasse deoverschreven gelijknamige methode uit de superklasse toch nog op teroepen.Hiervoor dient de functie super. Deze functie neemt twee argumenten:super(Klasse, zelf). Het resultaat van deze functie is eigenlijkgewoon opnieuw het object zelf, maar dan met de eigenschap dat alsje er methodes op oproept, niet de meest specifieke methodes uitKlasse zullen genomen worden, zoals normaal zou gebeuren, maar welde overschreven methode uit de superklasse van Klasse. Laat ons ditillustreren met een voorbeeld.

super dient om eenoverschreven methodetoch nog op te roepen

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

Page 45: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

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 lijnvan dit voorbeeld vervangen door gewoon:

return z e l f . alcoholpercentage ( ) − 0.01

Dit zou immers zorgen voor een nooit eindigend programma, waarin demethode alcoholpercentage uit CocktailMetGarnituur zichzelfaltijd zou blijven oproepen. Het gebruik van de functie super dient dusprecies 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 werktenkel maar bij klassen in de nieuwe stijl, maw. die een (directe ofindirecte) subklasse zijn van object.

3.4 Voorbeelden

3.4.1 VierkantenWe definiëren een klasse Vierkant als subklasse van de klasseVeelhoek uit Sectie 2.5.1. Deze klasse overschrijft de constructor vande klasse Veelhoek om ervoor te zorgen dat we een Vierkant kunnenaanmaken op basis van één hoekpunt en een lengte, in plaats van doorhet opsommen van al de vier hoekpunten. Daarnaast voegt dezeconstructor ook een extra attribuut toe (de klasse Veelhoek heeftalleen maar een attribuut punten), namelijk de lengte van een zijde.De andere methodes van de klasse Veelhoek, in het bijzonder demethode omtrek, worden gewoon overgeërfd. Een bijkomende methodeoppervlakte wordt toegevoegd.

class Vierkant ( Veelhoek ) :

def __ in i t__ ( ze l f , linksonder , lengte ) :z e l f . lengte = lengtelinksboven = 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 ) . __ in i t__ ( hoekpunten )

45

Page 46: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

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

Page 47: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

4Varia

In dit hoofdstuk komen tot slot nog een paar kleinere onderwerpen aanbod.

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 hetCGI protocol. De essentie hiervan is eenvoudig: als je jouw Pythonprogramma op de juiste plaats zet (en de webserver op de juiste maniergeconfigureerd is), dan zal dit worden opgeroepen als de gebruiker naareen bepaalde URL surft. Via gewone print instructies, kan het Pythonprogramma dan HTML code genereren, die zal worden teruggestuurdnaar de gebruiker.Het is wel aan te bevelen om als eerst de lijnContent-Type: text/html af te printen, die de webbrowser van degebruiker vertelt dat er een HTML pagina aankomt. Daarna moet erdan 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 gebruikerkunnen verwerken. Herinner je dat de gegevens die een gebruikerinvult in een webformulier worden doorgestuurd in de vorm van parenvan een naam en een waarde, waarbij de naam afkomstig is van eenattribuut van het HTML element waarin de gebruiker zijn gegevensheeft ingevuld.Python biedt een methode getvalue(naam) waarbij de waarde die bijeen naam hoort kan worden opgevraagd. Deze methode behoort bij eenobject dat je aanmaakt met de functie cgi.FieldStorage() uit demodule cgi. Het volgend voorbeeld laat zien hoe dit in zijn werk gaat.Dit is een HTML-formulier met daarop een tekstveldje met als de naamnaam.

47

Page 48: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

<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 hettekstvenster en dan op de submit-knop klikt, zal zijn webbrowsersurfen naar de URL:

.../programma.py?naam=Joost

Hierbij is programma.py de naam van volgend Python programma, datde naam die de gebruiker heeft ingevuld zal opvragen, en gebruiken omeen 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 naamprint ".</p> </body>"print "</html>"

4.2 Methodes met default Laargumenten

Het is in Python mogelijk om argumenten van een methode of functieeen default waarde te geven. Dit laat dan toe om deze methode op teroepen met minder argumenten dan normaal, waarbij de defaultwaarde dan automatisch wordt ingevuld voor de ontbrekendeargumenten. Default argumenten worden dus m.a.w. optioneel.Beschouw bijvoorbeeld volgende functie die de afstand tussen tweepunten (x1, y1) en (x2, y2) berekent en waarbij de laatste tweeargumenten een default waarde 0 hebben.

def afstand ( x1 , y1 , x2=0, y2=0) :import mathreturn 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)1.4142135623730951

48

Page 49: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Dankzij de default argumenten, is het echter ook mogelijk om hetlaatste argument weg te laten. Hiervoor zal dan de overeenkomstigedefault waarde gebruikt worden, zodat volgende code de afstand tussenpunten (2,2) en (1,0) zal berekenen.

>>> afstand(2,2,1)2.2360679774997898

Aangezien het derde argument van de methode ook een default waardeheeft, mag dit eveneens worden weggelaten. Volgende code berekentdus de afstand tussen punten (2,2) en (0,0).>>> afstand(2,2)2.8284271247461903

Zoals aan deze voorbeelden te zien is, gebeurt het gebruik van defaultargumenten altijd van achter naar voor: dit wilt zeggen dat als je éénargument weglaat, dit altijd het laatste argument is; als je tweeargumenten 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. Hierbijmaak je duidelijk welk van de optionele argumenten je een waarde wiltgeven door middel van de naam van dit argument (zoals opgegeven bijde definitie van de methode/functie). Bijvoorbeeld:

>>> afstand(1,1,y2=5)4.123105625617661>>> afstand(1,1,0,5)4.123105625617661

Het is enkel toegestaan om bij de definitie van de functie een defaultwaarde voor een argument te voorzien als ook alle verdere argumenteneen default waarde hebben. Dit mag dus bijvoorbeeld niet, omdat y2geen default waarde heeft:

def afstand ( x1 , y1 , x2:::=0 , y2 ) :

. . .

4.3 Vergelijken van objectenElk object is uniek

In een objectgeöriënteerde programmeertaal heeft elke object zijn eigen,unieke identiteit. We kunnen ons dit voorstellen zoals bijvoorbeeld onseigen rijksregisternummer: een getal dat van bij onze geboorte aan onswordt toegekend, en dat ook tot onze dood van ons zal blijven. Wekunnen dit eenvoudig zien in de Python interpreter:

>>> vodka = Drank("vodka", 0.4, 10)>>> vodka<__main__.Drank instance at 0x50d788>

Hier is 0x50d788 het unieke nummer1 dat bij dit object hoort. Als wetwee verschillende objecten aanmaken, hebben deze altijd verschillendenummers:

1Dit nummer is trouwens niet zomaar een nummer, maar is eigenlijk het geheuge-nadres waarop het object in kwestie zit opgeslagen in het RAM geheugen van de compu-ter.

49

Page 50: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

>>> vodka2 = Drank("vodka", 0.4, 10)>>> vodka2<__main__.Drank instance at 0x50d760>

Ondanks het feit dat alle attributen van de objecten vodka en vodka2dus hetzelfde zijn, zijn deze twee objecten zélf toch verschillend.In het algemeen heeft elk object een unieke identiteit en daarnaast ookeen aantal verschillende namen. In bovenstaande voorbeeldjes heefthet object met nummer 0x50d788 de naam vodka, en het object metnummer 0x50d760 de naam vodka2. We kunnen een object zoveelverschillende namen geven als we willen, en we kunnen ook debetekenis van een naam (dwz. het object waarnaar een naam verwijst)naar believen veranderen. De identiteit van een object blijft echteraltijd dezelfde.In onderstaand code-fragmentje geven we bijvoorbeeld het object0x50d760 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 envodka2 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 dingendie zouden kunnen gebeuren:

• Python zou kunnen kijken of deze twee namen verwijzen naarhetzelfde object;

• Of Python zou kunnen kijken of deze twee namen verwijzen naartwee (potentieel verschillende) objecten die gelijk zijn aan elkaar,bijvoorbeeld omdat ze dezelfde waarde hebben voor al hunattributen.

Het antwoord op deze vraag is een beetje complex. Een eerste deel vanhet antwoord is het feit dat Python niet één, maar twee verschillendemanieren aanbiedt om dingen met elkaar te vergelijken: naast het

50

Page 51: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

dubbele is-gelijk-aan-teken ==, kent Python ook het sleutelwoord is.Laten we om te beginnen eens kijken wat deze tweevergelijkingsoperatoren doen met twee “verschillende” strings, dieallebei 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 hetvolgende doet:

• Met “x is y” kijkt Python of x en y twee namen zijn die naarhetzelfde 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, danzal Python ons ook zelf laten kiezen wanneer we twee verschillendeobjecten van deze klasse als “gelijk” wensen te beschouwen. Wekunnen dit doen door in onze klasse de magische methode__eq__(zelf, ander) te definiëren, waarbij de naam eq eenafkorting is van het Engelse equals. Zolang we echter zelf geen zo’nmethode voorzien, zal Python gewoon uitgaan van deze implementatie:

def __eq__ ( ze l f , ander ) :return z e l f is ander

Met andere woorden, Python zal in zo’n geval == gewoon beschouwenals een synoniem voor is.Hieronder zijn twee pathologische voorbeelden van klassen die wel een__eq__ methode hebben.

class KlasseA :

def __eq__ ( ze l f , ander ) :return True

class KlasseB :

def __eq__ ( ze l f , ander ) :return False

51

Page 52: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Het effect hiervan is natuurlijk als volgt:

>>> KlasseA() == KlasseA()True>>> KlasseB() == KlasseB()False

Een meer zinvolle __eq__ zou bijvoorbeeld de volgende toevoeging aanonze klasse Drank kunnen zijn:

class Drank:

. . .

def __eq__ ( ze l f , ander ) :return ( z e l f .naam == ander .naam and

z e l f . alcoholpercentage == ⤦Ç ander . alcoholpercentage and

z e l f . pr i j s == ander . pr 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 gelijkbeschouwd worden vanaf dat, bijvoorbeeld, alleen al maar hun naamdezelfde is, dan volstaat het natuurlijk om de __eq__ methode in diezin aan te passen.

4.4* Nog meer vergelijkingen

Naast de == operator, kent Python nog een aantal anderevergelijkingsoperatoren. Ook van deze operatoren kan het gedragaangepast 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 hetandere 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 methodeste moeten implementeren. Hiervoor dient de methode __cmp__ (vancompare). Deze methode geeft een getal terug dat aangeeft hoe zelf enhet andere object zich tot elkaar verhouden: als de twee objecten gelijk

52

Page 53: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

zijn, moet de methode 0 teruggeven; als zelf strikt kleiner is dan hetandere object, moet een negatief getal < 0 worden teruggegeven; en alszelf strikt groter is, dan moet een positief getal > 0 wordenteruggegeven.Moesten we bijvoorbeeld onze dranken willen ordenen volgensalcoholpercentage, dan kan dit als volgt:

class Drank:

. . .

def __cmp__ ( ze l f , ander ) :return z e l f . alcoholpercentage − ⤦

Ç ander . alcoholpercentage

>>> cola = Drank("cola", 0, 2)>>> fanta = Drank("fanta", 0, 2)>>> cola is fantaFalse>>> cola == fantaTrue>>> cola >= fantaTrue>>> cola <= fantaTrue

Als we zowel een __cmp__ methode implementeren als één of meer vande meer specifieke methodes uit bovenstaande tabel, dan krijgen demeer specifieke methodes voorrang.Bovenstaande magische methodes kan je dus gebruiken om te zorgendat de vergelijkingsoperatoren voor je eigen objecten een specifiekebetekenis krijgen. Ook van andere operatoren kan je door middel vanmagische methoden de betekenis aanpassen. Bijvoorbeeld, om aan tepassen wat er gebeurt als je de indexatie operator [ ] toepast op eenobject 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 weintussen al een paar keer zijn tegengekomen, om het gemiddelde teberekenen van een aantal getallen die de gebruiker ingeeft.

class GetallenRij ( object ) :

def __ in i t__ ( z e l f ) :z e l f . r i j = [ ]

53

Page 54: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

def voegToe ( ze 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 = 0for getal in z e l f . r i j :

som += getalreturn som

def gemiddelde ( z e l f ) :som = ze 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, alsde gebruiker niet de gewenste invoer geeft. Eén van de dingen diebijvoorbeeld kan gebeuren, is dat de gebruiker op de entertoets druktzonder getallen te hebben ingegeven. In dat geval, zal ons programmaop een deling door nul stoten en op weinig elegante wijze eindigen.Laten we nu eens nadenken over hoe we dit probleem het besteoplossen.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 nietnoodzakelijk hetzelfde is. Inderdaad, als we hierover nadenken, komenwe tot volgende conclusies:

• Het detecteren van de fout gebeurt best zo laag mogelijk, dwz. vlakvoor we effectief de deling door nul zouden gaan doen in demethode gemiddelde van de klasse GetallenRij. Door defoutendetectie op deze plaats te doen, zorgen we er immers voordat elk gebruik van deze klasse van deze foutendetectie kanprofiteren. Als we op twintig verschillende plaatsen in onsprogramma deze methode oproepen, dan zal dankzij één enkeleif-test er bij alle twintig oproepen gecontroleerd worden of ertoevallig geen deling door nul zou gebeuren.

54

Page 55: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

• Het oplossen van de fout gebeurt echter liefst op een hoger niveau.Om te weten hoe we deze fout het beste oplossen, moeten weimmers weten waar onze GetallenRij vandaan komt. Als wedeze—zoals in bovenstaand programma—hebben aangemaakt opbasis van invoer die de gebruiker heeft ingegeven aan de prompt,dan is de beste aanpak waarschijnlijk om middels een printcommando de gebruiker te melden dat hij minstens één getalmoet ingeven. Het is daarnaast ook best mogelijk dat we ooit eenander programma schrijven dat dezelfde methode zal gebruikenom, bijvoorbeeld, het gemiddelde te berekenen van een rij getallendie we inlezen van een bestand op de harde schijf. In dit laatstegeval, zou het natuurlijk niet zinvol zijn om de gebruiker te zeggendat hij te weinig getallen heeft ingegeven, aangezien de gebruikervan niets weet. Aangezien de klasse GetallenRij zelf geen flauwidee heeft waar de getallen wiens gemiddelde ze moet berekeneneigenlijk vandaan komen, kan deze klasse dus de fout nietoplossen en moet dit op een hoger niveau gebeuren.

Samengevat merken we dus dat we de fout zullen moeten oplossen opeen andere plaats dan waar we ze detecteren. Hiervoor is er nood aaneen mechanisme waarmee we de informatie dat er een fout isopgetreden kunnen doorgeven van de ene naar de andere plaats. Ditgebeurt in Python dmv. uitzonderingen.

• Op de plaats waar de fout gedetecteerd wordt, wordt deuitzondering opgeworpen door middel van het sleutelwoord raise.

• Op de plaats waar de fout afgehandeld moet worden, wordt deuitzondering afgehandeld door middel van een try-except blok.

4.5.1 Uitzonderingen opwerpenLaten we eerst eens kijken naar een voorbeeld van het opwerpen vaneen 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 = ze l f .som( )8 return float (som) / len ( z e l f . r i j )9

10 . . .

Het opwerpen van de uitzondering gebeurt in lijn 6 van dit voorbeeld.Zoals hier te zien is, gebeurt dit door middel van het sleutelwoordraise gevolgd door een nieuw aangemaakt object, in dit geval van deklasse ValueError. Deze klasse is één van de veleuitzonderingsklassen die ingebouwd zitten in Python. Een overzichtvan een aantal van de meest gebruikten is te zien in Figuur 4.1. Al dezeklassen hebben een constructor die als argument een string neemt diede opgetreden fout beschrijft.

55

Page 56: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

BaseException+-- Exception

+-- StopIteration+-- StandardError| +-- BufferError| +-- ArithmeticError| | +-- FloatingPointError| | +-- OverflowError| | +-- ZeroDivisionError| +-- EOFError| +-- LookupError| | +-- IndexError| | +-- KeyError| +-- RuntimeError| | +-- NotImplementedError| +-- TypeError| +-- ...

+-- ...

Figuur 4.1: Een aantal ingebouwde uitzonderingsklassen in eenoverervingshië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 lijnendaarvoor geven een traceback van de uitvoeringsstack die aangeeftwaar deze uitzondering is opgetreden. Dit vertelt ons dat er vanopstandaard-invoer (<stdin>) een commando binnenkwam, waarvan deeerste lijn de methode gemiddelde heeft opgeroepen, die zich bevondin het bestand GetallenRij. Bij het uitvoeren van de instructie vande methode gemiddelde die op lijn 6 van dit bestand staat, is er daneen uitzondering opgeworpen.Om eens een wat meer complexe traceback te zien te krijgen, voegenwe 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( ge ta l l enr i j ) :return ge ta l l enr i j . gemiddelde ( )

>>> gemiddelde([])

56

Page 57: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

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 methodegemiddelde, die was opgeroepen vanuit de functie gem, die wasopgeroepen vanuit de functie gemiddelde, die was opgeroepen vanop<stdin>.Bovenstaande uitzondering hebben we zelf geraised, maar ookingebouwde 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 / 0Traceback (most recent call last):

File "<stdin>", line 1, in ?ZeroDivisionError: integer division or modulo by zero

4.5.2 Uitzonderingen afhandelenIn de voorbeelden die we tot dusver gezien hebben, werden deuitzondering die we gooiden altijd gewoon aan de gebruiker getoond.Als dat ons enige doel was, hadden we natuurlijk even goed met printkunnen werken. De kracht van het uitzonderingsmechanisme schuiltin het feit dat deze uitzonderingen ook weer kunnen afgehandeldworden, zodat de gebruiker ze nooit te zien zal krijgen. Met anderewoorden, als we een fout of abnormale situatie ontdekken, dan gooienwe een uitzondering, en als we er in slagen om de fout weer teherstellen of de situatie weer te normaliseren, dan handelen we dezeuitzondering af, zonder dat de gebruiker ooit hoeft te weten dat er ietsaan 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 inde uitvoeringsstack. Als we terugdenken aan het voorbeeld vangemiddelde([]) uit de vorige sectie, dan zal het volgende gebeuren:

• In de methode gemiddelde van de klasse GetallenRij wordt deinstructie raise ValueError(”...”) uitgevoerd.

57

Page 58: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

• Op dit moment wordt de uitvoering van deze methode beëindigd,en keren we terug naar de plaats van waaruit deze methode werdopgeroepen, namelijk, de functie gem.

• Ook de uitvoering van de functie gem wordt beëindigd, en wekeren terug naar de plaats van waaruit deze werd opgeroepen,namelijk, de functie gemiddelde.

• Ook de uitvoering van deze functie wordt beëindigd, en we kerenterug 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 natuurlijkhet resultaat van dit proces.Elk van deze tussenniveaus gaat nu de kans krijgen om deuitzondering die opgeworpen werd weer op te vangen. We kunnen onsdit voorstellen als een soort van gezagshiërarchie, zoals bijvoorbeeld inhet leger. Als een generaal een opdracht moet vervullen, zal hij eenaantal taken geven aan zijn kolonels. Een kolonel geeft op zijn beurttaken aan zijn kapiteins. Een kapitein geeft taken aan zijn sergeanten,en deze sergeanten geven taken aan de soldaten. Als nu een soldaateen probleem tegenkomt bij het uitvoeren van zijn taak, zal hij ditsignaleren aan zijn sergeant. Deze sergeant kan dan misschien hetprobleem zelf oplossen (= de uitzondering afhandelen). Als hij dit echterzelf niet kan, dan zal hij het probleem tot bij zijn kapitein moetenbrengen. Als de kapitein het ook niet kan oplossen, dan gaat het naarde kolonel. Als de kolonel het al evenmin opgelost krijgt, gaat het naarde generaal, en als die het ook niet kan oplossen, dan zal de taakgewoon niet uitgevoerd kunnen worden.In Python wordt zoiets geïmplementeerd dmv. een try-except blok.Dit ziet er als volgt uit:

1 . . .2 try :3 . . .4 . . .5 . . .6 except ValueError :7 . . .8 except ArithmeticError :9 . . .

10 . . .

In het try-blok staan een aantal gewone Python instructies. Als tijdenshet uitvoeren van één van deze instructies een uitzondering wordtopgeworpen, dan wordt deze mogelijk afgehandeld door één van deexcept-blokken die bij het try-blok horen. Hiervoor wordt er gezochtnaar een except-blok dat een superklasse vermeld van de klasse vande uitzondering die gegooid geweest is, of natuurlijk deze klasse zelf. Inbovenstaand code-fragmentje, zullen dus alle uitzonderingen van (eensubklasse van) de klasse ValueError en ArithmeticError wordenafgehandeld. Andere uitzonderingen worden op deze plaats nietafgehandeld, en zullen dus verder naar boven op de uitvoeringsstackescaleren.

58

Page 59: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Als een geschikt except-blok gevonden wordt, dan wordt de code diehierin staat uitgevoerd. Het is natuurlijk de bedoeling dat deze codedan ook maatregelen neemt om de fout die is opgetreden te herstellen.Nadat deze code werd uitgevoerd, wordt de uitzondering dan ook alsafgehandeld beschouwd. De uitvoering van het programma zal dangewoon verder gaan op het einde de try-except constructie. Als dus,bijvoorbeeld, in lijn 4 een uitzondering word opgeworpen, dan zal lijn 5sowieso niet meer worden uitgevoerd. Als de uitzondering dieopgeworpen werd een ValueError (of subklasse daarvan) was, danwordt eerst lijn 7 uitgevoerd, en gaan we daarna verder vanaf lijn 10.Als het een ArithmeticError (of subklasse daarvan) was, wordt lijn 9uitgevoerd, en ook dan gaan we daarna verder vanaf lijn 10. Als eenander soort uitzondering gegooid werd, dan wordt niets vanbovenstaande code meer uitgevoerd, maar escaleert de uitzonderingverder naar de plaats van waarop deze code werd opgeroepen.

4.5.3 Herwerking van het voorbeeld

We kunnen onze kennis van uitzonderingen nu gebruiken om hetvoorbeeld waarmee we dit hoofdstuk begonnen te verbeteren.

class GetallenRij ( object ) :

def __ in i t__ ( z e l f ) :z e l f . r i j = [ ]

def voegToe ( ze 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 = 0for getal in z e l f . r i j :

som += getalreturn 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 = ze 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

Page 60: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

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. 4Geef een getal in of druk op enter om het gemiddelde te zien. 6Geef een getal in of druk op enter om het gemiddelde te zien.5.0Wilt u (o)pnieuw beginnen of (s)toppen? [o/s]oGeef 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]sVaarwel!

4.6 Statische methodes

Tot dusver hebben we gezien dat een klasse een verzameling vanobjecten is, waarbij deze objecten gegevens bijhouden in hunattributen en methodes aanbieden om de gegevens in deze attributen temanipuleren. Het is echter ook mogelijk om zowel methodes alsattributen te koppelen aan een klasse op zich. Dit is nuttig voor zakendie nodig zijn voor de goede werking van een klasse, maar nietgekoppeld zijn aan een individueel object hiervan.Laten we nog eens een meetkundig voorbeeld nemen. Een cirkel heefteen middelpunt en een straal. De meest voor de hand liggende manierom een cirkel te construeren is daarom ook de volgende, waarbij wegebruik maken van de klasse Punt uit Sectie 2.5.1 om het middelpuntvoor te stellen.

class Cirkel ( object ) :

def __ in i t__ ( ze l f , middelpunt , straal ) :z e l f . middelpunt = middelpuntz 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

Page 61: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

In meetkunde, kunnen we een cirkel nog op veel andere manierendefiniëren dan via zijn middelpunt en straal. Zo kunnen webijvoorbeeld vertrekken van een vierkant en daarvan ofwel deingeschreven of de omgeschreven cirkel construeren, zoals getoond inFiguur 4.2. We zouden in de klasse Vierkant methodes kunnenopnemen die deze cirkels construeren:

class Vierkant ( Veelhoek ) :

. . .

def middelpunt ( z e l f ) :middenX = ze l f . hoekpunten [ 0 ] . x + z e l f . lengte/2middenY = ze l f . hoekpunten [ 0 ] . y + z e l f . lengte/2return 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 mathhalf = z e l f . lengte/2return Cirkel ( z e l f . middelpunt ( ) , math. sqrt (2 ) ⤦

Ç * half )

Als andere programmeurs willen weten wat de verschillende manierenzijn om een cirkel te construeren, gaan ze echter waarschijnlijk eerderin de klasse Cirkel zelf gaan kijken, dan in de klasse Vierkant. Hetkan daarom beter zijn om de twee laatste methodes daar te zetten, inplaats van in de klasse Vierkant. We zouden dit eventueel als volgtkunnen doen:

class Cirkel ( object ) :

. . .

def ingeschreven ( ze l f , vierkant ) :return Cirkel ( vierkant . middelpunt ( ) , ⤦

Ç vierkant . lengte /2)

def omgeschreven ( ze l f , vierkant ) :import mathhalf = vierkant . lengte/2return 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 dezemethode dus altijd moeten oproepen op een cirkel, maakt het eigenlijkhelemaal niet uit op dewelke: het resultaat zal altijd hetzelfde zijn. Datis natuurlijk ook logisch, aangezien de ingeschreven/omgeschrevencirkel alleen maar afhangt van het vierkant in kwestie.

vierkant = Vierkant(Punt(2,2),2)

61

Page 62: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

c1 = Cirkel(Punt(1,1),1)c2 = Cirkel(Punt(0,0),6)>>> print c1.omgeschreven(vierkant)Cirkel van 1.41421356237 rond Punt(3, 3)>>> print c2.omgeschreven(vierkant)Cirkel van 1.41421356237 rond Punt(3, 3)

Om dit nutteloze eerste argument te vermijden, kunnen we eenstatische methode gebruiken. Een statische methode wordt nietopgeroepen op een object van een klasse, zoals een normale methode,maar wel op de klasse zelf. Een statische methode heeft geen implicieteerste argument zelf. Je maakt een statische methode door gebruik temaken van de annotatie @staticmethod.

Een staticmethodheeft geen implicieteerste argumente class Cirkel ( object ) :

. . .

@staticmethoddef ingeschreven ( vierkant ) :

return Cirkel ( vierkant . middelpunt ( ) , ⤦Ç vierkant . lengte /2)

@staticmethoddef omgeschreven ( vierkant ) :

import mathhalf = vierkant . lengte/2return Cirkel ( vierkant . middelpunt ( ) , ⤦

Ç math. sqrt (2 ) * half )

We kunnen nu deze methodes gebruiken zonder daarvoor nog eenobject van de klasse Cirkel nodig te hebben.

>>> print Cirkel.omgeschreven(vierkant)Cirkel van 1.41421356237 rond Punt(3, 3)>>> print Cirkel.ingeschreven(vierkant)Cirkel van 1 rond Punt(3, 3)

Een ander voorbeeld is onderstaande statische methode die eeneenheidscirkel construeert en teruggeeft.

class Cirkel ( object ) :

. . .

@staticmethoddef 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

Page 63: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Figuur 4.2: Een vierkant met zijn ingeschreven (blauw) en omge-schreven (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, ophetzelfde 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 tekrijgen, is natuurlijk nog steeds:

import mathmath. pi

Merk wel op dat het niet mogelijk is om de klasse Cirkel bijvoorbeeldals 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 pasmogen gebruiken nadat de definitie van deze klasse volledig ten eindeis.

4.7* Nog een blik achter de schermen

Zoals we ons herinneren uit Sectie 1.8, zorgt Python er achter deschermen voor dat al de attributen van een object in een woordenboekbelanden dat bij dit object hoort. Daarnaast hebben we ook gezien datelke klasse eveneens een woordenboek heeft, waarin o.a. al demethodes van deze kasse zitten. Zoals we zouden verwachten, is hetspeciale aan een statisch attribuut niet meer of niet minder dan dat dit

63

Page 64: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

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__’: ’__main__’, ’omgeschreven’: <staticmethod object at 0x50af30>, ’__str__’: <function __str__ at 0x5061b0>, ’pi’: 3.1428571428571428, ’__weakref__’: <attribute ’__weakref__’ of ’Cirkel’ objects>, ’ingeschreven’: <staticmethod object at 0x50aef0>, ’__init__’: <function __init__ at 0x5034f0>, ’__doc__’: None}

4.7.1 De volledige klasse ‘Cirkel’

Hieronder nog eens de volledige klasse Cirkel met haar drie statischemethodes en haar statische attribuut.

class Cirkel ( object ) :

def __ in i t__ ( ze l f , middelpunt , straal ) :z e l f . middelpunt = middelpuntz 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 )

@staticmethoddef ingeschreven ( vierkant ) :

return Cirkel ( vierkant . middelpunt ( ) , ⤦Ç vierkant . lengte /2)

@staticmethoddef omgeschreven ( vierkant ) :

import mathhalf = vierkant . lengte/2return Cirkel ( vierkant . middelpunt ( ) , ⤦

Ç math. sqrt (2 ) * half )

@staticmethoddef 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

Page 65: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

4.8 Private variabelen

Zoals we intussen weten, is encapsulatie een belangrijke motivatie voorobjectgericht programmeren. Door gegevens binnen een klasse zoveelmogelijk af te schermen, behouden we langs de ene kant zoveelmogelijk vrijheid om later aanpassingen binnenin deze klasse te doen,en maken we anderzijds ook het gebruik van deze klasse in de rest vanons programma zo eenvoudig mogelijk. Om dit te bereiken, is gewoonhet gebruik van een objectgerichte programmeertaal op zich natuurlijkniet voldoende. We moeten hiervoor ook de discipline aan de dag leggenom nooit van buiten een klasse rechtstreeks in te grijpen in diensinterne werking.Hier is nog eens een voorbeeld om dit te illustreren. We maken eenklasse Datum en een klasse Reis die deze Datums gebruikt om zijnbegin- en einddatum voor te stellen.

class Datum( object ) :

def __ in i t__ ( ze l f , dag , maand, jaar ) :z e l f . dag = dagze l f .maand = maandze 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__ ( ze l f , ander ) :

jaarVerschil = z e l f . jaar − ander . jaarmaandVerschil = z e l f .maand − ander .maanddagVerschil = z e l f . dag − ander . dagverschi l len = [ jaarVerschil , maandVerschil , ⤦

Ç dagVerschil ]# We geven nu het eerste verschil != 0 terugfor verschi l in verschi l len :

i f verschi l != 0:return verschi l

# Tenzij er zo geen is, want dan zijn ze gelijkreturn 0

@staticmethoddef schrikkeljaar ( jaar ) :

return jaar % 4 == 0 and ( jaar % 100 != 0 or ⤦Ç jaar % 400 == 0)

@staticmethoddef aantalDagen (maand, jaar ) :

met31 = [1 ,3 ,5 ,7 ,8 ,10 ,12]met30 = [4 ,6 ,9 ,11]i f maand in met31:

65

Page 66: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

return 31i f maand in met30:

return 30# Als we hier komen, is het februarii f Datum. schrikkeljaar ( jaar ) :

return 29else :

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 = 12i f z e l f . dag < laatsteDag :

volgende .dag += 1e l i f z e l f .maand < laatsteMaand :

volgende .maand += 1volgende .dag = 1

else : # Gelukkig nieuwjaar!volgende . jaar += 1volgende .dag = 1volgende .maand = 1

return volgende

class Reis ( object ) :

def __ in i t__ ( ze l f , van , tot , naar ) :z e l f . vertrek = vanze l f . terugkeer = totz e l f . bestemming = naar

def overlaptMet ( ze l f , ander ) :return not ( z e l f . terugkeer < ander . vertrek

or ander . terugkeer < z e l f . vertrek )

def verleng ( ze 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

Page 67: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

In bovenstaande code is de manier waarop we een datum voorstellennetjes ingekapseld in de klasse Datum. Dit betekent dat we dezevoorstelling kunnen veranderen zonder code buiten deze klasse temoeten aanpassen. In het bijzonder de klasse Reis kan gewoon blijvenzoals ze is. Laten we dit illustreren door een andere voorstelling vaneen datum te kiezen, waarbij er geen gebruik gemaakt wordt van dagen,maanden en jaren, maar waarbij we (net zoals het Unixbesturingssysteem dat doet) een datum zullen voorstellen door middelvan het aantal seconden dat verstreken is sinds 00:00:00 op 1 januari1970.

class Datum( object ) :

secPerDag = 60 * 60 * 24

dagenPerJaar = 365

def __ in i t__ ( ze l f , dag=1, maand=1, jaar=1970) :# Het aantal dagen sinds 1970# tot aan het begin van dit ’jaar’dagenTotBeginJaar = 0for j in range(1970, jaar ) :

dagenTotBeginJaar += dagenPerJaari f Datum. schrikkeljaar ( jaar ) :

dagenTotBeginJaar += 1# Het aantal dagen in dit ’jaar’# tot het begin van deze ’maand’dagenTotBeginMaand = 0for m in range (maand, 1) :

dagenTotBeginMaand += Datum. aantalDagen (m, ⤦Ç jaar )

# Het aantal dagen in deze ’maand’# tot aan deze ’dag’dagenTotDag = dag − 1dagenTotaal = dagenTotBeginJaar + ⤦

Ç dagenTotBeginMaand + dagenTotDagze 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__ ( ze l f , ander ) :

return z e l f . verstreken − ander . verstreken

@staticmethoddef schrikkeljaar ( jaar ) :

return jaar % 4 == 0 and ( jaar % 100 != 0 or ⤦Ç jaar % 400 == 0)

@staticmethod

67

Page 68: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

def aantalDagen (maand, jaar ) :met31 = [1 ,3 ,5 ,7 ,8 ,10 ,12]met30 = [4 ,6 ,9 ,11]i f maand in met31:

return 31i f maand in met30:

return 30# Als we hier komen, is het februarii f Datum. schrikkeljaar ( jaar ) :

return 29return 28

def volgendeDag ( z e l f ) :volgende = Datum( ) # Maakt niet uit welke datumvolgende . verstreken = ze l f . verstreken + 1return volgende

Je kan gemakkelijk nakijken dat, ook met deze nieuwe versie van deklasse Datum, de klasse Reis nog steeds correct zal werken. Dit is dusprecies wat we met encapsulatie hopen te bereiken.Dit zou echter niet gelukt zijn, moesten we in onze klasse Reisbijvoorbeeld volgende methode gehad hebben:

class Reis ( object ) :

. . .

def __str__ ( z e l f ) :r1 = "Reis naar " + ze l f . bestemmingr2 = "(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 Datumgoed gewerkt hebben, maar met onze nieuwe versie niet meer. Dereden hiervoor is dat ze de encapsulatie doorbreekt door rechtstreeksde attributen van een datum aan te spreken. Als we bij het maken deklasse Datum al het vermoeden hadden dat we misschien ooit devoorstelling van een datum zouden willen wijzigen, dan hadden wemisschien beter geprobeerd om dit te voorkomen. In Python wordt een

Leidende underscore =privaat

underscore _ gebruikt om aan te geven dat een attribuut enkel bedoeldis voor de interne werking van een klasse, en dat het niet de bedoelingis dat er vanuit andere klassen rechtstreeks met dit attribuut gewerktwordt.We hadden onze eerste versie van de klasse Datum dus misschien beterzo geschreven:

class Datum( object ) :

def __ in i t__ ( ze l f , dag , maand, jaar ) :z e l f . _dag = dag

68

Page 69: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

z e l f ._maand = maandze l f . _jaar = jaar

. . .

Merk op: _dag, _maand en _jaar zijn nu gewoon de namen van de drieattributen van een Datum. Er is dus helemaal niets speciaals aan deunderscore; dit is gewoon een “letter” die uitmaakt van de naam vanhet attribuut. De betekenis die eraan gegeven wordt (namelijk dat ditattributen zijn voor intern gebruik), is alleen maar een kwestie vanafspraak tussen Python programmeurs.Als de waarde van deze attributen van buiten de klasse tochopgevraagd of gewijzigd moet kunnen worden, kunnen we hiervoornatuurlijk methodes voorzien. Een standaard conventie is om, voor eenattribuut _ding, de naam getDing te gebruiken voor een methode diede waarde van _ding teruggeeft, en de naam setDing voor eenmethode die de waarde verandert. We spreken ook wel van getters ensetters.

class Datum( object ) :

def __ in i t__ ( ze l f , dag , maand, jaar ) :z e l f . _dag = dagze l f ._maand = maandze l f . _jaar = jaar

def getDag ( z e l f ) :return z e l f . _dag

def setDag ( ze l f , dag ) :z e l f . _dag = dag

. . .

Als we nu later de dag/maand/jaar-voorstelling zouden willenvervangen door een andere, dan kunnen we door het aanpassen van degetters en setters voor deze attributen ervoor zorgen dat andereklassen toch onveranderd correct blijven werken.

4.9* Eigenschappen (Properties)

Het voorbeeld met getters en setters uit de vorige sectie is een beetjeon-Pythoniaans. De reden hiervoor is dat Python eigenlijk een beteremanier aanbiedt om hetzelfde effect te bekomen, namelijk het conceptvan eigenschappen (of properties in het Engels). Zo’n eigenschapbestaat uit een attribuut met een bijhorende getter en/of setter. Bij degetter wordt de annotatie @property geplaatst, en bij de eventuelesetter de annotatie @getter.setter, waarbij de naam van de gettermoet worden ingevuld. Zowel getter als setter moeten bovendiendezelfde naam hebben.Onderstaande code definieert drie eigenschappen, namelijk dag, maanden jaar.

69

Page 70: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

class Datum( object ) :

def __ in i t__ ( ze l f , dag , maand, jaar ) :z e l f . _dag = dagze l f ._maand = maandze l f . _jaar = jaar

@propertydef dag ( z e l f ) :

return z e l f . _dag

@dag. setterdef dag ( ze l f , dag ) :

z e l f . _dag = dag

@propertydef maand( z e l f ) :

return z e l f ._maand

@maand. setterdef maand( ze l f , maand) :

z e l f ._maand = maand

@propertydef jaar ( z e l f ) :

return z e l f . _jaar

@jaar . setterdef jaar ( ze l f , jaar ) :

z e l f . _jaar = jaar

Het speciale hieraan is dat we zo’n eigenschap nu kunnen gebruikenalsof het een gewoon attribuut betrof. We kunnen dus met anderewoorden gewoon dit doen:

>>> d = Datum(5,4,2001)>>> d.dag5>>> d.jaar = 1999>>> d.jaar1999

Merk op dat we hier dus niet de naam van de echte attributen van hetDatum object gebruiken (die zijn immers _dag, _maand en _jaar, endat zouden zelfs ook totaal andere namen kunnen zijn), maar wel denamen van de getters/setters die we geschreven hebben. Telkens alswe hieraan een toekenning doen, zal Python de setter oproepen, entelkens als we hiervan de waarde opvragen, roept Python de getter op.In bovenstaand code fragmentje doet de tweede lijn dus eigenlijk eenmethode-oproep d.dag(), terwijl de 4e lijn een methode-oproepd.jaar(1999) doet. Het leuke is dat al deze methode-oproepenonzichtbaar zijn, zodat we eigenlijk niet hoeven te weten of dag nu eenecht attribuut dan wel een eigenschap is. Dit is een detail waarover

70

Page 71: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

enkel de persoon die de klasse Datum implementeert zich zorgen moetmaken, maar niet de programmeurs die deze klasse gewoon maargebruiken.

71

Page 72: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

72

Page 73: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

5Python voor data-analyse

De reden dat deze cursus gebruik maakt van Python is dat deze taal intechnisch-wetenschappelijke kringen steeds vaker gebruikt wordt.Vooral voor het visualiseren en analyseren van data is Python vandaagde dag een populaire keuze, die kan wedijveren met statistischepakketten zoals SAS of R. Hierbij heeft Python natuurlijk het voordeeldat het een echte programmeertaal is, zodat je over een veel grotereflexibiliteit beschikt.Eén van de voornaamste sterktes van Python voor dit soort van werk isde (gratis) beschikbaarheid van verschillende geavanceerdeobjectgeöriënteerde bibliotheken.

• numpy is een basisbibliotheek voor het doen van numeriekebewerkingen in Python.

• matplotlib is een bibliotheek om verschillende soorten vangrafieken te maken. De gegevens die op deze grafieken getoondworden, worden typisch voorgesteld door numpy objecten.

• pandas is een bibliotheek voor datamanipulatie. Ook dezebibliotheek maakt gebruik van numpy objecten om data voor testellen. Om visualisaties te maken, maakt pandas dan weergebruik van matplotlib.

• scikit-learn is een Machine Learning bibliotheek, die toelaatom 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. Meerinformatie is natuurlijk beschikbaar in de API1 documentaties enverschillende online tutorials. Daarnaast is het ook altijd mogelijk omin een interactieve Python sessie gebruik te maken van de help functieom te ontdekken wat je met een bepaald object of met een bepaaldefunctie juist kan aanvangen.

1Application Programming Interface: de verzameling van klassen, methodes, functies,. . . die een bibliotheek aanbiedt aan haar gebruikers.

73

Page 74: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

5.1 De Pandas bibliotheek

Pandas heeft twee klassen om data voor te stellen: één-dimensionaledata wordt voorgesteld door een object van de klasse Series, terwijlmeer-dimensionale data wordt voorgesteld door een DataFrame.

Een DataFrame stelt een tabel voor met een aantal rijen en een aantalkolommen (zoals bv. ook een rekenblad in Excel eruit ziet). Hetattribuut columns van zo’n dataframe geeft de verschillende kolommenterug (samengevat in een object van de klasse Index). De indices vande verschillende rijen van de tabel zijn op te vragen met het attribuutindex (eveneens een object van de klasse Index); meestal zijn de rijengewoon genummerd met 0,1,2,. . .

Je kan de indexatie operator [] gebruiken om één kolom te selecterenuit een dataframe. Zo selecteert df[’Foo’] bijvoorbeeld de kolomgenaamd Foo uit het dataframe df. Ook de notatie df.Foo kan jehiervoor 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 aantalrijen uit een dataframe te selecteren: dit doe je door niet de naam vaneen kolom mee te geven, maar wel een test, waarbij je een kolomvergelijkt met iets anders. Bijvoorbeeld, df[df.Foo > 5] selecteert uithet dataframe df alle rijen waarvoor er in de kolom Foo een waardestaat die groter is dan 5.

Een dataframe wordt meestal ingevuld met gegevens die afkomstig zijnuit een bestand. Het gemakkelijkst is om te werken met bestanden inCSV (Comma-Separated Values) formaat: een bestand in dit formaatbestaat uit een aantal rijen, waarbij elke rij bestaat uit een aantalwaardes die van elkaar gescheiden worden door een “separator”(scheidingsteken). In de Angelsaksische wereld gebruikt men vaak dekomma als scheidingsteken (vandaar de naam Comma-SeparatedValues), maar ook puntkomma’s of tabs worden vaak gebruikt, en zijnin Europese context natuurlijk handiger. Excel kan bijvoorbeeldeenvoudig data exporteren naar dit formaat, waarbij je kan kiezen welkscheidingsteken gebruikt wordt. Pandas heeft een functie read_csvwaarmee je zo’n CSV bestand kan inlezen. Het resultaat van dezefunctie is een dataframe.

Om een dataframe te bekijken, kan je het gewoon printen. Daarnaastheeft een dataframe ook een methode head(i) die de enkel de eerste irijen toont (i heeft een default waarde van 5) en kan je met de methodedescribe() verschillende statistieken berekenen. Om data tevisualiseren kan je de methode plot(x,y) gebruiken, waarmee je eenplot maakt van kolom y van het dataframe t.o.v. kolom x. Dezemethode (die onderliggend gebruik maakt van matplotlib) kan eenaantal verschillende soorten grafieken maken; het soort van grafiek datje wilt, kan je aangeven met een optioneel derde argument kind (= hetEngelse woord voor “soort”).

74

Page 75: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

5.2 De SciKit Learn Bibliotheek

De scikit-learn bibliotheek wordt gebruikt om data te modelleren.Er zijn hiervoor een heleboel verschillende modellen beschikbaar. Aldeze verschillende soorten van modellen zijn subklassen van eengenerieke klasse BaseEstimator. Een dergelijke schatter heeft eenaantal parameters. De normale manier van werken is dat deparameters van de schatter eerst worden geleerd uit de dataset,waarna de getrainde schatter dan gebruikt kan worden om nieuwewaardes te voorspellen.Het eerste kan gebeuren dmv. de methode fit(X,y) van hetestimator-object (hierbij is y de afhankelijke variabele die voorspeldmoet worden op basis van de variabelen X). Zowel X als y zijn hierbijnumpy arrarys: X heeft het formaat aantal voorbeelden × aantalvariabelen, terwijl y een één-dimensionale vector is van grootte aantalvariabelen.Eenmaal de parameters zijn ingevuld, kan de methode predict(X)gebruikt worden om de y-waarde te voorspellen die volgens de schatterbij X hoort. Het is vaak ook interessant om te weten hoe goed degetrainde schatter de data vat. Hiervoor kan de methode score(X,y)gebruikt worden, die de R2-waarde teruggeeft.

5.3 Een voorbeeld analyse

Als voorbeeld maken we een analyse van een online beschikbaredataset over de druksterkte van beton2. In deze dataset zijn 1030verschillende tests opgenomen. In elke test werd de druksterkte vaneen bepaald beton opgemeten. Elk beton wordt gekarakteriseerd doorzeven verschillen eigenschappen: de leeftijd van het beton en dehoeveelheid (in kg/m3) van zes verschillende materialen in het beton.Onderstaand voorbeeld toont hoe we deze dataset kunnen inlezen metbehulp van pandas, verschillende grafieken genereren met behulp vanpandas en matplotlib, en tenslotte met scikit-learn een aantallineaire regressiemodellen opstellen om de druksterkte te voorspellenaan de hand van de andere parameters.

2http://www.bliasoft.com/data-mining-software-concrete.html

75

Page 76: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

In [79]: %matplotlib inlineimport pandas as pd

# De data staan in CSV formaat, met tabs (’\t’) als separator# Decimale getallen zijn met een komma genoteerddf = 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 \0 540.0 0.0 0 162 2.51 540.0 0.0 0 162 2.52 332.5 142.5 0 228 0.03 332.5 142.5 0 228 0.04 198.6 132.4 0 192 0.0

Coarse aggregates Fine aggregates Age Compressive strength0 1040.0 676.0 28 79.991 1055.0 676.0 28 61.892 932.0 594.0 270 40.273 932.0 594.0 365 41.054 978.4 825.5 360 44.30

In [80]: df.describe() # We bekijken wat statistieken van elke kolom

Out[80]: Cement Blast furnace slags Fly ashes Water \count 1030.000000 1030.000000 1030.000000 1030.000000mean 281.167864 73.895825 54.188350 181.567282std 104.506364 86.279342 63.997004 21.354219min 102.000000 0.000000 0.000000 121.80000025% 192.375000 0.000000 0.000000 164.90000050% 272.900000 22.000000 0.000000 185.00000075% 350.000000 142.950000 118.300000 192.000000max 540.000000 359.400000 200.100000 247.000000

Superplasticizers Coarse aggregates Fine aggregates Age \count 1030.000000 1030.000000 1030.000000 1030.000000mean 6.204660 972.918932 773.580485 45.662136std 5.973841 77.753954 80.175980 63.169912min 0.000000 801.000000 594.000000 1.00000025% 0.000000 932.000000 730.950000 7.00000050% 6.400000 968.000000 779.500000 28.00000075% 10.200000 1029.400000 824.000000 56.000000max 32.200000 1145.000000 992.600000 365.000000

Compressive strengthcount 1030.000000mean 35.817961std 16.705742min 2.33000025% 23.71000050% 34.44500075% 46.135000max 82.600000

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 makendf[’Compressive strength’].hist()

Out[81]: <matplotlib.axes._subplots.AxesSubplot at 0x11645f410>

76

Page 77: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

In [82]: # Om een idee te krijgen van welke variabelen een invloed hebben# op de doelkolom, maken we een aantal scatterplotsdoel = ’Compressive strength’for kolom in df.columns:

if kolom != doel:df.plot(kolom, doel, kind=’scatter’)

77

Page 78: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

78

Page 79: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

79

Page 80: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

80

Page 81: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

In [83]: # Een eerste observatie is dat "jong" beton minder sterk# lijkt dan oud beton.# We controleren of dit klopt d.m.v. een boxplotdf.boxplot(column=’Compressive strength’, by=’Age’)

Out[83]: <matplotlib.axes._subplots.AxesSubplot at 0x116902390>

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

Page 82: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

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 281.000000 281.000000 281.000000 281.000000mean 294.698932 63.120996 49.131673 182.826690std 101.954243 79.322306 62.149342 25.763355min 102.000000 0.000000 0.000000 121.80000025% 213.500000 0.000000 0.000000 161.90000050% 277.200000 19.000000 0.000000 186.00000075% 362.600000 116.000000 118.200000 195.500000max 540.000000 305.300000 174.700000 228.000000

Superplasticizers Coarse aggregates Fine aggregates Age \count 281.000000 281.000000 281.000000 281.000000mean 5.851957 979.701423 773.964769 117.359431std 6.354363 68.063585 91.254666 85.162213min 0.000000 822.000000 594.000000 56.00000025% 0.000000 932.000000 746.600000 56.00000050% 5.500000 968.000000 781.000000 90.00000075% 10.200000 1028.400000 845.000000 100.000000max 32.200000 1134.300000 992.600000 365.000000

Compressive strengthcount 281.000000mean 48.565765std 13.468959min 21.86000025% 39.00000050% 46.93000075% 56.340000max 82.600000

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_modelreg_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 zienprint "Sterkte = "+str(reg_cem.coef_[0])+’ * Cement + ’+str(reg_cem.intercept_)

Sterkte = 0.0648078680775 * Cement + 29.466955592

In [86]: # Als er bv. 100 kg/m^3 cement aanwezig is,# zouden we een sterkte verwachten van:cement = 100print 0.0648078680775 * cement + 29.466955592

35.9477423997

In [87]: # Dit is ook de waarde die berekend wordt# door de methode "predict" van het model.print reg_cem.predict(cement)

82

Page 83: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

[ 35.9477424]

In [88]: # We tekenen even opnieuw een scatterdf.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 toontimport matplotlib.pyplot as pltplt.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 = -0.218466316259 * Water + 88.5072386882

In [90]: # Ook deze rechte tekenen we op een scatterplotdf.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

Page 84: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

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 kijkenX = 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: 0.240656732228Rˆ2 water: 0.174624727637

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 = 0.0685745987997 * Cement -0.235633409176 * Water 71.4369804149Rˆ2 beide: 0.442990809855

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

Page 85: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

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

Rˆ2 beide: 0.310261560311

85

Page 86: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

86

Page 87: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

APracticum opgaves

Deze opgave is geïnspireerd door een project van de EAVISEonderzoeksgroep, waarin beelden van verkeersstromen—zoalshieronder te zien—geanalyseerd worden om de verkeersveiligheid tevergroten. Zo kan bijvoorbeeld het effect van verschillende soortenwegmarkeringen in de buurt van zebrapaden op de snelheid vanpasserende 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 zienzijn, 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 stelleneen auto dus gewoon voor als een rechthoek.

Oef. 1.1 ▸ Maak een klasse Auto. Voorzie een constructor waarmee je een autokan aanmaken met zijn verschillende eigenschappen (kleur, x-positie,y-positie, lengte, breedte). De (x, y)-coördinaat van een auto is die van

87

Page 88: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

zijn linkerbenedenhoek (dwz. zijn hoekpunt met de kleinste x en dekleinste y coördinaat).

Oef. 1.2 ▸ Schrijf onderaan in je bestand een main functie:

def main():...

main()

Maak in deze functie een nieuw Auto object auto1 aan dat de kleur“red” heeft, zich op positie (10,20) bevindt, 30 lang en 20 breed is.

Oef. 1.3 ▸ Maak een tweede Auto object auto2 aan met dezelfde gegevens alsauto1 en test wat er gebeurt met volgend stukje code:

if auto1 == auto2:print "gelijk"

else:print "verschillend"

Wat gebeurt er als je in plaats hiervan auto1 met zichzelf vergelijkt?

Oef. 1.4 ▸ Schrijf in je klasse Auto een __str__ methode die ervoor zorgt datbijvoorbeeld auto1 als volgt wordt afgeprint:

auto op (10,20) van grootte (30,20) in kleur red

Test uit of je methode effectief goed werkt.

Oef. 1.5 ▸ Schrijf in je klasse Auto een methode omvat waarmee je kan nagaan ofeen bepaalde (x, y)-positie door de auto bedekt wordt. Deze methodemoet dus een boolean (True of False) teruggeven. Later gaan we dezemethode gebruiken om bv. botsingen te detecteren. Schrijf in je mainfunctie een test die het woord “botsing” afprint als auto1 het punt(20,30) bevat. Test dit ook eens uit met het punt (0,0).

A.2 Een eenvoudig datamodel: Punt

In plaats van x en y coördinaten altijd apart mee te geven, kunnen wedeze twee coördinaten ook samen nemen in één object van een nieuweklasse Punt. Dit levert een netter resultaat op, met betere encapsu-latie. Bovendien kunnen we deze klasse Punt dan ook gemakkelijkherbruiken.

Oef. 2.1 ▸ Maak een klasse Punt met een constructor die een x en y waarde alsargument krijgt. Zorgt ervoor dat een punt wordt afgeprint in hetformaat (x, y). Maak in je main functie het punt (10,20) aan encontroleer dat dit juist wordt afgeprint.

Oef. 2.2 ▸ Pas de constructor van de klasse Auto aan zodat er nu een Punt objectwordt meegegeven als argument in plaats van twee aparte coördinaten.Pas ook de __str__ methode aan en het aanmaken van de Autoobjecten in je main functie. Controleer dat het afprinten van auto1 nogsteeds goed werkt. (Je if-test met de methode omvat zal nu niet meerwerken. Zet deze even in commentaar.)

88

Page 89: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Oef. 2.3 ▸ Vervang ook het apart doorgeven van de lengte en de breedte in deconstructor van Auto door het gebruik van één Punt en pas de__str__ methode en je main functie ook hieraan aan. Controleer weerdat het afprinten nog goed werkt.

Oef. 2.4 ▸ Neem nu ook de methode omvat onder handen: vervang de tweeafzonderlijke argumenten door één punt, zorg dat de methode weerwerkt, en test dit uit in je main functie.

Oef. 2.5 ▸ Geef de klasse Punt een methode afstand(zelf, ander) waarmee jede afstand van een punt tot een ander punt kan bereken:

δ((x1, y1), (x2, y2)) =√

(x1 − x2)2 + (y1 − y2)2

Herinner je dat je eerst een import math moet doen, vooraleer je inPython de vierkantswortel functie kan gebruiken. Gebruik dezemethode om in je main-functie te berekenen wat het verschil in afstandis tussen de linkerbenedenhoek van een auto op positie (10,20) en dievan eentje op (40,60).

A.3 Een eenvoudig datamodel: Ruimte

Uiteindelijk willen we beelden kunnen analyseren waarop er verschil-lende auto’s samen voorkomen. Hiervoor introduceren we een klasseRuimte, die aggregatie is van Auto’s.

Oef. 3.1 ▸ Maak een klasse Ruimte met een constructor die een lege ruimteaanmaakt. Deze ruimte houdt een (initieel lege) lijst bij met alle auto’sdie erin aanwezig zijn. Geef deze klasse een methode voegAutoToewaarmee een auto aan de ruimte kan worden toegevoegd. Maak in jemain functie twee auto’s aan en gebruik deze methode om ze te voegenaan een ruimte r.

Oef. 3.2 ▸ Geef de klasse Ruimte een methode __str__(zelf), die eenstring-voorstelling van de ruimte construeert, door destring-voorstellingen van alle auto’s in deze ruimte te combineren.Controleer dat de string voorstelling van r inderdaad correct is.

We gaan nu detecteren of twee auto’s in dezelfde ruimte al dan niet metelkaar botsen. Hiervoor moeten we aanpassingen doen aan de drieklassen die we al hebben.

Oef. 3.3 ▸ Schrijf in de klasse Punt een methode die twee punten bij elkaar optelt.Concreet moet deze methode een nieuw punt teruggeven dat hetresultaat is van de optelling van het huidige punt (zelf) met een anderpunt, dat als argument van de methode wordt meegegeven. Als je dezemethode bovendien de naam __add__ geeft, dan kan je deze oproependoor gewoon het symbool + te gebruiken (als in: som = p1 + p2,waarbij p1 en p2 twee Punt objecten zijn).

Oef. 3.4 ▸ Schrijf in de klasse Auto een methode hoekpunten die een lijstteruggeeft van vier Punt-objecten, die de vier hoekpunten van dezeauto voorstellen.

89

Page 90: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Oef. 3.5 ▸ Schrijf in de klasse Auto een methode botstMet die nagaat of dezeauto botst met een andere auto, door voor elk van zijn eigenhoekpunten aan de andere auto te vragen of deze dit hoekpunt omvat.

Oef. 3.6 ▸ Schrijf in de klasse Ruimte een methode botsing die nagaat of er indeze ruimte al dan niet twee verschillende auto’s met elkaar in botsingzijn (elke auto is natuurlijk “in botsing met” zichzelf).

Tot slot beschouwen we nog een uitbreiding aan de klasse Auto die de“risico’s” van gelijkheidstesten illustreert.

Oef. 3.7 ▸ Schrijf in de klasse Auto een methode heeftAlsHoekpunt(punt) dienagaat of een gegeven Punt object voorkomt in de lijst met hoekpuntenvan die auto. Gebruikt deze methode om te controleren of het punt(40,40) voorkomt in de lijst met hoekpunten van een auto op (10,20)van grootte (30,20). Mogelijk is het resultaat hier niet wat je verwacht.Ga eens na hoe dit komt.

Oef. 3.8 ▸ Zorg ervoor dat je methode heeftAlsHoekpunt wel goed werkt.Hiervoor moet je deze methode zelf niet aanpassen, maar wel eenaanpassing doorvoeren aan de klasse Punt.

A.4 Visualisatie van een ruimte

We gaan nu onze ruimtes visualiseren met behulp van SVG prentjes inde webbrowser. Hiervoor overlopen we eerst alle verschillende stappendie nodig zijn om een Python programma te “installeren” op de webser-ver laurel.local.thomasmore.be in lokaal A217. Daarna voegen weaan onze bestaande klassen code toe om de juist SVG tekst te produ-ceren.

A.4.1 Installatie op de serverOef. 4.1 ▸ Download het bestand

http://iiw.denayer.be/∼jve/svg.py

en plaats het in de map public_html. Dit bestand heeft de volgendeinhoud:

#!/usr/local/bin/pythonimport jve

svg = """<svg xmlns="http://www.w3.org/2000/svg">

<rect x="50" y="20" width="150" height="150" fill="blue" /></svg>

"""

def main():print jve.http(svg)

main()

90

Page 91: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

De eerste lijn dient om de webserver te vertellen dat dit een programmais dat moet worden uitgevoerd in Python. Vergeet ook hetxmlns-attribuut in het <svg>-element niet, aangezien dit nodig is omFirefox te vertellen dat je een SVG-tekening wilt maken. Verdergebruiken we ook nog de functie http uit de module jve die webovenaan importeren. Het effect hiervan wordt verderop duidelijk.

Oef. 4.2 ▸ Vertel de webserver dat hij dit programma mag uitvoeren. Dit doe jebijvoorbeeld door in een terminal volgende commando’s uit te voeren(het eerste is niet nodig als je terminal zich al in deze map bevindt):

% cd public_html% chmod a+rx svg.py

Oef. 4.3 ▸ Je kan nu je programma als volgt uitvoeren:

% ./svg.py

Het resultaat zou er zo moeten uitzien:

% ./svg.pyContent-type: text/xml

<svg xmlns="http://www.w3.org/2000/svg"><rect x="50" y="20" width="150" height="150" fill="blue" />

</svg>

Je ziet hier meteen wat het effect is van de functie http uit de modulejve: deze heeft gewoon bovenaan een extra lijn toegevoegd, die Firefoxvertelt wat voor soort inhoud er volgt.

Oef. 4.4 ▸ Als je nu in Firefox naar volgende URL surft:

http://laurel.local.thomasmore.be/~aXXX/svg.py

zou je een blauwe rechthoek moeten zien.

A.4.2 Genereren van SVG codeNu we in staat zijn om SVG tekeningen te maken in de webbrowser,gaan we dit gebruiken om de toestand van een ruimte te visualiseren.

Oef. 4.5 ▸ Breid de klasse Auto uit met een methode svg die een <rect ... />tag teruggeeft waarmee de auto wordt voorgesteld. Geef alle attributenvan het Auto object hierbij hun juiste plaats.

Oef. 4.6 ▸ Breid de klasse Ruimte uit met een methode svg die een<svg ...> ... </svg> element teruggeeft met daarin de SVGvoorstelling van alle auto’s in deze klasse.

Oef. 4.7 ▸ Schrijf een main functie die een ruimte aanmaakt met twee auto’s erinen die de SVG visualisatie hiervan toont (zoals je met svg.py gedaanhebt). Bekijk dat je in Firefox effectief het juiste prentje te zien krijgt (inSVG prentjes loopt de Y-as van boven naar beneden).Tip: Als je in de webbrowser niet het gewenste resultaat ziet, probeerdan je programma uit te voeren in een terminal (met het commando./svg.py) . Zo zie je vaak sneller wat er mis is.

91

Page 92: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

A.5 Overerving: bewegende auto’s

Uiteindelijk willen we niet alleen maar een momentopname van de po-sitie van verschillende auto’s kunnen voorstellen, maar ook volgen hoedeze zich bewegen, zodat we bijvoorbeeld ook de snelheid van een autokunnen berekenen. Hiervoor maken we een subklasse die de functio-naliteit van Auto uitbreidt.

Oef. 5.1 ▸ Om te beginnen, geven we de klasse Auto een extra methodeverplaats, waarmee we de positie van de auto kunnen aanpassen.Deze methode krijgt een nieuwe positie (een Punt object) als argumenten vervangt de vorige positie van de auto door deze nieuwe.

Oef. 5.2 ▸ Maak een auto die begint op positie (30,40) en die zich daarnaverplaatst naar (50,40). Print de auto voor en na de verplaatsing af omzeker te zijn dat alles correct is.

Nu zijn onze auto’s misschien al wel verplaatsbaar, maar we zijn nogaltijd niet in staat om bijvoorbeeld hun gemiddelde snelheid teberekenen, aangezien de auto’s onmiddellijk vergeten wat hun vorigepositie was. We gaan nu een nieuwe klasse maken, waarmee we auto’skunnen maken die hun vorige posities wel onthouden. Om geen codeonnodig te moeten dupliceren, laten we deze klasse overerven van debestaande klasse Auto.

Oef. 5.3 ▸ Maak een klasse AutoMetGeschiedenis die overerft van de klasseAuto. In deze klasse, moet je twee methodes van de oorspronkelijkeklasse Auto overschrijven: (1) je maakt een nieuwe constructor, diehetzelfde doet als de constructor van Auto maar daarnaast ook nog eenattribuut geschiedenis maakt met daarin een lijst die de huidigepositie van de auto bevat; (2) je overschrijft de methode verplaats,zodat deze niet alleen de huidige positie vervangt door de nieuwe, maarook de nieuwe positie toevoegt aan de geschiedenis-lijst.

Oef. 5.4 ▸ Pas je main-functie aan zodanig dat er nu een AutoMetGeschiedenisobject wordt aangemaakt ipv. een gewone Auto. Natuurlijk zal je noggeen verschil zien in hoe dit object van deze nieuwe klasse zichgedraagt in je main-functie.

Oef. 5.5 ▸ Zorg ervoor dat een AutoMetGeschiedenis wordt afgeprint in hetvolgende formaat:

onthoudende auto op (10,20) van grootte (30,20) in kleur red

(Merk op dat dit, op het eerste woord na, identiek is aan destringvoorstelling van een gewone auto.)

Oef. 5.6 ▸ Voeg aan je klasse AutoMetGeschiedenis een methode snelheden toedie een lijst teruggeeft met daarin de snelheden die de auto behaaldheeft tussen de verschillende punten in zijn geschiedenis. Met anderewoorden, als de geschiedenis van de auto bestaat uit punten

[p0, p1, . . . , pn]

92

Page 93: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

dan produceert deze methode als resultaat de lijst

[d0, d1, . . . , dn−1]

waarbij elke di = δ(pi, pi+1). Laat in je main-methode een auto rijden van(0,10) naar (20,10) naar (50,10) naar (80,10) en controleer of zijn lijstmet snelheden inderdaad correct is.

Oef. 5.7 ▸ Voeg aan je klasse AutoMetGeschiedenis een methodegemiddeldeSnelheid toe, die de gemiddelde snelheid van de auto(dwz. de gemiddelde waarde van de lijst met snelheden) teruggeeft.Controleer of de gemiddelde snelheid van de auto uit de vorige oefeninginderdaad correct is.

A.6 Visualisatie van oude toestanden

We hebben nu al een datastructuur waarmee we het traject dat eenauto aflegt, kunnen bijhouden. Nu gaan we dit ook visualiseren.

Als eerste stap, maken we het mogelijk om de toestand van een auto (enbij uitbreiding ook een ruimte) op eender welk tijdstip te (her-)bekijken.

Oef. 6.1 ▸ Breidt de klasse AutoMetGeschiedenis uit met een methodereset(i) die de positie van de auto terugzet naar het i-de element uitzijn geschiedenis. Geef het argument van deze methode eendefault-waarde, zodat er naar het allereerste tijdstip 0 wordtteruggegaan als de methode wordt opgeroepen zonder argument.

Oef. 6.2 ▸ Geef de klasse Ruimte eenzelfde methode die elke auto uit de ruimteterugzet naar zijn i-de positie en voorzie ook hier een default-waarde 0.

Oef. 6.3 ▸ Maak in je main-functie een ruimte met twee auto’s die elk vier positieshebben en controleer (door je ruimte te resetten en af te printen) datalles correct werkt.

We gaan nu de toestand op het i-de moment visualiseren met een SVGprentje.

Oef. 6.4 ▸ Maak in je main-functie nog steeds dezelfde ruimte met twee auto’s aanen zorg dat er een SVG-prentje geproduceerd wordt dat de toestandvan de ruimte op het 2e tijdstip toont. Bekijk dit in Firefox. Controleerdat je ook voor andere tijdstippen het juiste resultaat krijgt, door hetcijfer 2 te veranderen in iets anders.

In plaats van telkens onze code te moeten aanpassen als we een andertijdstip willen zien, is het beter om hiervoor een webformulier te maken.Dit doen we nu in een aantal stappen.

Oef. 6.5 ▸ Zorg ervoor dat je Python-programma een query-string van de vormtijdstip=i aanvaardt, en de waarde i doorgeeft als argument aan dereset functie. Het resultaat moet met andere woorden zijn dat als jesurft naar volgende URL, je de toestand van de ruimte op het derdetijdstip te zien zal krijgen:

93

Page 94: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

http://laurel.local.thomasmore.be/~aXXX/svg.py?tijdstip=3

Je kan hiervoor vertrekken van het voorbeeld in Sectie 4.1. Mogelijkheb je ook nood aan de functie int(string) die een string zoals "7"omzet in een getalwaarde.

Oef. 6.6 ▸ Maak een webpagina auto.html met daarop een formulier. In ditformulier, moet zich een tekstveldje bevinden, waarin een gebruiker hettijdstip dat hij wenst te zien, kan invullen. Het formulier moetdaarnaast natuurlijk ook een submit-knop hebben. Als de gebruikerhierop klikt, dan wordt het getal dat hij heeft ingevuld doorgestuurdnaar je Python-programma, zodat de gebruiker een afbeelding krijgtmet daarop de toestand op het juiste tijdstip.

Ons programma rekent er nu nog op dat de gebruiker altijd “goede”invoer zal leveren. Dat is natuurlijk een beetje een naïeveveronderstelling.

Oef. 6.7 ▸ Bekijk eens in Firefox wat er gebeurt als je een getal ingeeft dat groteris dan het aantal posities van de auto’s. Bekijk ook eens (vanuit eenterminal) wat er gebeurt als je vanuit je main-functie dereset-methode oproept met dit getal.

Oef. 6.8 ▸ Voeg aan de methode reset in de klasse Auto een test toe diecontroleert of het opgegeven tijdstip wel degelijk in het juiste intervalvalt. Indien dit niet zo is, moet je een IndexError uitzondering raisenmet de boodschap “Ongeldig tijdstip”. Bekijk in je editor of je dezeuitzondering inderdaad ziet verschijnen.

Oef. 6.9 ▸ We gaan er nu voor zorgen dat als de gebruiker een verkeerd tijdstipingeeft, hij gewoon de toestand op tijdstip 0 te zien krijgt. Om dit voormekaar te krijgen, moet je de IndexError uitzondering opvangen opde juiste plaats in de klasse Ruimte.

Oef. 6.10 ▸ Controleer dat dit goed werkt. Bekijk ook eens wat er gebeurt als degebruiker in het tekstveld iets zou invullen dat helemaal geen getal is.Hoe moet je je code aanpassen om ook in dit geval de toestand optijdstip 0 te tonen? (Je hoeft hiervoor zelf geen nieuwe uitzondering opte steken, maar je kan gewoon de uitzondering die Python genereertopvangen. Eigenlijk hadden we dit ook al kunnen doen met deIndexError, maar dan had je natuurlijk het raisen van eenuitzondering niet kunnen inoefenen.)

A.7 Dynamische visualisatie van een ruimte

Nu we de verschillende toestanden van een ruimte individueel kunnenvisualiseren, gaan we deze samennemen om hiermee een filmpje temaken.

De module jve bevat een functie movie die je hiervoor kan gebruiken.De volgende code toont bijvoorbeeld een filmpje van een blauwe bal dienaar rechts rolt:

94

Page 95: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

#!/usr/local/bin/pythonimport jve

svg = """<svg id="frame1" xmlns="http://www.w3.org/2000/svg">

<circle cx="20" cy="20" r="10" fill="blue" /></svg><svg id="frame2" xmlns="http://www.w3.org/2000/svg">

<circle cx="30" cy="20" r="10" fill="blue" /></svg><svg id="frame3" xmlns="http://www.w3.org/2000/svg">

<circle cx="40" cy="20" r="10" fill="blue" /></svg>

"""

print jve.movie(svg)

Pas jouw programma aan zodat op dezelfde manier een filmpje getoondwordt:

Oef. 7.1 ▸ Vervang de instructie jve.http(...) door jve.movie(...). Als je jeprogramma nog eens uitvoert in een terminal, zal je zien dat dezefunctie jve.movie nog wat meer code rond jouw SVG tagsgeproduceerd heeft. Deze dient om ervoor te zorgen dat je verschillendeSVG elementen als opeenvolgende frames van een filmpje kan afspelen.Daarvoor moeten deze wel “genummerd” zijn met de juisteid-attributen (zoals in het voorbeeld hierboven).

Oef. 7.2 ▸ Zorg ervoor dat de svg-methode in de klasse Ruimte een extraargument i aanvaardt, en dit gebruikt om aan het <svg> element dathet produceert een attribuut id="framei" toe te voegen.

Oef. 7.3 ▸ Maak in de klasse Ruimte een bijkomende methode alle_svgs(n) diede SVG elementen voor tijdstippen 0 tot en met n − 1 verzamelt, achterelkaar plakt en teruggeeft.

Oef. 7.4 ▸ Laat je main-methode alle_svgs(4) teruggeven en controleer dat je inFirefox effectief een filmpje te zien krijgt.

A.8 Regio’s in het beeld

Een interessante vraag om te bekijken, is of de snelheid van auto’s insommige regio’s (bv. waar er een zebrapad is) hoger of lager ligt danelders

Een regio zal een rechthoekig gebied zijn in de ruimte, dat we zullenvoorstellen door een hoekpunt en een lengte–breedte paar. Dit komt jewaarschijnlijk bekend voor, aangezien we auto’s natuurlijk ook op dezemanier voorspellen. Om duplicatie van code te vermijden, zullen wedan ook een hiërarchie van drie klassen maken: een superklasseRechthoek met zowel een subklasse Auto als een subklasse Regio.

95

Page 96: NETWERKEN en OBJECTORIËNTATIE - KU Leuvenjoost.vennekens/DN/2BaNet... · 2017-10-03 · FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN ! CAMPUS DE NAYER N ETWERKEN en O BJECTORIËNTATIE

Oef. 8.1 ▸ Maak een klasse Rechthoek. Elke rechthoek bestaat uit(x, y)-coördinaten van de linkerbenedenhoek en een lengte–breedtepaar. Er moet een constructor zijn om dergelijke rechthoeken mee aante maken, en een methode die nagaat of een bepaald punt zich al danniet in de rechthoek bevindt. We kunnen nu een auto zien als eenspeciaal soort rechthoek, namelijk één die zich kan verplaatsen en dieook een kleur heeft. Maak daarom van de klasse Rechthoek eensuperklasse van de klasse Auto en plaats de verschillende methodes inde juiste klasse.

Oef. 8.2 ▸ Maak nu een tweede subklasse Regio van Rechthoek. Een Regioheeft, naast de attributen van een rechthoek, ook nog een naam.Daarnaast moet een regio ook een methode svg hebben die de regiotekent als een rect met een fill van none en een stroke van black.

Oef. 8.3 ▸ Breid de klasse Ruimte uit, zodanig dat elke ruimte ook een lijst vanregio’s bijhoudt. Dit is een lege lijst op het moment dat het Ruimteobject wordt aangemaakt. Er moet een methode voegRegioToe zijn omlater regio’s toe te voegen. Zorg ervoor dat de regio’s eveneens getekendworden in de SVG voorstelling van een ruimte.

Oef. 8.4 ▸ Maak een ruimte met daarin een auto van 30 op 20 die van (0,10) naar(20,10) naar (50,10) naar (80,10) rijdt, en een even grote auto die van(100,100) naar (90,100) naar (80,100) naar (70,100) rijdt. Voeg nu ookeen regio “bovenkant” toe op (0,0) met lengte 100 en breedte 60, en eentweede regio “onderkant” die even groot is maar op (0,60) begint. Bekijkde visualisatie van deze ruimte om na te gaan dat alles correct is.

Oef. 8.5 ▸ Om het onszelf niet te moeilijk te maken, gaan we ervan uit dat eenauto altijd binnen dezelfde regio blijft. Schrijf in de klasse Ruimteeen methode gemiddeldeSnelheidVoorRegio(regio) die degemiddelde snelheid van alle auto’s in de regio berekent. Hiervooroverloop je alle auto’s en test dan voor elke auto of één van zijnhoekpunten in de regio valt. Zo ja, dan neem je de gemiddelde snelheidvan die auto mee in de berekening; anders negeer je de auto.

Oef. 8.6 ▸ Voeg aan de klasse Ruimte een methode analyseerRegios toe, diealle regio’s uit de ruimte overloopt en voor elke regio zijn naam afprint,gevolgd door de gemiddelde snelheid van de auto’s in die regio.

96