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

Temauppgift 6

I Temauppgift 6 kommer vi att fortsätta öva på att skapa egna klasser. Denna gång ska vi dock lagra instanser av klasser i andra instanser, samt skapa metoder som delegerar vidare den uppgift som ska lösas.

Temauppgiften består av 2 uppgifter för 1 poäng, 3 uppgifter för 3 poäng. Lös uppgifterna i ordning.

Redovisning, inlämning och kompletteringar

  • Allmän information om den muntliga redovisningen, samt eventuella kompletteringar kan ni läsa om sidan Redovisning.
  • Ni får antingen 1 poäng, 3 poäng eller Komplettering på inlämningar som hör ihop med temauppgift 4-6. Vid Komplettering får ni instruktioner om vad som ska kompletteras.

Uppgiftsnivåer och betyg

Se avsnittet om LAB2 på sidan Examination & Deadlines för information om kraven för att få VG på kursmomentet LAB2 (Temauppgift 4-6). Se avsnittet om att uppgradera temauppgiftsbedömning på sidan Examination & Deadlines om ni vill uppgradera bedömningen.

Poäng

För 1 poäng på Temauppgift 6 ska följande göras:

För 3 poäng på Temauppgift 6 ska följande göras:

Kodkrav

  • Koden ska följa PEP 8.
  • Moduler (själva filen), funktioner, klasser och metoder ska dokumenteras enligt PEP 257.
  • Koden ska följa koddispositionen nedan.
  • Koden ska utföra uppgifterna enligt respektive uppgiftsbeskrivning.
  • Koden ska vara uppdelad i olika filer för respektive uppgift om inget annat anges.

För hjälp med kontroll av PEP, se PEP 8 och PEP 257.

Krav på koddisposition

För att göra det lättare att hitta i koden 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 (se också PEP8 - Imports för intern ordning)
  2. Eventuella globala konstanter
  3. Funktionsdefinitioner och klassdefinitioner
  4. Kod utanför funktioner, t.ex. anrop till en main()-funktion och annan kod i if __name__ == "__main__":-block.

