Laboration 5: Skapa egna datatyper (klasser) och jobba med objekt
I laboration 5 kommer ni öva på att skapa och använda egna klasser, skriva en algoritm som placerar kvadrater, samt öva på att skriva kommentarer och följa pythonstandarderna PEP8 samt PEP257.
Redovisning, inlämning och kompletteringar
- Information om den muntliga redovisningen, samt eventuella kompletteringar kan ni läsa om sidan Redovisning.
Checklistor att gå igenom vid redovisning
Redovisning Del 1
Uppgift 1
- Kör skriptet som testar klassen
Pet
för att visa att koden fungerar. Visa sedan koden för klassenPet
samt koden för skriptet som testar klassen. Peka ut var nedanstående punkter görs i skriptet. Berätta även vad era eventuella utskrifter säger om funktionaliteten hos klassenPet
.- minst tre instanser skapas, varav minst två ligger i en lista
- en instans av
Pet
kan ha noll eller fler leksaker - olika instanser av
Pet
har olika leksaker - visa att metoden
Pet.add_toy()
inte lägger till dubletter av leksaker - utskrift av en
Pet
-instans medprint()
ser korrekt ut
- Berätta skillnaden mellan klassen
Pet
och en instans av klassenPet
- Förklara vad en instansvariabel är och hur man kan läsa och ändra innehållet i en instansvariabel utanför en instans (dvs inte i en metod inuti klassen).
- Förklara varför argumentet
self
behövs när man definierar en metod. Visa exempel på närself
används i er kod. - Förklara när metoden
__str__()
används, visa var er testkod använder den.
Uppgift 2
- Visa koden för
Vector2D
och gör följande:- Berätta vad metoden
__init__()
gör. - Peka ut vilka instansvariabler som klassen
Vector2D
har. - Förklara hur instansvariablerna får sina värden när en instans av
Vector2D
skapas.
- Berätta vad metoden
- Kör skriptet som testar klassen
Vector2D
för att visa att koden fungerar. Visa sedan koden för klassenVector2D
samt koden för skriptet som testar klassen. Gå igenom koden som testar klassenVector2D
och visa att alla metoder testas.
Redovisning Del 2
Berätta om ni gjort Uppgift 1 eller Uppgift 2. Kör GUI:t och visa att er layoutfunktion fungerar för olika storlekar på kvadraterna och olika storlek på fönstret som kvadraterna ska placeras ut i.
Gå översiktligt igenom hur ni lagt upp koden för uppgiften. Ni behöver inte gå igenom varje enskild rad, utan berätta istället om den övergripande strategin och vilka delar av koden ansvarar för vad. T.ex.
- Var i koden placeras kvadraterna ut?
- Hur och när byter ni kolumn/rad?
- Hur vet ni att det finns plats kvar i fönstret?
- Var räknar ni ut var nästa kvadrat ska placeras ut?
Krav för godkänd kod
- Koden ska följa PEP8
- Moduler (själva filen), funktioner, klasser och metoder ska kommenteras enligt PEP257
- Koden ska följa koddispositionen nedan.
- Koden ska utföra uppgifterna enligt respektive uppgiftsbeskrivning.
- Koden ska vara uppdelar i olika filer enligt instruktionerna för respektive uppgift.
Krav på koddisposition
För att göra det lättare att hitta i koden är det bör den följa en genomtänkt koddisposition. Nedan är en vanlig disposition som används i många språk.
- Alla import-satser placeras högst upp i filen
- Eventuella globala konstanter
- Funktionsdefinitioner
- Kod utanför funktioner, t.ex. anrop till en
main()
-funktion.
Del 1: OOP syntax
- Uppgift 1: Definition och användning av en klass som representerar ett husdjur.
- Uppgift 2: Definition och användning av en klass som representerar en tvådimensionell vektor.
Del 2: Använda objekt: placera ut kvadrater
- Utplacering av kvadrater i rader eller kolumner.
Del 1
Filstruktur för uppgift 1 och 2
- Definiera alla klasser för både uppgift 1 och 2 i en gemensam fil, t.ex. filen
klasser.py
- Separata filer som demonstrerar uppgift 1 respektive uppgift 2 ska användas.
Uppgift 1: Husdjur
I uppgift 1 ska ni definierar klassen Pet
. Scenariot är att instanser av
klassen ska användas i ett ett text-baserat program som lagrar och bearbetar
information om husdjur.
Testning av klassen Pet
Skriv ett skript som testar klassen Pet
i annan fil än den som klassen är
definierad i (t.ex. i filen uppg1.py
). När detta skript körs ska följande göras:
- minst tre instanser av klassen
Pet
skapas - minst en
Pet
-instans ska refereras till explicit med en variabel (t.ex.hund = Pet()
) - minst två
Pet
-instanser ska ligga i en lista. - olika leksaker läggs till alla instanser som skapats
- försök att lägga till dubletter av leksaker ska göras (men inga faktiska
dubletter läggs till, se metoden
add_toy
nedan). - alla instanser skrivs ut med
print()
(t.ex.print(hunden)
)
Exempeltestning
hund1 = Pet()
hund1.name = "Pluto"
hund1.kind = "hund"
print(hund1)
hund1.add_toy("boll")
print(hund1)
katt1 = Pet("Misse")
katt1.kind = "katt"
katt1.add_toy("fjäder")
print(katt1)
katt1.add_toy("fjäder")
print(katt1)
katt1.add_toy("tygråtta")
print(katt1)
Exempelutskrifter
>>> print(pinne)
Fido är en vandrande pinne som har följande leksaker: boll
>>> print(fisk)
Nemo är en fisk som inte har några leksaker.
Instansvariabler i klassen Pet
Varje instans av Pet
ska ha kunna lagra följande information:
- instansvariabeln
name
ska lagra husdjurets namn som en som en sträng - instansvariabeln
kind
ska lagra vilket slags djur husdjuret är som en sträng - instansvariabeln
toys
ska lagra leksaker som husdjuret gillar i form av en lista av strängar, t.ex.[]
för inga leksaker,["boll"]
för en leksak, eller["nalle", "boll"]
för två leksaker.
Metoder i klassen Pet
__init__(self, name='')
: När man skapar en instans av klassenPet
ska man kunna välja om man skickar med ett namn eller inte. Instanser ska alltså kunna alltså skapas med antingenPet("Fido")
ochPet()
. Inga leksaker ska skickas med från början.add_toy(toy)
: Argumentettoy
ska vara en sträng med namnet på en leksak. Användning av metodenadd_toy
lägger till värdet itoy
till listanself.toys
. Inga dubletter av en leksak får läggas till (de ignoreras). Exempelanvändning därpet1
är refererar till ettPet
-objekt:pet1.add_toy("nalle")
__str__()
: Om en klass definierar metoden__str__()
används den automatiskt av Python för att konvertera instanser av klassen till en sträng. Detta händer t.ex. när man använderprint()
för att skriva ut en variabel. Exempelanvändning därpet1
refererar till ettPet
-objekt.- för
name == "Fido"
,kind == "hund"
ochtoys == []
skapet1.__str__()
returnera strängen"Fido är en hund som inte har några leksaker."
- för
name == "Fido"
,kind == "hund"
ochtoys == ["boll", "nalle"]
skapet1.__str__()
returnera strängen"Fido är en hund som har följande leksaker: boll, nalle"
- OBS! Metoden
Pet.__str__()
ska dock i regel inte anropas direkt utan det sköter Python när man t.ex. skriver ut medprint()
eller konverterar till sträng medstr()
.
- för
Klass-diagram
Nedan ser ni klassdiagrammet för klassen Pet
.
OBS! I vanliga fall är inte konstruktorn (hur man skapar en instans) med,
men den är med här för att exemplet ska vara explicit. Metoder som __str__()
brukar i regel inte heller finnas med, dvs endast de som användaren tagit fram
beskrivs.
Uppgift 2: Tvådimensionell vektor
I uppgift 2 ska ni definierar klassen Vector2D
. Scenariot är att instanser av
klassen ska användas för att representera en tvådimensionell vektor.
Testning av klassen Vector2D
Skriv ett skript som testar klassen Vector2D
i annan fil än den som klassen är
definierad i (t.ex. i filen uppg2.py
). När detta skript körs ska följande
göras:
- minst två instanser av klassen
Vector2D
skapas med slumpmässiga värden - alla metoder ska testas
- vid testning av metoderna ska följande information skrivas ut i terminalen:
- information om vilken metod som testas
- instansens innehåll innan metoden körs
- resultatet av testningen (t.ex. vad som returneras eller instansens innehåll efter att metoden körts)
Instansvariabler i klassen Vector2D
Varje instans av Vector2D
ska ha kunna lagra följande information:
- instansvariabeln
x
ska lagra ett flyttal - instansvariabeln
y
ska lagra ett flyttal
Metoder i klassen Vector2D
__init__(self, x, y)
:get_length()
: Returnera längden på vektornadd(v1)
: Addera komponenterna (x och y) från vektornv1
till instansens egna vektorkomponenter.v1
ska inte ändras.add_to_new(v1)
: Returnera en ny instans avVector2D
som representerar den vektor som fås när vektornv1
adderas till den egna instansen. Varken den egna instansen ellerv1
ska ändras.is_longer_than(v1)
: ReturneraTrue
om den egna instansen är längre änVector2D
-instansenv1
. ReturneraFalse
om den egna instansen är lika lång eller kortare änVector2D
-instansenv1
.create_unit_vector()
: Returnera en ny instans av klassenVector2D
som är enhetsvektorn för den existerande instansen. Den egna instansen ska inte ändras.__str__()
: Returnera en strängrepresentation avVector2D
-instansen.
Klass-diagram
Nedan ser ni klassdiagrammet för klassen Vector2D
.
OBS! I vanliga fall är inte konstruktorn (hur man skapar en instans) med,
men den är med här för att exemplet ska vara explicit. Metoder som __str__()
brukar i regel inte heller finnas med, dvs endast de som användaren tagit fram
beskrivs.
Del 2
I denna del ska ni skriva funktioner som får in en lista med
tkinter.Label
-objekt, samt höjd och bredd på den tkinter.Frame
som de
placeras ut i. tkinter.Label
-objekten ska placeras ut i sekvens, utan att
de nuddar varandra. Varje fyrkant ska synas helt och hållet.
I uppgift 1 placeras kvadraterna ut i rader. I uppgift 2 placeras kvadraterna ut i kolumner.
Krav på kommentarer och namngivning av funktioner och variabler
- Alla satser som består av fler än en rad ska ha en tillhörande kommentar. Dvs
if
-satser, loopar, funktioner och metoder. Kommentaren ska inte vara en “innantilläsning” av koden den hör till. Använd rätt kommentarsmarkering (t.ex."""
för funktions- metodkommentarer,#
för kommentarer i löpande kod, se PEP8 och PEP257). - Funktioner och variabler ska vara döpta på ett bra sätt, dvs att de är beskrivande och reflekterar innehåll/funktion. Låt funktionsnamn vara verb och variabelnamn vara substantiv. Undvik namn på enbart en bokstav (var beredda att kunna motivera varför ni valt just det namnet).
Här är icke-godkänd kod:
v = 0
q = 0
u = [19, 21, 24, 20]
# för alla element t, i listan, lägg till t till v
for t in u:
v += t
q = v/len(u)
Här är en godkänd variant
sum_age = 0
avg_age = 0
ages = [19, 21, 24, 20]
# räkna ut summan av alla åldrar
for age in ages:
sum_age += age
avg_age = sum_age/len(ages)
Försök att tänka på
- Minimera användningen av hårdkodade siffror, använd istället variabler med förklarande namn.
- Dela upp er kod i delfunktioner vid behov:
- när ni stöter på kod som ni upprepar, bryt ut koden (lägg koden i en funktion)
- skicka nödvändig information som argument till funktionen och låt den returnera eventuellt resultat, använd inte globala variabler
- dela även upp er kod i fler funktioner när den blir för lång
Kort om modulen tkinter
Modulen tkinter
innehåller funktionalitet för att skapa grafiska gränssnitt. I
Del 2 har tkinter
använts för att skapa det grafiska gränssnittet ni använder
för att testa er layout-funktion. Kvadraterna som placeras ut är instanser av
klassen tkinter.Label
. Den yta som de placeras ut på är ett objekt av klassen
tkinter.Frame
.
Kodskelett
När ni ska börja koda hittar ni de filer som hör till labben i
/courses/TDDE44/kursmaterial/laboration5/del2
. Ett exempel på en algoritm som
placerar ut kvadraterna slumpmässigt hittar ni i random-layout.py
. I den
ser ni hur en instans av klassen lab5.LayoutTester
skapas och får
layoutfunktionen som ett argument.
- DISTANSLÄGE: Ladda ner filerna här: lab5_del2.zip
OBS! Läs igenom random-layout.py
och testkör den för att orientera er i
kodupplägget.
Ni testkör den med:
$ python3 random-layout.py
Koden till uppgiftens grafiska gränssnitt hittar ni i filen lab5.py
. Ni
behöver inte ändra i den filen. Nedan ser ni hur gränssnittet ser ut och ett
exempel på en slumpmässig utplacering (oförändrad version av random-layout.py
har körts).
Utplacering av kvadraterna (tkinter.Label-objekt)
square.place(x=100, y=50)
I ovanstående exempel refererar variabeln square
till en
tkinter.Label
-instans. I exemplet används metoden .place()
med de
namngivna argumenten x
och y
för att placera ut instansen 100 enheter
från den vänstra kanten och 50 enheter från den övre kanten. Placeringen av
en Label
mäts från dess övre vänstra hörn. Origo i koordinatsystemet
som används ligger alltså högst upp till vänster, med en y-axel som pekar
nedåt, och en x-axel som pekar åt höger.
Kvadraternas storlek
För att ta reda på hur hög en tkinter
-widget är, kan man använda sig av
metoden .winfo_height()
. För bredden använder man .winfo_width()
. Om
square1
ett tkinter.Label
-objekt så får man dess bredd med
square1.winfo_width()
och dess höjd med square1.winfo_height()
.
Uppgift 1
Skriv funktionen row_layout
som placerar ut fyrkanterna radvis. Den första
fyrkanten placeras ut i det översta vänstra hörnet. Nästa fyrkant läggs till
höger om den första med ett förbestämt (välj själva) mellanrum mellan dem.
Algoritmen fortsätter så tills inga fler får plats på den raden. Nästa fyrkant
som placeras ut ska ligga under den första fyrkanten, d.v.s. på nästa rad.
Specifika krav på utplaceringen
- Hela det tillgängliga utrymmet ska användas, dvs om fönstret görs större bör fler kvadrater få plats vid nästa körning av layout-funktionen (att bara ändra fönstrets storlek startar inte om utplaceringen).
- Ingen del av kvadraten ska placeras utanför fönstret (så att det inte syns).
- Om det finns fler kvadrater som ska placeras än vad som faktiskt får plats behöver endast de som får plats placeras ut.
- Kvadraterna placeras ut så att mitten på varje kvadrat är i linje med alla andra kvadrater på samma rad och samma kolumn.
- Er algoritm behöver endast hantera utplacering av kvadrater där alla är lika stora. Den gemensamma storleken för alla kvadrater kan dock variera.
row_layout-funktionen
Er funktion row_layout
ska alltså ersätta funktionen random_layout
som finns
i kodskelettet och istället för att placera ut kvadraterna slumpmässigt, använda
den ovan beskrivna strategin.
En layout-funktion som ska fungera med testgränssnittet måste ta emot följande argument:
squares
: Lista som innehållertkinter.Label
-objektframe_height
: Höjden (int
) på denFrame
som kvadraterna ligger i.frame_width
: Bredden (int
) på denFrame
som kvadraterna ligger i.
När man trycker på knappen “Kör layoutfunktion” i gränssnittet körs skapas kvadraterna som ska layoutas enligt inställningarna i gränssnittet, sen anropas den layoutfunktion som angavs när gränssnittet när det skapades.
I det funktionsanropet skickar gränssnittet med listan av kvadrater, höjd och bredd som ni har att jobba med.
Det är alltså koden i filen lab5.py
som anropar er funktion. Ni behöver inte
själva anropa den.
Uppgift 2 (frivillig extrauppgift)
Gör en ny variant som klarar av att placera ut kvadrater av olika storlek, dvs
kvadratstorleken random
har använts.
Sidansvarig: Johan Falkenjack
Senast uppdaterad: 2020-04-07