Jag tycker det ser rätt bra ut. Jag anstränger mig lite extra nu för att hitta små anmärkningar. Det är egentligen främst "stilmässiga" sådana, och vissa onödigt krångliga konstruktioner som kommer från ovana — programmet ser robust ut! Du kan se detta som "överkursläsning" till en regnig dag om du vill (förutom min allra sista mening i hela detta inlägg, som jag tycker du bör läsa). Ska du programmera fler uppgifter i Python så kan det dock vara bra, då det visar några nya användbara koncept.
Jag vill också "varna" för att forumets design bygger väldigt mycket på höjden när man använder många separata [quote]- och [code]-taggar, så mina inlägg här ser meterlånga ut, men det är inte så mycket text som man kan tro.
De stilmässiga, officiella och generella riktlinjerna för Python är PEP-8. Det är lätt att tycka att "regler" på detta sätt är fåniga och främst i vägen, men de bygger ofta på erfarenheter av personer som kodat bra mycket längre än vi. Med erfarenhet så kommer man ofta själv på att reglerna är naturliga. Jag tyckte t ex att "4 blanksteg för indentering" istf tabbar var trams rätt länge, men efter några år så märkte jag att det var rätt bra ändå .
En överskuggande riktlinje är att det egentligen inte finns några "fel" vad gäller stil — bara "okunskap". Vet man varför reglerna finns så är det OK att ta beslutet att de bör frångås i enstaka fall där de inte gör saker bättre.
Skrivet av CarlKabul:
def print_i(msg, n=1): # Egen print-funktion med indentering för att slippa global indent-variabel
Python har en inbyggd speciell dokumentationssyntax för t ex funktioner, så kallade docstrings:
def print_i(msg, n=1):
"""Egen print-funktion med indentering för att slippa global indent-variabel"""
En radbrytning, indrag och tre citationstecken runt dokumentationssträngen, alltså. Detta gör att Python automatiskt kommer använda denna sträng som beskrivning av funktionen om t ex funktionen är definierad och en användare skriver `help(print_i)`. Det kan även hjälpa kodredigerarprogram, automatiskt dokumentationsgenerering, etc. Framför allt så är det det "naturliga" stället att hitta korta förklaringar av funktioner, klasser, metoder, etc.
Du har många "inline-kommentarer" (kommentarer på samma rad som kod). De ska enligt praxis försöka hållas till ett minimum. De försämrar snabbt läsbarheten om de är längre än ett ord eller två. Vi (iaf i västländer) är vana vid att läsa uppifrån och ner, från vänster till höger: en inlinekommenter gör att vi får två grupper av information på samma rad, och kommentaren är ofta intressantare att läsa innan koden. Dessutom kan det bli onödigt långa rader, vilket beroende på editor kan bli svårläst. Allmänt ska rader försökas hållas <80 tecken, av olika tekniska och historiska skäl.
Skrivet av CarlKabul:
print_i ("Kalenderprogram", 2)
Mellanslaget mellan `print_i` och parantesen ser udda ut. Dessutom används det inte konsekvent. Jag rekommenderar att alltid skippa detta (återigen enligt PEP-8, vilket jag antar bygger på matematiska konventioner för funktionsbegreppet).
Detta är bara ett invecklat sätt att skriva `print()`, vad jag kan se. `print()` utan argument skriver ut en blankrad. Även `print('')` gör detta, om man verkligen vill, men, tja.
Skrivet av CarlKabul:
print_i ("(1) Se denna månads kalender", 1)
Det är fritt att lägga till argumentet `1`, men samtidigt är det satt som "default"-argument till `print_i`, så om du utelämnar det andra argumentet till funktionen så händer samma sak som nu, men med mindre kod. Att ange mer information än nödvändigt är ofta onödigt, så jag skulle skippat det argumentet här (och på andra ställen).
Faktum är att du aldrig använder det andra argumentet till `print_i`, så du skulle helt kunna ta bort det i definitionen av funktionen utan att vara orolig (så länge du även tar bort alla onödiga argument när du kallar på funktionen).
Skrivet av CarlKabul:
resp = int(input(" {}: ".format(msg))) # Klamrarna lämnar utrymme för meddelandet (msg)
Denna typ av kommentar anser jag är helt rätt för dig att skriva nu, för att visa att du förstår programmets funktion. När man "kan" detta så bör man dock se till att kommentarer inte beskriver "uppenbara" saker, som t ex:
i += 1 # Lägger till 1 till i
och dylikt. Koden talar för sig själv, och sådana kommentarer är bara upprepande för någon med baskunskaper. Vad som är "baskunskap" och inte är svårt att kvantifiera, så det är lite luddigt. Känner man själv att man behöver dokumentation så är det bara att köra, givetvis, och det är lärorikt till en början.
Skrivet av CarlKabul:
# ------- Funktioner för felhantering/omstart av programmet -------
Dessa grupperingar tycker jag är bra för att visa hur du har insett hur funktioner ska representera logiska delar, och grupperar därefter.
Skrivet av CarlKabul:
def get_alternative(): # Skickar info om meddelande för val, tillåtet intervall samt intervallets felmeddelande till felhanteraren
return get_int("Välj alternativ", [1, 2], "Ej giltigt alternativ")
[…]
def get_run_again(): # Skickar info val för omstart resp. avslutning, tillåtet intervall samt intervallets felmeddelande till felhanteraren
return get_int("(1) Kör programmet igen\n (2) Avsluta \n Ange val", [1, 2], "Ej giltigt alternativ")
Dessa två funktioner är snarlika i funktion, men du hanterar dem olika. I den första så skriver du ut valen i `main()` och frågar bara "Välj alternativ" i `get_int`. I den andra inkluderar `get_run_again()` valen. Så som `get_int` är uppbyggd så kommer alltså den andra funktionen generera hela meddelandet vid felinput, men den första bara "Välj alternativ:". För att illustrera vad jag menar:
(1) Se denna månads kalender
(2) Ange år och månad
Välj alternativ: 7
Fel: Ej giltigt alternativ. Försök igen!
Välj alternativ:
kontra
(1) Kör programmet igen
(2) Avsluta
Ange val: 7
Fel: Ej giltigt alternativ. Försök igen!
(1) Kör programmet igen
(2) Avsluta
Ange val:
Utan att värdera vilket som är "bäst", eller spekulera i att omorganisera koden, så borde de iaf bete sig på samma sätt. Användaren ska bli så lite överraskad som möjligt.
Skrivet av CarlKabul:
# -------- Hämtar automatisk uppgift om dagens datum ---------
def today_year_month():
d = date.today() # Anropar "date" i "datetime" vid menyval 1
inYear = d.year
inMonth = d.month
return inYear, inMonth
Även här bör en "docstring" användas istf kommentaren innan funktionen.
Sedan ser jag att du gjort ett medvetet val (och medvetna val är inte "fel", som jag sa tidigare; bara okunskap är fel) att inte direkt skriva `return d.year, d.month`. Jag tycker att det vore klarare, och variabeltilldelningen känns lite onödig (minns att jag inte har några egentliga klagomål på koden, utan bara anstränger mig för att "klaga" på minsta lilla just nu ).
Skrivet av CarlKabul:
weekdayList = [[0, "Må", 6], [1, "Ti", 10], [2, "On", 14], [3, "To", 18],
[4, "Fr", 22], [5, "Lö", 26], [6, "Sö", 30]]
"Listor i en lista" skulle inte vara mitt naturliga val för denna datatyp. Om du vill ha strängarna som index så skulle en dictionary med en tuple för varje index vara bättre. I detta fall så noterar jag dock att du överhuvudtaget inte använder varken index 0 eller index 1 i dina inre listor: du hade helt enkelt kunna skriva ovanstående som:
weekdayList = (6, 10, 14, 18, 22, 26, 30)
[…] # och sedan förenkla din enda användning av weekdayList till:
print ("1".rjust(weekdayList[wDay]), end=" ")
Eller ah, nu förstår jag vad din offset i `weekdayList` är. Än smidigare än att hårdkoda dessa värden för veckodagarna som du gör nu vore att helt ta bort din `weekdayList` ur koden och i stället direkt skriva:
print ("1".rjust(6+wDay*4), end=" ")
…så slipper vi hela datatypsproblemet .
Skrivet av CarlKabul:
monthList = [[31, "januari"], [28+leapDay, "februari"], [31, "mars"], [30, "april"],
[31, "maj"], [30, "juni"], [31, "juli"], [31, "augusti"], [30, "september"],
[31, "oktober"], [30, "november"], [31, "december"]]
Här använder du däremot all data. En naturligare datatyp vore dock en tuple:
monthList = ((31, "januari"), (28+leapDay, "februari"), (31, "mars"), (30, "april"),
(31, "maj"), (30, "juni"), (31, "juli"), (31, "augusti"), (30, "september"),
(31, "oktober"), (30, "november"), (31, "december"))
Det spelar ingen roll i din vidare hantering i programmet (en tuple kallas på samma sätt som en lista). Paranteserna ger som sagt en tuple, som är en naturlig datatyp för statisk information. En lista inkluderar funktionalitet för att i efterhand ändras, dvs lägga till objekt, ändra deras värden, etc. Vi kommer troligen inte komma på fler månader fem rader längre ner i programmet, eller säga att juni har 31 dagar, så en tuple passar fint.
En tuple har en omärkbar prestandavinst mot en lista, men anledningen till att använda "rätt" datatyp är snarare att det visar att man har kontroll över språket. Det är bra att ha i ryggmärgen när man programmerar.
Skrivet av CarlKabul:
c = 0
while c < monthList[inMonth-1][0] : # Loop som räknar upp månadens dagar...
c += 1 # ...med variabeln c
Eftersom du vet hur länge loopen ska köra så bör du använda en `for`-sats i stället: `for` är egentligen bara en förenkling av en `while`-loop där man automatiskt sätter en iterationsvariabel och avslutar den enligt ett förutbestämt kriterium. Dina tre rader ovan (ja, alla tre, utan andra ändringar) kan helt ekvivalent (men kortare, mer naturligt och därmed mer läsbart) skrivas:
for c in range(1, monthList[inMonth-1][0]+1):
Det extra "krånglet" med `(1, […]+1)` tillkommer för att c inte ska börja på 0. En annan lösning är att skriva
for c in range(monthList[inMonth-1][0]):
och modifiera dina `if`-satser för att ta hänsyn till att c kommer börja på 0, och du kommer behöva skriva ut `c+1` i din kalender.
Gällande din första `if`-sats inne i loopen så kommer den kontrolleras varje gång, men alltid bara köras första varvet. Du vet ju att alla månader börjar med sin första dag, så det hade inte behövt vara i loopen överhuvudtaget, utan skulle kunna ligga ovanför, och sedan ändra loopens villkor till:
for c in range(2, monthList[inMonth-1][0]+1):
Du kan också helt skippa din `if c > 1:`-kontroll: till att börja med så hade den lika gärna kunnat vara bara en `else:` till `if c == 1:`, för vi vet ju att c antingen är 1 eller större än 1. Om första dagens utskrift flyttas ovanför loopen så behövs `if c > 1:` inte alls, eftersom den alltid kommer vara sann.
Du skriver ibland typ:
Skrivet av CarlKabul:
print ("\n", " ", end="")
det vill säga med dubbla strängargument till `print()`. Det är onödigt här och gör det svårare att se vad som händer. `print()` lägger automatiskt till `sep` mellan strängarna, men ovanstående hade ekvivalent kunnat skrivas:
vilket är kortare, men framför allt så ser man direkt hur slutsträngen kommer se ut, utan att behöva tolka in `sep`.
Skrivet av CarlKabul:
else :
print ("\n")
return
`while-else` är en ganska ovanlig konstruktion, och behövs sällan (t ex inte här). Din loop kommer stanna ändå enligt sitt krav, och du skulle kunna plocka bort `else` och minska indenteringen på nästa två kodrader, och programmet skulle fungera precis som nu. Du skulle även kunna plocka bort `return`, eftersom en funktion i Python returnerar automatiskt när den är "klar".
Nu var det inte mycket kvar att anmärka på, förutom:
Skrivet av CarlKabul:
# -------- Pseudokod --------
som inte är helt korrekt. "Pseudokod" är "låtsasprogramkod" som man använder för att göra en prototyp av ett program. Jag skulle kunna skriva pseudokod för att vispa grädde som:
bowl.add('vispgrädde')
while bowl.whip():
if bowl.consistency == 'runny':
continue
elif bowl.consistency == 'firm':
break
else:
print('Katten har ätit upp grädden!')
Det är alltså bara kod som "typ" är som riktig programkod, utan att skriva de faktiska funktionerna. Det är bara till för att beskriva en algoritm. Enbart uttrycket `main()` är inte pseudokod. Jag nämnde uttrycket tidigare för att själva `main()`-funktionens innehåll i Python ofta kan likna pseudokod om man gjort "rätt" tidigare i programmet, men det var ingen kommentar att hänga upp sig på . Ta helt enkelt bort din kommentarsrad om "Pseudokod" så ser det bättre ut.