Föreläsningsöversikt, FÖ 5.1-5.2¶
- Period 1 & Period 2
- Objektorienterad programmering (OOP)
- Begrepp: klass, instans (objekt), instansvariabler, metoder
- Syntax: skapa klasser, använda instanser
- Funktioner som objekt
- Exempel på OOP: grafiska gränssnitt
- Exempel på OOP: undantagshantering
- UML-diagram
- Kodstandarder för Python: PEP8 & PEP257
- Programmeringsmetod
- Implementation, testning, felsökning
Sammanfattning av period 1¶
- Grundläggande datatyper/datastrukturer
- Syntax och användning av funktioner
- Syntax och användning av kontrollstrukturer (villkor, loopar)
- Repetition på två sätt: iteration och rekursion
- Dela upp problem, bryta ut funktioner, introduktion till abstraktion
Inför period 2¶
- Anmäl er till nya grupper
- Stark rekommendation: byt programmeringspartner
- Introduktion till objektorienterad programmering
- Exempel på vanliga mönster
5.1.1 Vad är objektorienterad programmering?¶
Objekt i Python¶
- Python är objektbaserat, dvs.
- Alla värden i Python är objekt , dvs. instanser av klasser
- Många andra programmeringsspråk har några så kallade primitiva datatyper (ofta heltal, flyttal, tecken, array, etc.) som inte är objekt, Python har inga primitiva datatyper.
- I Python gäller att datatyper == klasser
Exempel: Inbyggda klassen list
¶
- Vi har klassen
list
som används (som en mall) för listor. - När vi vill skapa en specifik lista, skapar vi en instans av klassen
list
genom att anropa klassen- I fallet
list
kan vi skicka med ett argument när vi skapar en instans
- I fallet
list()
→ instans av klassenlist
som ännu inte innehåller några elementmy_variable = list()
list(range(3))
→ instans av klassenlist
som innehåller talen 0, 1, 2my_other_variable = list(range(3))
- Vi kan komma åt egenskaper (variabler) och beteenden (metoder) med punktnotation:
my_variable.reverse()
my_variable.sort()
Exempel: Inbyggda klassen list
¶
In [1]:
print(list)
<class 'list'>
Exempel: Inbyggda klassen list
¶
In [2]:
list()
Out[2]:
[]
Exempel: Inbyggda klassen list
¶
In [3]:
my_variable = list()
print(my_variable)
[]
Exempel: Inbyggda klassen list
¶
In [4]:
print(type(my_variable))
<class 'list'>
Exempel: Inbyggda klassen list
¶
In [5]:
print(len(my_variable))
0
Exempel: Inbyggda klassen list
¶
In [6]:
print(my_variable.append(1))
None
Exempel: Inbyggda klassen list
¶
In [7]:
print(my_variable)
[1]
Så vad är det nya om vi jobbat med objekt hela tiden?¶
1. Inkapsling (Encapsulation)¶
- Objekt är själva ansvariga för den data de innehåller och ingen kod utanför objektet kan (givet ren objektorientering) direkt påverka det som finns inne i objektet
- Message passing: Genom att skicka meddelanden till ett objekt kan vi indirekt påverka objektet eller läsa den data som finns där i.
- Det är objektet självt som avgör vilken kod som ska köras när ett meddelande inkommer. (Dynamic dispatch)
- Ett meddelande skickas till ett objekt genom att anropa en av objektets metoder.
2. Abstraktion (Abstraction)¶
- Genom att modellera system som en samling objekt som representerar olika komponenter kan vi abstrahera bort onödig komplexitet.
- Det räcker att veta vad ett objekt gör, inte hur det gör det.
3. Arv (Inheritance) och/eller sammansättning (Composition)¶
- Arv
- Nya objekt kan skapas genom modifikationer av existerande typer utan att behöva duplicera kod.
- Mer generell kod i ett objekt och mer specifik kod i ett objekt som ärver den generella funktionaliteten från det första objektet.
- Ett objekt består alltså inte bara av en typ utan av en serie av typer med ökande abstraktionsgrad.
- Sammansättning
- Nya objekt kan skapas som innehåller instanser av andra objekt och därför kan använda deras funktionalitet utan att behöva duplicera kod.
- Kod som behövs i flera olika sammanhang kan placeras i ett objekt som inkluderas i flera olika objekt.
- Arv och sammansättning kommer bara behandlas översiktligt i kursen.
4. Polymorfism (Polymorphism)¶
- Objekt av olika typer kan behandlas som om de tillhörde samma typ under vissa förutsättningar.
- T.ex. objekt av olika typer som genom arv från en gemensam basklass eller prototyp delar viss mer generell funktionalitet.
- Polymorfism kommer bara behandlas översiktligt i kursen.
Klass eller prototyp?¶
- Två olika skolor
- Klassbaserad OOP: Varje objekt tillhör en klass som beskriver vilka metoder som finns tillgängliga och vilka typer av data som objektet innehåller. (Python, Java, C++, Swift, Kotlin, Go, etc.)
- Prototypbaserad OOP: Varje objekt startar som en kopia av ett prototypobjekt som innehåller vissa metoder och data. (JavaScript, Lua, Io, AHK)
Klassbaserad objektorientering¶
- Alla objekt är instanser av en klass.
- En klass är en mall, en beskrivning av något som kan finnas i världen.
- En klass beskriver vilka egenskaper eller attribut ett objekt har och vilka beteenden det har.
- egenskaper/attribut kan jämföras med variabler
- beteenden realiseras genom metoder som kan jämföras med funktioner
- Det som finns i världen är objekt och alla objekt är instanser av klasser.
Klasser i föreläsningssalen¶
- Y1.a? MED1? TMA1?
- Proletariat? Bourgeoisie? Aristokrati?
- Nja... Snarare:
- Bänk
- Stol
- Eluttag
- Whiteboard
- Projektor
- Student
- Lärare
Egna klasser¶
- Det huvudsakliga arbetet inom objektorienterad programmering organiseras runt att designa och implementera egna klasser.
- Klassnamn bör vara substantiv och egendefinierade klasser i Python börjar alltid med stor bokstav.
- Pythons inbyggda klasser börjar dock normalt sett med liten bokstav för att särskilja dem, t.ex.
list
,dict
,int
.
- Pythons inbyggda klasser börjar dock normalt sett med liten bokstav för att särskilja dem, t.ex.
- En fil/modul kan innehålla flera klasser.
Repetition: Abstrakta datatyper (ADT)¶
Repetition: Abstrakta datatyper (ADT)¶
- Abstrakta datatyper, konceptuell föregångare till OOP
- Liten uppsättning primitiva datatyper i programmeringsspråk, t.ex. heltal, flyttal, strängar, listor
- Hur gör vi för att representera begrepp/koncept som inte kan kokas ner till någon av dessa datatyper och de operationer vi kan utföra på dem?
- T.ex. en person, en kundkorg i ett e-handelssystem, mer specialiserade datastrukturer, etc.
Exempel på abstrakt datatyp¶
- Kö (eng. queue)
- lista med element är kön, index
0
är nästa på tur - funktionen
create_empty_queue()
som returnerar en tom kö - funktionen
enqueue(value, queue)
som lägger till värdetvalue
till slutet på könqueue
- funktionen
dequeue(queue)
som returnerar värdet som är först i kön och plockar även bort det från kön
- lista med element är kön, index
Implementation av kö-ADT:n¶
In [8]:
def create_empty_queue():
return ('queue', [])
def enqueue(value, queue):
if queue[0] == 'queue':
queue[1].append(value)
else:
raise TypeError("Not a queue")
def dequeue(queue):
if queue[0] == 'queue':
if queue[1]:
return queue[1].pop(0)
else:
return None
else:
raise TypeError("Not a queue")
Implementation av kö-ADT:n¶
In [9]:
q = create_empty_queue()
print(1, q)
enqueue('a', q)
print(2, q)
enqueue('b', q)
print(3, q)
print(4, f"{dequeue(q)=}")
print(5, q)
enqueue('c', q)
print(6, q)
1 ('queue', []) 2 ('queue', ['a']) 3 ('queue', ['a', 'b']) 4 dequeue(q)='a' 5 ('queue', ['b']) 6 ('queue', ['b', 'c'])
In [10]:
print(7, f"{dequeue(q)=}")
print(8, f"{dequeue(q)=}")
print(9, f"{dequeue(q)=}")
7 dequeue(q)='b' 8 dequeue(q)='c' 9 dequeue(q)=None
Implementation av kö-ADT:n¶
- Den abstrakta datatypen kan implementeras på många olika sätt.
- I Python är en lista speciellt lämplig.
- Det viktiga från ADT-paradigmet är själva idén om vi kan skapa egna abstraktioner och jobba med dem i ett program.
- Med ett programmeringsspråk utan objektorientering finns dock inga språkkonstruktioner som specifikt stödjer detta abstraktionsarbete.
- T.ex. det finns inget som hindrar oss att använda funktionen
enqueue()
på två heltal.
Objektorienterad version av kö-ADT:n¶
In [11]:
class Queue:
def __init__(self):
self.storage = []
def enqueue(self, value):
self.storage.append(value)
def dequeue(self):
if self.storage:
return self.storage.pop(0)
else:
return None
Objektorienterad version av kö-ADT:n¶
In [12]:
q = Queue()
print(1, q)
q.enqueue('a')
print(2, q)
q.enqueue('b')
print(3, q)
print(4, f"{q.dequeue()=}")
print(5, q)
q.enqueue('c')
print(6, q)
1 <__main__.Queue object at 0x0000012F9D5AB2D0> 2 <__main__.Queue object at 0x0000012F9D5AB2D0> 3 <__main__.Queue object at 0x0000012F9D5AB2D0> 4 q.dequeue()='a' 5 <__main__.Queue object at 0x0000012F9D5AB2D0> 6 <__main__.Queue object at 0x0000012F9D5AB2D0>
In [13]:
print(7, f"{q.dequeue()=}")
print(8, f"{q.dequeue()=}")
print(9, f"{q.dequeue()=}")
7 q.dequeue()='b' 8 q.dequeue()='c' 9 q.dequeue()=None
Klassdefinition¶
- Nyckelordet
class
används för att definiera en ny klass, på ungefär sätt somdef
används för att definiera en funktion. class
följs av namnet på den klass man skapar och kolon, ungefär som en funktionsdefinition.
class Queue:
- Inne i kodblocket för klassen, dvs i klassens kropp, defineras klassens metoder och attribut.
Metoddefinition¶
- Inne i en klasskropp defineras metoder med
def
på samma sätt som vilken funktion som helst, med ett undantag. - Första argumentet till en metod är alltid den aktuella instansen, dvs. objektet självt, och därför använder vi namnet
self
för den första parametern.- Detta är inte i strikt mening ett krav i Python, men det är en konvention man alltid ska följa.
- Inne i metoden används sedan
self
för att komma åt instansvariabler eller andra metoder.
def enqueue(self, value):
self.storage.append(value)
Magic methods i Python¶
- Python-klasser kan implementera vissa specialmetoder som inte anropas direkt utan genom andra språkliga konstruktioner, dessa kallas för magic methods
- Eftersom Python använder dubbla understreck för att indikera dessa kallas de ibland också för dundermetoder.
__init__
¶
__init__
är den vanligast förekommande magiska metoden och beskriver hur ett objekt ska skapas eller initieras.- Ofta kan
__init__
ta många olika argument men likt alla metoder är den första parameterna alltidself
. - Mer generellt kallas
__init__
för en konstruktor och motsvarande konstruktioner finns i alla språk med klassbaserad OOP. - Konstruktorn anropas inte manuellt utan genom att "anropa" själva klassen
class Queue:
def __init__(self):
self.storage = []
...
q = Queue()
__str__
¶
- Efter
__init__
är__str__
antagligen den mest vanligt förekommande dundermetoden. - Metoden
__str__
beskriver strängrepresentationen av ett objekt. __str__
anropas automatiskt av funktionenprint
eller "halvmanuellt" genom att skapa en ny sträng-instans medstr
och skicka med objektet som argument.
class Queue:
...
def __str__(self):
return f"(Queue: {str(self.storage)})"
...
print(q)
str(q)
Icke-strukturell ADT¶
- Exempel: Person i ett matchingsprogram
- I vår abstraktion (modell) så passar personer som har ett gemensamt intresse ihop
- Vi behöver följande operationer:
- Skapa en person
p
som har namnet Adap = create_person('Ada')
- Lägg till intresse till person
p
set_interest(p, 'Bernoulli numbers')
- Ta reda på om en person
p1
och en personp2
matcharmatches(p1, p2)
- En kö är bara en datastruktur.
- Kan lagra vilken typ av data som helst.
- ADT:er kan vara mycket mer specifika än så.
Implementation av person-ADT:n¶
In [14]:
def create_person(name):
return {'name':name, 'interest':None}
def set_interest(person, color):
person['interest'] = color
def matches(person1, person2):
return person1['interest'] == person2['interest']
Implementation av person-ADT:n¶
In [15]:
p1 = create_person('Annie')
set_interest(p1, 'rocketry')
p2 = create_person('Adele')
set_interest(p2, 'graphical user interfaces')
p3 = create_person('Margaret')
set_interest(p3, 'rocketry')
In [16]:
matches(p1, p2)
Out[16]:
False
Person-klassen¶
In [17]:
class Person:
def __init__(self, name):
self.name = name
self.interest = None
def set_interest(self, interest):
self.interest = interest
def matches(self, other):
return self.interest == other.interest
Person-klassen¶
In [18]:
p1 = Person('Annie')
p1.set_interest('rocketry')
p2 = Person('Adele')
p2.set_interest('graphical user interfaces')
p3 = Person('Margaret')
p3.set_interest('rocketry')
In [19]:
p1.matches(p3)
Out[19]:
True
OOP: Arv - ett sätt att återanvända kod¶
Översikt (vi kommer inte använda oss av explicita arv i denna kurs)
Arv¶
- Ytterligare ett viktigt koncept för OOP är arv
- Arv låter oss skapa härledda klasser (även kallade för subklasser).
- En härledd klass har alla egenskaper och beteenden som sin basklass (även kallad superklass), men kan utöka eller ersätta dessa.
- Analogt exempel från biologin: taxonomi över flora och fauna
Definiera en klass¶
- Python för bok över vilka klasser som finns. Varje klass lagras i en hierarki av klasser.
- Klasshierarkin finns för att vi ska kunna återanvända kod från "klassföräldrar", basklasser (kallas ibland även för superklasser).
- För att definiera en klass använder vi nyckelordet
class
följt av ett namn, samt ett parentespar med den nya klassens basklass. Anges ingen basklass kommer klassenobject
användas som basklass.
class Book:
pass
- Samma som:
class Book(object):
pass
Klassen object
¶
- Pythons mest fundamentala basklass. Alla andra klasser ärver, i något ändligt antal steg, från
object
. - Vi har sett kod ärvd från
object
köras.- Innan vi skapade
Queue.__str__
användesobject.__str__
när vi skrev ut Queue-objekt. - Det var
object.__str__
som returnerade'<__main__.Queue object at 0x0000021A9813B250>'
- Innan vi skapade
- När vi skapade
Queue.__str__
så överlagrade vi den ärvda__str__
-metoden frånobject
Exempel: Queue med arv¶
In [20]:
class QueueList(list):
def enqueue(self, value):
self.append(value)
def dequeue(self):
if self:
return self.pop(0)
else:
return None
Exempel: Queue med arv¶
In [21]:
q = QueueList()
print(1, q)
q.enqueue('a')
print(2, q)
q.enqueue('b')
print(3, q)
print(4, f"{q.dequeue()=}")
print(5, q)
q.enqueue('c')
print(6, q)
1 [] 2 ['a'] 3 ['a', 'b'] 4 q.dequeue()='a' 5 ['b'] 6 ['b', 'c']
In [22]:
print(7, f"{q.dequeue()=}")
print(8, f"{q.dequeue()=}")
print(9, f"{q.dequeue()=}")
7 q.dequeue()='b' 8 q.dequeue()='c' 9 q.dequeue()=None
Funktionsobjekt¶
- Alla värden (data) är objekt i Python.
- Definierade funktioner kan också ses som en typ av värden - funktionobjekt
Funktionsobjekt¶
- Klasser kan instansieras och producera objekt
- I Python är även funktioner objekt.
- En funktion:
def hejsan():
print("Hejsan")
- Ett funktionanrop:
hejsan()
- Funktionsobjektet:
hejsan
Exempel på funktionsobjekt¶
In [23]:
def print_hello():
print("Hello World!")
bacon = print_hello
bacon()
Hello World!
Högre ordningens funktioner¶
- Funktioner som antingen tar emot en funktion som argument eller returnerar en funktion kallas högre ordningens funktioner
- Exempel på användning
- Pythons inbyggda funktioner
sum()
,max()
,min()
m.fl. kan ta emot ett nyckelordsargument med en funktion som plockar fram det värde som ska användas.
- Pythons inbyggda funktioner
Exempel¶
In [24]:
def second(values):
return values[1]
value_pairs = [ ["Ada", 3], ["Bertil", 2], ["Cissi", 7] ]
print("Högst poäng:", max(value_pairs, key=second))
print("Lägst poäng:", min(value_pairs, key=second))
Högst poäng: ['Cissi', 7] Lägst poäng: ['Bertil', 2]
Grafiska gränssnitt¶
- exempel på tillämpning av objektorienterad programmering
Programflöde: GUI¶
- GUI = Graphical User Interface
- Icke-linjär interaktion, händelsestyrt
- Reaktivt gränssnitt - tät återkoppling
- Interaktion t.ex. via mus och tangentbord
- Interaktion med en begränsad och standardiserad uppsättning widgets (oftast)
Om modulen tkinter
¶
- Lastgammal, men...
- Det enda biblioteket för att bygga GUI-applikationer i Pythons standardbibliotek (dvs det är inbyggt).
- Långt ifrån det enda biblioteket/ramverket för att bygga GUI-applikationer i Python.
- Python-wrapper för Tcl/Tk, ett GUI-bibliotek som sträcker sig tillbaka till början av 90-talet och som kan användas från massor olika programmeringsspråk.
- Tcl, uttalat "tickle", är ett idag utdaterat språk som bara lever kvar genom Tk.
- Widgets är definierade som klasser: faktiska gränssnittskomponenter är instanser av klasserna.
- Olika inställningar kan ges när man skapar en widget.
- Widgets kan placeras i ett GUI på tre sätt - det finns tre "geometry managers" (i andra ramverk också kallade för "layout managers").
Widgets¶
Litet exempel¶
In [25]:
import tkinter as tk
import random
def clicked():
colors = [("red", "white"), ("green", "black"), ("blue", "white")]
bg_color, fg_color = random.choice(colors)
label.config(bg=bg_color, fg=fg_color, text="button clicked")
def mouse_enter(event):
label.config(text="mouse over")
def mouse_leave(event):
label.config(text="mouse exit")
# skapa ett Tk-fönster
root = tk.Tk()
# skapa label-widget
label = tk.Label(root, text="Hello!")
label.pack(fill=tk.X)
# koppla ihop händelser med funktioner. OBS! Funktionsobjekt som argument.
label.bind("<Enter>", mouse_enter)
label.bind("<Leave>", mouse_leave)
# skapa knappen. OBS! Funktionsobjekt som argument
button = tk.Button(root, text="Press Me!", command=clicked)
button.pack(fill=tk.X)
# starta GUI-loopen
root.mainloop()
Laboration 5¶
- Del 1: syntax för att skapa och använda klasser
- Del 2: använda objekt + öva på att skriva kommentarer, följa kodstandarder
Del 2¶
- Kodskelett: random-layout.py
- Gränssnitt: lab5.py
- Syftet är inte att ge en introduktion till hur moderna GUI-ramverk fungerar.
random-layout.py
¶
In [26]:
%%python
#!/usr/bin/env python3
"""Laboration 5 -- TDDE44
Exempel på slumpmässig layout-funktion. Layout-funktionen skickas som
argument när en instans av lab5.LayoutTester skapas.
"""
# Läs denna fil för att se hur gränssnittet skapats.
import lab5
import random
def random_layout(squares, frame_height, frame_width):
"""Placera ut fyrkanterna i listan squares slumpmässigt.
Argument:
squares -- Lista som innehåller tkinter.Label-objekt
frame_height -- Höjden (int) på den Fram som fyrkanterna ligger i
frame_width -- Bredden (int) på den Frame som fyrkanterna ligger i
"""
# Slumpa ut positioner för alla fyrkanter utan att de hamnar utanför framen
for square in squares:
square_size = square.winfo_width()
xpos = random.randint(0, frame_width - square_size)
ypos = random.randint(0, frame_height - square_size)
square.place(x=xpos, y=ypos)
if __name__ == "__main__":
layout_tester = lab5.LayoutTester(random_layout)
Introduktion till klassdiagram i UML¶
UML - Unified Modelling Language¶
- Ett sätt att grafiskt representera system, t.ex. datorprogram, hur de är uppbyggda och fungerar
- UML definierar en uppsjö av olika diagram i tre grova kategorier för att beskriva olika aspekter
- Strukturdiagram, beskriver hur ett system är uppbyggt.
- Beteendediagram, beskriver hur olika processer sker i systemet.
- Interaktionsdiagram, beskriver hur en användare interagerar med systemet.
- I sin striktaste form, nästintill ett eget programmeringsspråk.
- Används dock ofta mer informellt för att illustrera specifika saker, och då utelämnas detaljer som inte behövs i det fallet
Klassdiagram¶
Klassdiagram¶
Book-klassen, definition¶
In [27]:
class Book:
def __init__(self, title):
self.title = title
self.author = "Unknown"
self.year = None
def print_citation(self):
if not self.year:
year = "N/A"
else:
year = self.year
print(f"{self.author}. ({year}). {self.title}")
Book-klassen, UML-diagram¶
Relationer i klassdiagram¶
- Relationer informerar oss om
- Vilka klasser känner till vilka andra klasser?
- Hur många instanser deltar i relationen? (Kardinalitet)
- Vilken roll har en klass i relation till en annan?
- Innebär att en instans av en klass har en referens till en instans av en annan klass (eller ytterligare en instans av den egna klassen)
- Exempel på referenser
- en instansvariabel låter oss referera till en enskild instans
- en lista/ett dictionary låter oss referera till flera instanser
UML: Relationen association¶
- association: allmän relation
- Klass A → Klass B
- roll (i praktiken variabelnamn), t.ex. "lästa_böcker" och
- Instansen/-erna av klassen B har rollen <roll>
- multiplicitet (
<lägst antal>..<högst antal>
,*
betyder godtyckligt många), exempel:0..1
,1..1
,0..*
- A känner till <multiplicitet> st instanser av klassen B
- En dubbelriktad association kan ritas utan pilar.
Exempel¶
Exempel¶
UML, klassdiagram: Association¶
- Exempel på roller:
lästa_böcker
squares
contacts
- Exempel på multiplicitet
0..1
: noll eller en0..*
: noll eller flera1..*
: en eller fler3
: exakt tre0..3
: noll till tre5..9
: fem till nio
UML: Andra relationer¶
Namngivning¶
- Namn ska hjälpa läsaren förstå koden. Undvik intetsägande namn.
- Namngivning av klasser: substantiv i singular, använd
CamelCase
- Namngivning av metoder: verb, använd
snake_case
- Namngivning av instansvariabler: substantiv, använd
snake_case
- OBS! Om klassen representerar en samling objekt, dvs fungerar som en behållare använd fortfarande singular, men lägg till ett suffix, t.ex.
Collection
,Set
ellerList
Exempel på klassnamn¶
- DataPoint
- DataPointCollection
- Word
- Sentence
- Paragraph
- Document
- Person
- ContactList
- Book
- Library
- BookCollection
Vanliga prefix till metodnamn¶
- Använd samma prefix för metoder som gör liknande saker.
get_
*: använd för metoder som returnerar ett värde från ett objekt (antingen beräknat eller direkt från ett attribut). T.ex..get_max()
,.get_friendlist()
set_
*: använd för metoder som tar emot argument och lagrar dessa i objektet.- T.ex.
.set_filename(filename)
- T.ex.
add_
: använd för metoder som tar emot argument och lägger till dessa till- objektet. T.ex.
.add_friend(name)
- objektet. T.ex.
remove_
: använd för metoder som tar bort data från ett objekt.- T.ex.
.remove_last_task()
- T.ex.
load_
: använd för metoder som laddar data från fil. T.ex..load_data(filename)
save_
: använd för metoder som sparar data till fil. T.ex..save_data()
* Generellt sett bör man undvika "getters" och "setters" då de bryter inkapslingen och abstraktionen som är själva poängen med OOP, men ibland är de oundvikliga.
Undantag (Exceptions)¶
Exempel på kod som utlöser undantag¶
In [28]:
# Exempel 1
number = 5
name = "Ada"
print(number + name)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[28], line 4 2 number = 5 3 name = "Ada" ----> 4 print(number + name) TypeError: unsupported operand type(s) for +: 'int' and 'str'
In [29]:
# Exempel 2
ans = 5 / (42 * 0)
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Cell In[29], line 2 1 # Exempel 2 ----> 2 ans = 5 / (42 * 0) ZeroDivisionError: division by zero
In [30]:
# Exempel 3
printt("Hello world")
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[30], line 2 1 # Exempel 3 ----> 2 printt("Hello world") NameError: name 'printt' is not defined
Exception - exempel på OOP¶
- Varje typ av undantag är definerad som en klass i Python
- När ett undantag inträffar skapas en instans av den undantagstypen
- Undantaget innehåller information om vad som hänt (data)
Undantag - Exceptions¶
- Undantag (eng. Exception) är ett sätt att hantera fel i kod som kan utlösas (eng. throw/raise) när ett program kör (körfel).
- Vid fel kan ett undantag utlösas. Programmet avbryter sitt normala flöde för att gå in i "undantagsläge".
- Om kod för att hantera ett undantag saknas, kraschar programmet.
- Skillnad mot
if
/else
:- Villkor:
if
/else
- kontrollera först, utför sen - Undantagshantering:
try
/except
- utför först, hantera ev. fel
- Villkor:
- Undantag ska inte ses som ett alternativ till villkorssatser utan just som ett sätt att hantera undantag.
Exempel på olika undantag i Python¶
ZeroDivisionError
: inträffar när man försökt dividera ett tal med 0IOError
: inträffar t.ex. om man försöker öppna en fil som inte finnsIndexError
: inträffar när man använder ett ogiltigt index för en specifik listaKeyError
: inträffar när man försöker komma åt värdet för en nyckel som inte finns i ett dictionaryTypeError
: inträffar t.ex. när man försöker använda operatorn+
på en sträng och ett heltal
Hantera undantag i Python¶
- Om man vill hantera undantag i Python lägger man koden som kan utlösa undantag i ett
try
-block. - Undantag hanteras sedan i ett
except
-block. - Vid behov kan ett
finally
-block kan läggas till sist. Koden i detta block körs sist och alltid; dvs efter felfritt utförande avtry
-blocket samt efter att någotexcept
-block uförts. - Minst ett
except
-block, eller ettfinally
-block måste defineras för varjetry
-block.
Mönster för undandagshantering i Python¶
# nyckelordet try används för att påbörja ett try-block
try:
# här lägger man koden som kan utlösa ett undantag
# "försök utföra dessa satser"
# nyckelordet except används för att påbörja ett except-block
except [typ av undantag] [as <namn på undantagsinstans>]:
# här lägger man koden för vad som ska hända vid undantag
except [typ av undantag] [as <namn på undantagsinstans>]:
# flera olika typer av undandtag kan hanteras
# genom att lägga till flera except-block
# koden i finally-blocket utförs både efter felfritt try-block
# och efter except-block körts vid fel i try-block
finally:
# kod för att städa upp, t.ex. stänga fil eller nätverks-
# anslutning
Hakparenteserna ska inte skrivas ut, de visar att det som står innanför dem är valfritt att skriva. Det som står inom mindre än/större än-tecken måste vara med. Mindre än/större än-tecknena ska inte heller skrivas i din kod.
Exempel: Fånga undantag¶
In [31]:
# krasch.py
# Exempel på kod som kraschar
letters = ["a", "b", "c", "d", "e", "f"]
index = 0
while True:
print(f"letters[index]: {letters[index]}")
index += 1
print("Program done.")
letters[index]: a letters[index]: b letters[index]: c letters[index]: d letters[index]: e letters[index]: f
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) Cell In[31], line 7 5 index = 0 6 while True: ----> 7 print(f"letters[index]: {letters[index]}") 8 index += 1 9 print("Program done.") IndexError: list index out of range
Exempel: Fånga undantag¶
In [32]:
# undantag1.py
# Undantag hanterat, kraschar inte
letters = ["a", "b", "c", "d", "e", "f"]
try:
index = 0
while True:
print("letters[index]: {}".format(letters[index]))
index += 1
print("Loop done.")
except:
print("An exception was raised.")
print("Program done.")
letters[index]: a letters[index]: b letters[index]: c letters[index]: d letters[index]: e letters[index]: f An exception was raised. Program done.
Exempel: Fånga och namnge undantag¶
In [33]:
# undantag2.py
letters = ["a", "b", "c", "d", "e", "f"]
try:
index = 0
while True:
print(letters[index])
index += 1
# Om undantag inträffar kommer variabeln error referera till en
# instans av klassen Exception
except Exception as error:
print("An exception was raised.")
print(error)
print("Program done.")
a b c d e f An exception was raised. list index out of range Program done.
Exempel: Fånga och hantera specifika undantag på olika sätt¶
In [34]:
# undantag3.py
import random
def run_until_exception():
values = ["a", 2, "c", 4, "e", "f"]
try:
while True:
# slumpa fram två index som ev. kan vara ogiltiga
index1 = random.randint(0, len(values))
index2 = random.randint(0, len(values))
# använd operatorn +. Fel om operanderna inte är samma datatyp.
print(f"{values[index1]} + {values[index2]} -> {values[index1] + values[index2]}")
except TypeError as exception:
print(f"TypeError: {exception}. Tried {values[index1]} + {values[index2]}")
except IndexError as exception:
print(f"IndexError: {exception}, {len(values)=}, {index1=}, {index2=}")
run_until_exception()
print("Program done.")
4 + 2 -> 6 f + e -> fe TypeError: can only concatenate str (not "int") to str. Tried c + 2 Program done.
Anledningar till att använda undantag istället för villkorssatser¶
- Man skriver ett bibliotek som andra programmerare ska använda och man vill lämna vissa beslut till dem.
- Ibland kan man inte förebygga fel, t.ex.
- filen fanns först, men sedan tog någon bort den
- vi har en fungerande internet-anslutning, men den dör medan programmet kör
- Läsbarhet, i de få fall där en korrekt villkorssats blir oläslig kan det vara enklare att använda ett undantag. Detta är i sig ett undantag! I 99 fall av 100 bör förväntade omständigheter hanteras med villkorssatser.
- I denna kurs är det inget krav att hantera fel med hjälp av undantag.
- Överanvändning av undantag kan ge komplettering på labbarna.
Kodstandarder¶
- Ökad läsbarhet och lättare att orientera sig i koden
- Lättare att underhålla kod
- För Python är PEP 8, PEP 257 och PEP 287 de policy-dokument som har med kodstandard och dokumentation av kod.
- Vi koncentrerar oss på PEP 8 och PEP 257
Krav på PEP 8 och PEP 257¶
- Från och med laboration 5 kommer det att vara krav på att den kod ni skriver följer PEP 8 och PEP 257.
- För att underlätta kontroll av att er kod följer dessa kan ni antingen
- installera ett plugin till er texteditor som kontrollerar PEP 8 och PEP 257 medan ni skriver er kod, eller
- installera paket
pycodestyle
som används för att kontrollera PEP 8 och paketetpydocstyle
som används för att kontrollera PEP 257
PEP 8¶
- Radlängd, indentering och whitespace
- Indentering: 4 mellanslag
- Radlängd: 79 tecken
- Rader bryts på rätt sätt med rekommenderad indentering
- Två tomma rader mellan funktioner
- En tom rad mellan metoder
- Namnkonventioner för variabelnamn, funktionsnamn, klassnamn
PEP 257¶
- Konventioner för docstrings
- Docstrings används för att dokumentera moduler, klasser, metoder och funktioner
- Skriv det som är viktigt för den som ska använda modulen/klassen/metoden/funktionen.
- Börjar och slutar med tre citationstecken (
"""
) - Skrivs på följande ställen
- först i en modul
- raden efter
class Klassnamn(object):
- raden efter
def funktionsnamn():
ellerdef metodnamn():
Utformning av en docstring¶
- Två varianter:
- docstring som endast använder en rad
- docstring som använder flera rader
- Docstring på en rad
- en mening som slutar på punkt
- ska vara formulerad i imperativ (som en "order")
- Exempel
- OK:
"""Returnera alla udda tal i argumentet value_list."""
- Inte OK:
"""Funktionen returnerar alla udda tal i argumentet value_list"""
- OK:
Docstring på flera rader¶
- Sammanfattande en-radsbeskrivning som första rad.
- Formuleras på samma sätt som en docstring på en rad.
- En tom rad, följt av resterande docstring-rader.
- Avslutande trippel-citationstecknena skrivs på en egen rad.
- Ingen tom rad mellan avslutande trippel-citationstecken och första raden kod.
Detaljnivå på docstrings¶
- En docstring är till för användare av modulen/klassen/funktionen/metoden.
- I den sammanfattande första meningen är syftet att berätta vad som görs, inte hur det görs.
- Exempel på "vad"-beskrivning (bra):
- Returnerar en sorterad lista med innehållet från value_list.
- Exempel på "hur"-beskrivning" (dåligt):
- Använder en temporär lista för att lagra alla element som tupler som innehåller elementet som en sträng följt av det faktiska elementet innan de sorteras med hjälp av en bubbelsort
Användning av docstrings kontra inlinekommentarer¶
- Docstrings är till för att läsas av personer som vill veta hur de ska använda modulen/klassen/metoden/funktionen.
- Inline-kommentarer (som börjar med
#
) är till för personer som utvecklar/underhåller modulen/klassen/metoden/funktionen.
Automatisk kontroll av PEP8 & PEP257¶
- Kommandoradsverktyg
pycodestyle
för att kontrollera PEP8pydocstyle
för att kontroller PEP257
- Tillägg för "linting" i Visual Studio Code
- Mer information på kurshemsidan
Testning och felsökning¶
Att testa sin kod¶
- Testdriven programmering - utanför omfånget för denna kurs
- Vi använder spårutskrifter och ipythons interaktiva läge.
- Vad borde funktionen/metoden returnera? Gör den det?
- Interaktivt läge:
ipython3 -i <filnamn>
- Kommandon:
ls
,cd
,cat
etc. fungerar iipython
%run <filnamn
> - kör fil%reset
- rensa minnet från användarens variabler
- Ctrl-D för att avsluta (Ctrl-D är även kontrolltecknet för EOT, End of Transmission)
Använda python interaktivt¶
- Anropa funktioner, skapa instanser av klasser från fil(er) som laddats in i minnet.
- Allt man kan göra från en textfil.
- Kom dock ihåg att allt ligger kvar i minnet:
- globala variabler
- moduler som laddats in
- funktioner som definierats
- etc.
Rätta buggar¶
- syntaxfel
- runtime-fel (inträffar under körning)
- logiska fel
Övning¶
flatten_and_sort_without_duplicates()
- Ska platta till, sortera och ta bort dubletter från en nästlad lista med siffror (högst två nivåer)
- T.ex.
[[2], 1, 3, [3, 2]]
→[1, 2, 3]
In [36]:
def flatten_and_sort_without_duplicates(list):
"""Return flattened and sorted values of list without duplicates."""
# flatten contents of list, i.e. [1, [2]] -> [1, 2]
new = []
for e in list:
if type(e) == list:
for e2 in e:
new.append(e2)
else:
new.append(e)
# sort values in new, [2, 3, 1, 3] -> [1, 2, 3, 3]
sorted(new)
# remove duplicates from sorted values e.g. [1, 2, 2, 3] -> [1, 2, 3]
i = 0
while i < len(new):
# remove new[i] if it is equal to the subsequent value
if new[i] = new[i+1]:
new.remove(new[i])
i += 1
return new
File <tokenize>:9 else: ^ IndentationError: unindent does not match any outer indentation level