Se lecture 3
Pausa vid varje felmeddelande och försök hitta typ, rad och orsak.
Öppna Harvard vecka 3 ↗Lektion 3 · Cirka 45 min genomgång
Ett robust program förutsätter inte att all inmatning är giltig. Det upptäcker förväntade problem, förklarar vad som gick fel och låter användaren försöka igen.
Om texten inte kan omvandlas till typen int skapas ett ValueError. En specifik except-gren kan hantera just det problemet.
try:
alder = int(input("Ålder: "))
except ValueError:
print("Skriv ett heltal.")Skriv ett heltal.Före genomgången
Harvards lecture 3 heter Exceptions. Se filmen före lektionen. Ha en terminal och en editor öppna så att du kan framkalla små fel medvetet och sedan tolka dem.
Pausa vid varje felmeddelande och försök hitta typ, rad och orsak.
Öppna Harvard vecka 3 ↗Skapa lektion_03 i kursmappen och öppna den i VS Code.
Läs alltid sista raden i en traceback först. Den anger normalt undantagets typ och meddelande.
Vilken typ av fel rapporteras? På vilken rad upptäcktes det? Vilket värde eller antagande gjorde att operationen inte kunde genomföras?
Dagens karta
Vi skiljer först feltyperna åt och bygger sedan en kontrollerad väg tillbaka till normal körning.
8 min
8 min
7 min
8 min
7 min
7 min
Läs signalen
Ett syntaxfel betyder att Python inte kan tolka programtextens grammatik. Ett undantag, på engelska exception, uppstår när ett syntaktiskt giltigt program inte kan genomföra en operation under körning.
SyntaxErrorEn saknad avslutande parentes, ett glömt kolon eller ett felaktigt indrag kan göra att Python inte kan läsa programmet. Källkoden måste rättas innan den delen kan köras.
Syntaxen är giltig, men en viss operation misslyckas: text kan exempelvis inte bli int, en nämnare är noll eller en nyckel saknas i ett dict.
En traceback visar den kedja av anrop som ledde till ett ohanterat undantag. Sista raden innehåller normalt undantagets klass, exempelvis ValueError, och ett meddelande. Raderna ovan visar filer, radnummer och funktionsanrop.
Pilen markerar var Python upptäckte problemet. Den bakomliggande orsaken kan ligga tidigare, till exempel i ett oväntat värde som skickades in till funktionen.
Traceback (most recent call last):
File "alder.py", line 1, in <module>
alder = int("trettio")
ValueError: invalid literal for int()ValueErrorrätt typ av argument, men ogiltigt värdeTypeErroroperationen stöder inte den givna typenNameErrornamnet är inte definieratZeroDivisionErrordivision eller modulo med nollIndexErrorindex finns inte i sekvensenKeyErrornyckeln finns inte i ett dictEOFErrorinput når slut på indataSyntaxError fångas normalt inte runt den felaktiga radenPython måste först kunna tolka filen innan satserna börjar köras. En vanlig try-sats i samma felaktiga fil kan därför inte rädda syntax som inte går att läsa.
Hantera förväntade problem
try och except delar kontrollflödetI ett try-block placeras den minsta operation som förväntas kunna skapa ett visst undantag. Om undantaget uppstår avbryts resten av blocket och Python söker uppifrån efter den första matchande except-grenen.
try:
tal = int(input("Heltal: "))
except ValueError:
print("Du skrev inte ett heltal.")Du skrev inte ett heltal.try-blocket.except ValueError över.int skapar ValueError avbryts återstoden av try.En handler är koden i en except-gren. Den ska bara fånga ett problem som programmet faktiskt kan hantera på ett meningsfullt sätt.
except-grenartry:
svar = taljare / namnare
except TypeError:
print("Använd tal.")
except ZeroDivisionError:
print("Nämnaren får inte vara noll.")try:
svar = taljare / namnare
except (TypeError, ZeroDivisionError):
print("Divisionen kunde inte göras.")Med as error binds det fångade undantagsobjektet till variabeln error. Objektet innehåller bland annat meddelandet från Python. Det är användbart för loggning och felsökning, men råa tekniska meddelanden är inte alltid lämpliga som användarinstruktion.
try:
tal = int(text)
except ValueError as error:
print(f"Teknisk detalj: {error}")except:En gren utan angiven typ fångar nästan allt och kan dölja programmeringsfel eller avbrott. Även except Exception är ofta för brett i nybörjarkod. Fånga i stället de specifika, förväntade undantag som du kan hantera.
Den lyckade vägen och den garanterade vägen
else körs vid framgång · finally körs alltidelse hör här till try-satsen och körs bara om inget undantag uppstod i try-blocket. finally körs när kontrollen lämnar satsen, oavsett om ett undantag inträffade.
I tal = int(text) utvärderas högerledet int(text) först. Om det skapar ValueError sker ingen tilldelning och tal får inget nytt värde. Att använda namnet efteråt kan därför ge NameError.
Lägg den lyckade fortsättningen i else. Då syns det att den bara får använda resultatet när try lyckades.
try:
tal = int(text)
except ValueError:
print("Ogiltigt heltal.")
else:
print(f"Dubbelt: {tal * 2}")try litetJu färre rader som ligger i blocket, desto tydligare är det vilken operation som förväntas misslyckas. Kod i else fångas inte av de föregående handler-grenarna av misstag.
finallyAnvänds bland annat för att frigöra resurser eller skriva en avslutande loggrad. För filer är with ofta ett ännu tydligare sätt att säkerställa stängning.
try:
resultat = 10 / namnare
except ZeroDivisionError:
print("Division med noll går inte.")
else:
print(resultat)
finally:
print("Försöket är avslutat.")Försöket är avslutat.Återhämtning
Att fånga ett undantag gör inte automatiskt inmatningen giltig. En while True-loop kan upprepa frågan tills omvandlingen lyckas och funktionen lämnar ett godkänt värde.
def get_int(prompt):
while True:
try:
return int(input(prompt))
except ValueError:
print("Skriv ett heltal.")
alder = get_int("Ålder: ")return avslutar både loop och funktionVid giltig inmatning returneras heltalet direkt. Då avslutas hela funktionsanropet, inte bara loopen. Vid ValueError körs handlern och nästa iteration börjar.
breakreturnpasspass är en tom sats, inte en felhantering i sigI en except-gren kan pass användas för att tyst försöka igen, men återkoppling är ofta mer pedagogisk. Använd det bara när tystnad är ett medvetet krav, exempelvis när en okänd menyartikel ska ignoreras.
EOFError signalerar slut på inmatningeninput skapar EOFError om indata tar slut. Det används i uppgifter där ett okänt antal rader läses. I en lokal terminal skickas EOF normalt med Ctrl+D på macOS och Linux, och ofta med Ctrl+Z följt av Enter på Windows.
Handlern kan då avsluta loopen med break. Ett vanligt mönster är att samla data i ett dict under loopen och skriva ett sorterat resultat efteråt.
rader = []
while True:
try:
rader.append(input("Rad: "))
except EOFError:
break
print(rader)Skapa tydliga fel själv
raise rapporterar ett brutet kontraktEtt värde kan ha rätt typ men ändå bryta mot programmets regler. Med raise skapar programmet avsiktligt ett undantag som beskriver problemet.
def procent(andel):
if not 0 <= andel <= 1:
raise ValueError("andel måste ligga mellan 0 och 1")
return andel * 100Funktionen har ett kontrakt: argumentet ska ligga i intervallet 0–1. raise ValueError(...) avbryter funktionen om kontraktet bryts. Anroparen kan sedan välja att fånga undantaget eller låta det fortsätta uppåt.
Använd en inbyggd typ när den beskriver problemet väl. Det gör koden lättare att förstå för andra Pythonprogrammerare.
raise ValueErrorPassar för validering av data och kan hanteras även när Python körs optimerat.
assert villkorKontrollerar ett internt antagande under utveckling. assert kan tas bort i optimerat körläge och ska därför inte användas för normal användarinmatning.
def medelvarde(tal):
assert tal, "intern regel: listan får inte vara tom"
return sum(tal) / len(tal)assert som inmatningsfilterAnvändaren förväntas ibland skriva fel. Det är en normal situation som ska valideras uttryckligen och hanteras med exempelvis ValueError, inte ett utvecklarantagande som kan kopplas bort.
En egen klass kan uttrycka ett domänspecifikt problem. Den ärver normalt från Exception och namnet slutar konventionellt med Error. Eftersom klasser kommer senare i kursen räcker det nu att känna igen formen. Välj en inbyggd typ om den redan beskriver problemet.
class NotEvenError(Exception):
pass
if tal % 2 != 0:
raise NotEvenError("talet måste vara jämnt")Defensiv programmering
Robust kod delar upp arbetet i begripliga steg. Det gör att varje undantag kan kopplas till en viss operation och att regler kan testas utan interaktiv inmatning.
strip, lower→omvandlaint, float→valideradomänens regler→bearbetaberäkna eller lagraEn låg nivå kan upptäcka och rapportera att ett värde är ogiltigt med raise. Den interaktiva delen kan fånga undantaget, ge ett begripligt meddelande och fråga igen. Om en funktion inte vet hur problemet ska lösas bör den normalt inte dölja det.
Skilj på datafel och programmeringsfel. En användare som skriver tre där ett heltal krävs är förväntat. Ett felstavat variabelnamn som ger NameError är ett kodfel som ska rättas, inte tyst fångas.
def parse_fraction(text):
delar = text.split("/")
if len(delar) != 2:
raise ValueError("formatet ska vara X/Y")
x, y = map(int, delar)
if x > y:
raise ValueError("X får inte vara större än Y")
return x / ysplit("/") delar strängen vid tecknet /. map(int, delar) tillämpar int på varje del, och uppackningen binder de två resultaten till x och y. ValueError beskriver fel format eller ogiltig relation; division med y == 0 ger ZeroDivisionError.
Skriv testfall för giltig inmatning, tom text, fel format, bokstäver, gränsvärden och noll. Ett undantag är en del av funktionens beteende och ska testas avsiktligt.
Säg vad som krävs: ”Skriv två heltal som X/Y” är bättre än ”Något gick fel”. Behåll tekniska detaljer i utvecklarens traceback eller logg.
Problem Set 3
Harvards aktuella uppgiftssidor är kravspecifikationen. Äldre lokala exempel kan avvika och ska inte behandlas som facit.
Läs bråket X/Y, skriv närmaste heltalsprocent och använd E vid högst 1 procent samt F vid minst 99 procent. Fråga igen vid icke-heltal, X > Y eller Y == 0.
Testa: 3/4, 1/100, 99/100, 4/4, 5/4, 3/0 och cat/dog.
Läs artiklar till EOFError, gör uppslaget skiftlägesoberoende och skriv löpande totalsumma med dollartecken och två decimaler efter varje giltig artikel. Okända artiklar ignoreras.
Testa: olika skiftläge, flera giltiga köp, okänd artikel och avslut med EOF.
Aktuell uppgift ↗Räkna förekomster i ett dict tills input skapar EOFError. Skriv sedan antalet följt av varan i versaler, sorterat alfabetiskt efter vara.
Testa: samma vara med olika skiftläge, en enda vara, flera varor i osorterad ordning och EOF.
Aktuell uppgift ↗Acceptera datum som 9/8/1636 eller September 8, 1636 och skriv 1636-09-08. Ogiltigt format, månad utanför 1–12 eller dag utanför 1–31 ska ge ett nytt försök.
Testa: båda formaten, inledande nollor, okänd månad, dag 32 och ofullständiga datum.
Aktuell uppgift ↗Exempelvis kostar Baja Taco nu 4,25 dollar, 4/4 är giltigt och ska ge F, och ett ISO-datum använder vanliga bindestreck samt två siffror för månad och dag.
Fortsätt arbeta
Använd notebooken för experiment, guiden för begreppen och originalkällorna för den aktuella kravspecifikationen.
✓ skilja SyntaxError från undantag under körning
✓ läsa typ, meddelande och plats i en traceback
✓ fånga specifika undantag med try och except
✓ förklara else, finally, raise och pass
✓ bygga en loop som frågar igen efter ogiltig inmatning
✓ förklara varför assert inte ersätter validering