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:
- Uppgift 1: Att-göra-program
- Uppgift 2: Utöka programmet med användare
- Koden ska följa Kod- och kommentarskraven
- Koden ska vara körbar som ett självständigt skript
För 3 poäng på Temauppgift 6 ska följande göras:
- Uppgift 1: Att-göra-program
- Uppgift 2: Utöka programmet med användare
- Uppgift 3: Utöka programmet med deluppgifter
- Koden ska följa Kod- och kommentarskraven
- Koden ska vara körbar som ett självständigt skript
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.
- Alla import-satser placeras högst upp i filen (se också PEP8 - Imports för intern ordning)
- Eventuella globala konstanter
- Funktionsdefinitioner och klassdefinitioner
- Kod utanför funktioner, t.ex. anrop till en
main()-funktion och annan kod iif __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:
|
|
Här är en godkänd variant:
|
|
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
|
|
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.pyellerpython3 skriptnamn.pyoch 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ändanew_task()frågar användaren vilken uppgift som ska läggas berTaskList-instansen lägga till denshow_tasks()visar uppgifternamark_done()frågar användaren vilken uppgift som ska markeras som klar och ser till att det blir gjortmain()innehåller interaktionsloopen och anropas sist från__init__när en instans avTodoAppskapas
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_counterhåller koll på hur många instanser avTasksom skapats (alternativt kan en klassvariabel iTaskanvändas)create_task()skapar och sparar en instans avTask.mark_done()ändrar rätt instans avTaskså att den blir markerad som klar (använd metodenmark_doneiTask).
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_idunikt ID för en uppgift, visas när uppgifter visas för användaren_descriptionbeskrivning av uppgiften_donefår värdetTrueom uppgiften är utfördmark_donemarkerar den egna instansen som klarget_idreturnerar instansens IDis_donereturnerar 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:
- Vad är lämpliga namn på relationerna
XochY? - Vad innebär det att relationerna
YochZdelar linje? - Behöver
Zvara 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
- 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.
- Deluppgifter skall representeras som
- 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.
- 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).
- 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
- Visa att ni kan köra ert program, att programmet uppfyller de krav på funktionalitet som ställts och att
pycodestyleochpydocstyleinte ger några anmärkningar. - Berätta vilka instansvariabler och metoder ni själva lagt till era klasser.
- Gå igenom er kod och förklara var instanser av de olika klasserna skapas. Ni behöver inte gå igenom all kod ni skrivit.
- 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
- Kör koden för Uppgift 2.
- Förklara hur
User-klassen används i er kod. - Besvara frågorna om UML-diagrammet och förklara hur ni tänker. Här kommer de igen:
- Vad är lämpliga namn på relationerna
XochY? - Vad innebär det att relationerna
YochZdelar linje? - Behöver
Zvara en namngiven relation och vad skulle det innebära om så var fallet?
- Vad är lämpliga namn på relationerna
- 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?
- Var används
Uppgift 3
- Kör koden för Uppgift 3.
- Visa hur deluppgifter representeras i
Task-klassen. - Visa hur en struktur av uppgifter och deluppgifter hanteras vid klarmarkering och utskrift.
- 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