Krav på kommentarer och namngivning av funktioner och variabler

  • Alla satser som består av icke-trivialt matematiskt uttryck ska ha en tillhörande kommentar som förklarar vad som beräknas. (Vad som räknas som icke-trivialt varierar med kontexten. Inom machine learning eller datorgrafik är tröskeln markant högre för vad som räknas som icke-trivialt. En bra tumregel är “behövde jag tänka efter innan jag skrev ned formeln”.)
  • Alla satser som består av fler än en rad ska ha en tillhörande kommentar eller docstring. Dvs. if-satser, loopar, funktioner och metoder. Kommentaren ska inte vara en “innantilläsning” av koden den hör till. Använd rätt notation (dvs. """ för dokumentationssträngar, # för kommentarer i löpande kod, se PEP8 och PEP257).
    • Anmärkning: Kommentarskravet reflekterar inte nödvändigtvis kommentarskrav “i verkligheten” utan finns som krav i kursen för att ni ska öva er på att skriva kommentarer som förklarar kod. I verkligheten är det viktigare att koden är “självdokumenterande” med bra namn på variabler och funktioner, och att kommentarer bara används sparsamt när det verkligen behövs.
  • Funktioner och variabler ska vara döpta på ett bra sätt, dvs. att de är beskrivande och reflekterar innehåll/funktion, och vara på Engelska. 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). Se PEP8 - Naming Conventions för mer detaljer.

Här är icke-godkänd kod:

1
2
3
4
5
6
7
8
v = 0
q = 0
u = [19, 21, 24, 20]

# för alla element t i u, lägg till t till v
for t in u:
    v += t
q = v/len(u)

Här är en godkänd variant:

1
2
3
4
5
6
7
8
9
sum_age = 0
avg_age = 0
ages = [19, 21, 24, 20]

# beräkna summan av alla åldrar
for age in ages:
    sum_age += age
# beräkna snittet av alla åldrar
avg_age = sum_age/len(ages)

Ett undantag är väletablerade matematiska variabler som t.ex. när x och y används för en enskild uppsättning x- och y-koordinater. Om ni har olika separata objekt med egna x- och y-koordinater bör ni dock markera detta (dvs. återanvänd inte samma variabelnamn för olika ändamål):

Ej godkänd kod

1
2
3
4
5
6
x = 100
y = 0
hero.set_position(x, y)
x = 500
y = 0
monster.set_position(x, y)

Undvik hårdkodade värden

Minimera användningen av “hårdkodade” värden, dvs. litteraler i icke-triviala uttryck, använd istället variabler eller konstanter med förklarande namn. Det går självklart bra att använda litteraler i enkla uttryck som t.ex. uppräkning av en loopvariabel (loopvar += 1) eller access av första eller sista element i en sekvens (seq[0] eller seq[-1]).

Som tumregel kan ni tänka att om ni använder samma litteral på fler än ett ställe, eller om det är en sifferlitteral som inte är -1, 0 eller 1, så ska ni ersätta det med en variabel.

En annan anledning att byta ut ett hårdkodat värde mot en variabel är för att enklare kunna experimentera med olika värden genom att ändra variabelvärden som samlats på ett smidigt ställe i koden.

Om ni tänker “Men det är så svårt att komma på vad vi ska döpa den till” så ska ni också tänka på att det oftast är ännu svårare för en utomstående att förstå vad syftet med värdet är om det inte har ett förklarande namn.

Användning av hårdkodade siffervärden (även kallade för “magic numbers”) ses generellt sett som ett anti-mönster, dvs. ett mönster man ska undvika. Läs mer om detta på t.ex. Wikipedia.

Ej godkänd kod

kontosaldo = 2000
att_betala = 500 - (500 * 0.1)
att_betala += 240 - (240 * 0.1)
kontosaldo -= att_betala

Godkänd kod

kontosaldo = 2000
pris_godis = 500
pris_godispåse = 240
rabatt = 0.1
att_betala = pris_godis - (pris_godis * rabatt)
att_betala -= pris_godispåse - (pris_godispåse * rabatt)
kontosaldo -= att_betala

Rekommendation: Dela upp er kod

  • 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 separat funktion/metod)
    • 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/metoder när den blir för lång

Som tumregler, försöka se till att era funktioner

  • endast gör en sak
  • inte är längre än 15 rader (gärna kortare) exklusive kommentarer

Använd __str__ och __repr__ på rätt sätt

  • Se till att använda metoden __str__ på rätt sätt, dvs. att om en instans av en klass t.ex. ska skrivas ut, så ska klassen implementera __str__ och den ska användas. Om en samling, t.ex. en lista, som innehåller instanser av en klass ska skrivas ut så ska klassen implementera __repr__.

Självständigt körbara skript

  • Se till att koden är körbar som ett självständigt skript. Dvs. koden ska kunna köras från kommandoraden med ./skriptnamn.py eller python3 skriptnamn.py och starta programmet. Läs mer om hur man åstadkommer detta här.

Uppgift 1: Att-göra-program

För att hålla isär filerna som hör ihop med Uppgift 1 från de som hör ihop med Uppgift 2, ge filerna för uppgift 1 suffixet _uppg1, t.ex. todo_uppg1.py. Ni ger sedan filerna som hör ihop med uppgift 2 suffixet _uppg2, t.ex. todo_uppg2.py, och motsvarande för uppgift 3.

Krav på funktionalitet

Med skriptet ska användaren kunna göra följande:

  • visa vilka kommandon som användaren kan skriva
  • visa listan med saker som finns att göra, där varje uppgift har ett unikt ID, samt visa om uppgiften är gjord eller inte
  • kunna markera en uppgift som klar
  • avsluta skriptet med ett kommando
  • skriptet ska inte krascha vid felaktig input

Se föreläsningsbilderna och storseminariematerialet för tips både på skapande av textgränssnittet och objektorienterad struktur.

Viktigt! Håll kod som ansvarar för interaktion med användaren separat från kod som modellerar data. Dvs. kod som som har med användargränssnittet att göra ska endast ligga i klassen TodoApp.

Klassdiagram Uppgift 1

Programmet ska använda nedanstående klasser och de angivna relationerna ska finnas mellan klasserna. Klasserna ska innehålla minst de angivna metoderna och instansvariablerna.

Viktigt! Håll kod som ansvarar för interaktion med användaren separat från kod som modellerar data. Dvs. kod som som har med användargränssnittet att göra ska endast ligga i klassen TodoApp.

Metoder som __init__, __str__ och __repr__ ritas normalt inte ut i klassdiagram. Alla klasser ska dock ha en __init__-metod och för felsökning är det också lämpligt att implementera __str__ och __repr__.

Klassen TodoApp

OBS! Det är klassen TodoApp som innehåller all kod som har med det textbaserade gränsnittet att göra. Dvs. ingen kod som direkt bidrar till det textbaserade användargränssnittet får skrivas i klasserna TaskList eller Task. Mao. ingen kod som ställer en fråga till användaren som ska besvaras får skrivas i någon metod i TaskList eller Task. All interaktionskod ska finnas i klassen TodoApp.

  • show_commands() skriver ut vilka kommandon som användaren kan använda
  • new_task() frågar användaren vilken uppgift som ska läggas ber TaskList-instansen lägga till den
  • show_tasks() visar uppgifterna
  • mark_done() frågar användaren vilken uppgift som ska markeras som klar och ser till att det blir gjort
  • main() innehåller interaktionsloopen och anropas sist från __init__ när en instans av TodoApp skapas

Klassen TaskList

Klassen TaskList är vår modell för en lista av uppgifter och används för att representera data för en uppgiftslista. Klassen lagrar godtyckligt många instanser av klassen Task. För läsbarhetens skull anges inte metodargumenten i listan nedan (se klassdiagrammet ovan istället).

  • _task_counter håller koll på hur många instanser av Task som skapats (alternativt kan en klassvariabel i Task användas)
  • create_task() skapar och sparar en instans av Task.
  • mark_done() ändrar rätt instans av Task så att den blir markerad som klar (använd metoden mark_done i Task).

Klassen Task

Klassen Task är vår modell för en uppgift och används för att representera data för en uppgift som t.ex. dess beskrivning och om den är utförd eller inte.

  • _task_id unikt ID för en uppgift, visas när uppgifter visas för användaren
  • _description beskrivning av uppgiften
  • _done får värdet True om uppgiften är utförd
  • mark_done markerar den egna instansen som klar
  • get_id returnerar instansens ID
  • is_done returnerar huruvida instansen är markerad som klar

Exempelkörning Uppgift 1

Notera att programmet startas genom att skriva ./todo_uppg1.py i terminalen.

$ ./todo_uppg1.py
Ange kommando (q=avsluta, ?=hjälp): ?
Kommandon: ny, visa, klar, ?
Ange kommando (q=avsluta, ?=hjälp): ny
Beskriv uppgiften: Tvätta
Du skrev 'Tvätta', är det OK? [j/n]: j
Ange kommando (q=avsluta, ?=hjälp): ny
Beskriv uppgiften: vika tvätt
Du skrev 'vika tvätt', är det OK? [j/n]: j
Ange kommando (q=avsluta, ?=hjälp): visa
  0. [ ] Tvätta
  1. [ ] vika tvätt
Ange kommando (q=avsluta, ?=hjälp): klar
  0. [ ] Tvätta
  1. [ ] vika tvätt
Vilken uppgift ska markeras som klar? 1
Ange kommando (q=avsluta, ?=hjälp): visa
  0. [ ] Tvätta
  1. [X] vika tvätt
Ange kommando (q=avsluta, ?=hjälp): q

Uppgift 2: Utöka programmet med användare

När ni är klara med ert program för Uppgift 1, utöka funktionaliteten så att ni kan skapa användare i programmet, samt ange användare för uppgifter. Kopiera era filer för Uppgift 1 och ge filerna som hör ihop med uppgift 2 suffixet _uppg2, t.ex. todo_uppg2.py.

Frivilligt: Importera klasserna från Uppgift 1 i era nya filer för Uppgift 2, istället för att kopiera koden. Låt sedan era nya klasser för Uppgift 2 ärva från klasserna i Uppgift 1 där det är lämpligt.

Krav på funktionalitet för Uppgift 2

  • Lägg till en klass för användare, klassen User.
  • Lägg till kommando för att skapa en användare. Det ska inte gå att skapa flera användare med samma användarnamn.
  • Lägg till kommando för att visa skapade användare.
  • Ändra i kommandot för att lägga till en uppgift så att man även måste ange användaren som ska utföra uppgiften.
  • Ändra i kommandot för att visa uppgifter så att den också visar vilken användare som blivit tilldelad till den uppgiften.
  • Lägg till kommando för att visa vilka uppgifter som en viss användare har blivit tilldelad.

Klassdiagram Uppgift 2

Programmet ska använda nedanstående klasser och de angivna relationerna ska finnas mellan klasserna. Klasserna ska innehålla minst de angivna metoderna och instansvariablerna.

Fundera över följande frågor:

  1. Vad är lämpliga namn på relationerna X och Y?
  2. Vad innebär det att relationerna Y och Z delar linje?
  3. Behöver Z vara en namngiven relation och vad skulle det innebära om så var fallet?

Viktigt! Håll kod som ansvarar för interaktion med användaren separat från kod som modellerar data. Dvs. kod som som har med användargränssnittet att göra ska fortfarande endast ligga i klassen TodoApp.

Exempelkörning Uppgift 2

$ ./todo_uppg2.py
Ange kommando (q=avsluta, ?=hjälp): ?
Kommandon: ny uppgift, ny användare, visa uppgifter, visa användaruppgifter, visa användare, klar, ?
Ange kommando (q=avsluta, ?=hjälp): ny uppgift
Beskriv uppgiften: tvätta
Inga användare finns, avbryter.
Ange kommando (q=avsluta, ?=hjälp): ny användare
Namn för användaren: ada
Ange kommando (q=avsluta, ?=hjälp): ny användare
Namn för användaren: bertil
Ange kommando (q=avsluta, ?=hjälp): ny användare
Namn för användaren: bertil
Användaren bertil finns redan.
Namn för användaren: cecilia
Ange kommando (q=avsluta, ?=hjälp): visa användare
Användare:
ada
bertil
cecilia
Ange kommando (q=avsluta, ?=hjälp): ny uppgift
Beskriv uppgiften: tvätta
Användare: ada
Ange kommando (q=avsluta, ?=hjälp): ny uppgift
Beskriv uppgiften: vika tvätt
Användare: bert
Felaktigt användarnamn!
Användare: bertil
Ange kommando (q=avsluta, ?=hjälp): visa uppgifter
  0. [ ] tvätta (ada)
  1. [ ] vika tvätt (bertil)
Ange kommando (q=avsluta, ?=hjälp): klar
  0. [ ] tvätta (ada)
  1. [ ] vika tvätt (bertil)
Vilken uppgift ska markeras som klar? 0
Ange kommando (q=avsluta, ?=hjälp): visa uppgifter
  0. [X] tvätta (ada)
  1. [ ] vika tvätt (bertil)
Ange kommando (q=avsluta, ?=hjälp): visa användaruppgifter
Användare: bertil
Uppgifter för användaren bertil:
  1. [ ] vika tvätt (bertil)
Ange kommando (q=avsluta, ?=hjälp):

Uppgift 3: Utöka programmet med deluppgifter (för 3 poäng)

Utöka funktionaliteten så att ni kan ge uppgifter deluppgifter.

Kopiera era filer för Uppgift 2 och ge filerna som hör ihop med denna extrauppgift suffixet _uppg3, t.ex. todo_uppg3.py.

Frivilligt: Importera klasserna från Uppgift 2 i era nya filer för Uppgift 3, istället för att kopiera koden. Låt sedan era nya klasser för Uppgift 3 ärva från klasserna i Uppgift 2 där det är lämpligt.

Krav på funktionalitet för Uppgift 3

  1. Lägg till ett kommando för att skapa en deluppgift.
    • Deluppgifter skall representeras som Task-objekt på samma sätt som vanliga uppgifter.
    • Dvs. även deluppgifter kan i sin tur ha deluppgifter, osv. och vi kan tänka på detta som en typ av nästling eller trädstruktur.
  2. När en uppgift som har deluppgifter klarmarkeras så ska alla dess deluppgifter, till godtycklig nivå av nästling (dvs. deluppgifter till deluppgifter, osv.), också markeras som klara oavsett om de tidigare markerats som klara eller ej.
  3. Lägg till ett kommando för att lista alla deluppgifter, på godtycklig nivå av nästling, till en uppgift. Uppgiften och dess deluppgifter ska visas på ett sätt som illustrerar hur de hänger samman (se exempel nedan).
  4. Fundera över hur 2. och 3. ovan skulle kunna implementeras som en högre ordningens funktion. Hur skulle den kunna fungera? Hur skulle de funktionsobjekt som denna högre ordningens funktion använder för att utföra 2. respektive 3. kunna se ut?

Tips

Ni bör använda er av rekursion för att hantera nästlade Task-objekt.

De flesta Task-objekt kommer vara både huvuduppgifter och deluppgifter, på samma sätt som en person i ett familjeträd oftast har både föräldrar och barn.

Alla Task-objekt bör innehålla en lista med Task-objekt som representerar dess deluppgifter. Om en uppgift inte har några deluppgifter kommer den listan helt enkelt att vara tom.

Alla Task-objekt bör också ha en referens till sin huvuduppgift. Om ett Task-objekt inte har någon huvuduppgift låter man den referensen vara None.

Lämpliga metoder där man kan vilja använda någon form av rekursion är t.ex. i Task.mark_done och TodoApp.show_subtasks.

Det kan vara lämpligt att låta metoden TodoApp.show_subtasks främst hantera interaktionen med användaren och låta en separat metod eller nästlad funktion göra själva utskriften (t.ex. TodoApp._print_subtasks). Det är i så fall denna andra metod eller nästlade funktion som bör vara rekursiv.

För att den rekursiva funktionen/metoden som sköter utskriften ska hantera indenteringsdjupet korrekt kan man med fördel låta denna ta ett argument (som binds till t.ex. en parameter depth) som multipliceras med strängen '├──' i början av varje utskrift.

Klassdiagram Uppgift 3

Programmet bör använda nedanstående klasser och de angivna relationerna kan finnas mellan klasserna. Klasserna bör innehålla minst de angivna metoderna och instansvariablerna.

Viktigt! Håll kod som ansvarar för interaktion med användaren separat från kod som modellerar data. Dvs. kod som som har med användargränssnittet att göra ska fortfarande endast ligga i klassen TodoApp.

OBS! All kod som som har med användargränssnittet att göra ska fortfarande endast ligga i klassen TodoApp.

Exempelkörningar Uppgift 3

Lägg till deluppgifter

$ ./todo_uppg3.py
Ange kommando (q=avsluta, ?=hjälp): ?
Kommandon: ny uppgift, ny deluppgift, ny användare, visa uppgifter, visa deluppgifter, visa användaruppgifter, visa användare, klar, ?
Ange kommando (q=avsluta, ?=hjälp): visa användare
ada
bertil
cecilia
Ange kommando (q=avsluta, ?=hjälp): visa uppgifter
0. [ ] baka kakor (cecilia)
1. [ ] tvätta (ada)
Ange kommando (q=avsluta, ?=hjälp): ny deluppgift
Ange huvuduppgift: 0
Beskriv uppgiften: handla ingredienser
Användare: cecilia
Ange kommando (q=avsluta, ?=hjälp): visa uppgifter
0. [ ] baka kakor (cecilia)
1. [ ] tvätta (ada)
2. [ ] handla ingredienser (cecilia) - deluppgift till 0

Klarmarkera uppgifter med deluppgifter

Ange kommando (q=avsluta, ?=hjälp): klar
0. [ ] baka kakor (cecilia)
1. [ ] tvätta (ada)
2. [ ] handla ingredienser (cecilia) - deluppgift till 0
3. [ ] torka (bertil) - deluppgift till 1
4. [ ] göra kaksmet (cecilia) - deluppgift till 0
5. [ ] grädda kakor (cecilia) - deluppgift till 0
6. [ ] vika (ada) - deluppgift till 1
7. [ ] tumla (bertil) - deluppgift till 3
8. [ ] hänga (bertil) - deluppgift till 3
Vilken uppgift ska markeras som klar? 1
Ange kommando (q=avsluta, ?=hjälp): visa uppgifter
0. [ ] baka kakor (cecilia)
1. [X] tvätta (ada)
2. [ ] handla ingredienser (cecilia) - deluppgift till 0
3. [X] torka (bertil) - deluppgift till 1
4. [ ] göra kaksmet (cecilia) - deluppgift till 0
5. [ ] grädda kakor (cecilia) - deluppgift till 0
6. [X] vika (ada) - deluppgift till 1
7. [X] tumla (bertil) - deluppgift till 3
8. [X] hänga (bertil) - deluppgift till 3

Visa deluppgifter till en uppgift

Ange kommando (q=avsluta, ?=hjälp): visa deluppgifter
Ange huvuduppgift: 1
1. [ ] tvätta (ada)
├──3. [ ] torka (bertil) - deluppgift till 1
├──├──7. [ ] tumla (bertil) - deluppgift till 3
├──├──8. [ ] hänga (bertil) - deluppgift till 3
├──6. [ ] vika (ada) - deluppgift till 1

Checklista för redovisning

Uppgift 1

  1. Visa att ni kan köra ert program, att programmet uppfyller de krav på funktionalitet som ställts och att pycodestyle och pydocstyle inte ger några anmärkningar.
  2. Berätta vilka instansvariabler och metoder ni själva lagt till era klasser.
  3. Gå igenom er kod och förklara var instanser av de olika klasserna skapas. Ni behöver inte gå igenom all kod ni skrivit.
  4. Assistenten ni redovisar för kommer att be er redogöra antingen för flödet för när en ny uppgift skapas och/eller flödet för när en uppgift markeras som klar.

Uppgift 2

  1. Kör koden för Uppgift 2.
  2. Förklara hur User-klassen används i er kod.
  3. Besvara frågorna om UML-diagrammet och förklara hur ni tänker. Här kommer de igen:
    1. Vad är lämpliga namn på relationerna X och Y?
    2. Vad innebär det att relationerna Y och Z delar linje?
    3. Behöver Z vara en namngiven relation och vad skulle det innebära om så var fallet?
  4. Gå igenom de tillägg ni gjort till koden:
    • Var används User-klassen?
    • Vad görs för att bara visa uppgifter som hör ihop med en specifik användare?

Uppgift 3

  1. Kör koden för Uppgift 3.
  2. Visa hur deluppgifter representeras i Task-klassen.
  3. Visa hur en struktur av uppgifter och deluppgifter hanteras vid klarmarkering och utskrift.
  4. Redogör för hur ni tänker kring att implementera klarmarkering och utskrift med hjälp av en högre ordningens funktion.

Sidansvarig: Johan Falkenjack
Senast uppdaterad: 2025-11-26