Göm meny
Gäller för: VT21

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 klassen Pet 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 klassen Pet.
    • 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 med print() ser korrekt ut
  • Berätta skillnaden mellan klassen Pet och en instans av klassen Pet
  • 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är self 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.
  • Kör skriptet som testar klassen Vector2D för att visa att koden fungerar. Visa sedan koden för klassen Vector2D samt koden för skriptet som testar klassen. Gå igenom koden som testar klassen Vector2D 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.

  1. Alla import-satser placeras högst upp i filen
  2. Eventuella globala konstanter
  3. Funktionsdefinitioner
  4. 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 klassen Pet ska man kunna välja om man skickar med ett namn eller inte. Instanser ska alltså kunna alltså skapas med antingen Pet("Fido") och Pet(). Inga leksaker ska skickas med från början.
  • add_toy(toy): Argumentet toy ska vara en sträng med namnet på en leksak. Användning av metoden add_toy lägger till värdet i toy till listan self.toys. Inga dubletter av en leksak får läggas till (de ignoreras). Exempelanvändning där pet1 är refererar till ett Pet-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änder print() för att skriva ut en variabel. Exempelanvändning där pet1 refererar till ett Pet-objekt.
    • för name == "Fido", kind == "hund" och toys == [] ska pet1.__str__() returnera strängen "Fido är en hund som inte har några leksaker."
    • för name == "Fido", kind == "hund" och toys == ["boll", "nalle"] ska pet1.__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 med print() eller konverterar till sträng med str().

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å vektorn
  • add(v1): Addera komponenterna (x och y) från vektorn v1 till instansens egna vektorkomponenter. v1 ska inte ändras.
  • add_to_new(v1): Returnera en ny instans av Vector2D som representerar den vektor som fås när vektorn v1 adderas till den egna instansen. Varken den egna instansen eller v1 ska ändras.
  • is_longer_than(v1): Returnera True om den egna instansen är längre än Vector2D-instansen v1. Returnera False om den egna instansen är lika lång eller kortare än Vector2D-instansen v1.
  • create_unit_vector(): Returnera en ny instans av klassen Vector2D som är enhetsvektorn för den existerande instansen. Den egna instansen ska inte ändras.
  • __str__(): Returnera en strängrepresentation av Vector2D-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.

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.

row_layout

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åller tkinter.Label-objekt
  • frame_height: Höjden (int) på den Frame som kvadraterna ligger i.
  • frame_width: Bredden (int) på den Frame 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