Föreläsningsöversikt Fö 6.1 & 6.2¶
- Tema 6: Temauppgift, seminarie, rapport
- Repetition av termer för objektorienterad programmering
- Klass-diagram (UML)
- OOP: Arv i Python
- Undantag (Exceptions) som exempel på OOP
- Objektorienterad design, olika mönster
- Interaktivt text-baserat program
Tema 6¶
- Tema 6: strukturera kod med hjälp av Pythons stöd för OOP.
- Temauppgift:
- Del 1: Husdjur med leksaker + Person med dataserie(r)
- Del 2: Program för att-göra-listor
Seminarie 6 / Rapport Tema 6¶
- Uppgift: ta fram UML-diagram för ett bok-katalogsystem som ska kunna göra följande
- lagra information om de böcker som är i systemet
- låna ut böcker
- hålla reda på de personer som varje utlånad bok är utlånad till
- Seminarie: Varje pargrupp tar med sig ett förslag som används som diskussionsunderlag på seminariet.
- Rapport: Varje pargrupp beskriver ett förslag med UML-diagram, beskrivning av klasser, samt diskussion
Repetition: klasser, instanser
Objektorienterad terminologi
Meddelande från framtiden:
Ni behöver ha koll på instanser och klasser i AI-kursen nästa höst.
Från Laboration 1, 729G78 Artificiell intelligens
class ReflexAgentWithState(BaseAgent):
class State:
def __init__(self):
self.bump = False
self.previous_action = ""
self.actions = ["GoRight", "GoLeft", "GoForward", "GoBack"]
def __repr__(self):
if self.bump:
return self.previous_action + " resulted in a bump"
else:
return self.previous_action
def update_state_with_percept(self, percept, state):
if percept[1] == "bump":
state.bump = True
else:
state.bump = False
return state
def choose_action(self, state):
actions = state.actions
if state.bump:
actions.remove(state.previous_action)
return random.choice(actions)
def update_state_with_action(self, action, state):
state.previous_action = action
# Print the representation (i.e. __repr__) of the state
print(state)
return state
Klasser och objekt¶
- Klass: mallkod som används när en instans av en klass skapas
- Vi kan skapa nya datatyper med hjälp av klasser (de inbyggda datatyperna i Python är klasser)
- Exempel:
"3953 bananer!"
är ett värde av datatypenstr
("string"/sträng).
- Instans och objekt är synonymer.
- Objekt är faktiska värden som skapas genom att använda definitionen av en klass.
- Exempel: listan
["hej", 36, True]
är en instans av klassenlist
.
Instansvariabler och metoder
- En grundläggande anledning som bidragit till utveckling av objektorienterade programmeringsspråk är behov av att kunna gruppera ihop en specifik datastruktur och funktionalitet som jobbar med just den datastrukturen: inkapsling.
- Klassen är mallen för datastrukturen som består av instansvariabler (variabler som är kopplade till en specifik instans). I Python använder man
self
för att referera till den den egna instansen:self.instansvariabel
- En funktion som är definierad i en klass är bunden till den klassens instanser och kallas för metod.
Repetition: klasser, instanser
Vanliga mönster när man designar en klass¶
- Namngivning av klasser: substantiv i singular
- Namngivning av metoder: verb
- Namngivning av instansvariabler: substantiv
Exempel på klassnamn¶
DataPoint
DataPointCollection
Word
Sentence
Paragraph
Document
Person
ContactList
Book
Library
BookCollection
Vanliga prefix till metodnamn¶
get_
: hämta ett värde från ett objekt (antingen beräknat eller direkt från ett attribut)set_
: sätt att värde (bör egentligen undvikas)add_
: lägg till ett värderemove_
: ta bort ett värdeload_
: ladda data från filsave_
: spara data till filis_X
: returneraTrue
om X stämmer, annarsFalse
has_X
: returneraTrue
om objektet har X, annarsFalse
Klassdiagram i UML
Objektorientering utan kod
Klassdiagram i UML
Exempel: Klassdiagram
Klassdiagram: Relationer mellan objekt¶
- Objekt av en klass kan ha olika typer av relationer till objekt av andra klasser.
- Association: allmän relation
- roll (i praktiken variabelnamn), t.ex. "lästa_böcker" och
- multiplicitet (
<lägst antal>..<högst antal>
,*
betyder godtyckligt många), exempel:0..1
,1..1
,0..*
Relationen association¶
ClassA
→ClassB
- Objekt av
ClassA
känner till <multiplicitet> st instanser avClassB
. - Instans/en/erna av
ClassB
har rollen <roll>. - En dubbelriktad association kan ritas utan pilar.
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: Relationer¶
UML: Relationer (överkurs)¶
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
Lite kort om arv¶
Arv / härleda klasser¶
- När en klass ärver från en annan klass följer alla attribut, dvs instansvariabler och metoder, med.
- Syfte: Abstraktion och kodåtervinning.
Arv: Exempel
class DataFile(object):
def __init__(self, filepath=None):
self.filepath = filepath
data = DataFile("data.txt")
print(data)
Basklass, superklass, föräldraklass¶
- Alla klasser i Python har en basklass (eng. base class) - en klass som de bygger vidare på.
- Kallas också superklass* (superclass) eller föräldraklass (parent class). (*super från latinets "över", inte super som i "extra mycket")
- Om klassen
DataFile
bygger vidare påobject
, kallar manobject
för basklassen för klassenDataFile
.
- En superklass till klassen
MyClass
kan i sin tur ha en superklass, och så vidare, hela vägen tills vi nårobject
. Alla dessa klasser kallar vi för superklasser tillMyClass
.- Ibland pratar vi om direkt superklass kontra ancestors för att hålla isär den närmaste superklassen och dess superklasser.
Härledd klass, subklass, barnklass¶
- Varje klass i Python, utom
object
, är en härledd klass (eng. derived class), dvs. en klass som har en basklass. - Kallas också subklass (subclass) eller barnklass (child class).
DataFile
är en klass härledd från klassenobject
.
- En subklass till
MyClass
kan i sin tur ha en subklass, och så vidare. Precis som med superklasser kallar vi alla dessa klasser för subklasser tillMyClass
.- Ibland pratar vi om direkt subklass kontra descendants.
Syntax klass med arv¶
class Base(object):
pass
class Derived(Base):
pass
Från Laboration 1 i AI-kursen igen
class ReflexAgentWithState(BaseAgent):
class State:
def __init__(self):
self.bump = False
self.previous_action = ""
self.actions = ["GoRight", "GoLeft", "GoForward", "GoBack"]
def __repr__(self):
if self.bump:
return self.previous_action + " resulted in a bump"
else:
return self.previous_action
def update_state_with_percept(self, percept, state):
if percept[1] == "bump":
state.bump = True
else:
state.bump = False
return state
def choose_action(self, state):
actions = state.actions
if state.bump:
actions.remove(state.previous_action)
return random.choice(actions)
def update_state_with_action(self, action, state):
state.previous_action = action
# Print the representation (i.e. __repr__) of the state
print(state)
return state
Arv i Python forts.¶
- En härledd klass har alla attribut (instansvariabler och metoder) som dess basklass har.
- En härledd klass kan ges ytterligare attribut.
- En härledd klass kan överskugga (override) attribut från dess basklass.
- T.ex. är det vanligt att klasser överskuggar
__str__
eftersom den__str__
de ärvt frånobject
bara skriver ut vilken klass ett objekt tillhör och på vilken minnesadress objektet är lagrat.
- T.ex. är det vanligt att klasser överskuggar
Överskuggning, exempel¶
In [3]:
class Bag(object):
def __init__(self):
self.contents = list()
def put(self, obj):
self.contents.append(obj)
def __str__(self):
res ="A bag containing:\n"
for item in list(self.contents):
res += "- " + item + "\n"
return res
my_bag = Bag()
my_bag.put("phone")
my_bag.put("keys")
my_bag.put("banana")
print(my_bag)
A bag containing: - phone - keys - banana
Undantag (eng. Exceptions)

Exempel på hur arv och OOP används i Python
Exempel på kod som utlöser undantag¶
In [71]:
# Exempel 1
number = 5
name = "Ada"
print(number + name)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[71], line 4 2 number = 5 3 name = "Ada" ----> 4 print(number + name) TypeError: unsupported operand type(s) for +: 'int' and 'str'
In [72]:
# Exempel 2
ans = 5 / (42 * 0)
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Cell In[72], line 2 1 # Exempel 2 ----> 2 ans = 5 / (42 * 0) ZeroDivisionError: division by zero
In [73]:
# Exempel 3
printt("Hello world")
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[73], 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-tecknen ska inte heller skrivas i din kod.)
Exempel: Fånga undantag¶
In [74]:
# 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[74], 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 [75]:
# 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 [76]:
# 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 [83]:
# 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.")
c + f -> cf 4 + 2 -> 6 2 + 4 -> 6 TypeError: can only concatenate str (not "int") to str. Tried c + 4 Program done.
Klasshierarki över alla inbyggda undantag i Python 3.10¶
BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
├── Exception
├── StopIteration
├── StopAsyncIteration
├── ArithmeticError
| ├── FloatingPointError
| ├── OverflowError
| ├── ZeroDivisionError
├── AssertionError
├── AttributeError
├── BufferError
├── EOFError
├── ImportError
| ├── ModuleNotFoundError
├── LookupError
| ├── IndexError
| ├── KeyError
├── MemoryError
├── NameError
| ├── UnboundLocalError
├── OSError
| ├── BlockingIOError
| ├── ChildProcessError
| ├── ConnectionError
| | ├── BrokenPipeError
| | ├── ConnectionAbortedError
| | ├── ConnectionRefusedError
| | ├── ConnectionResetError
| ├── FileExistsError
| ├── FileNotFoundError
| ├── InterruptedError
| ├── IsADirectoryError
| ├── NotADirectoryError
| ├── PermissionError
| ├── ProcessLookupError
| ├── TimeoutError
├── ReferenceError
├── RuntimeError
| ├── NotImplementedError
| ├── RecursionError
├── SyntaxError
| ├── IndentationError
| ├── TabError
├── SystemError
├── TypeError
├── ValueError
| ├── UnicodeError
| ├── UnicodeDecodeError
| ├── UnicodeEncodeError
| ├── UnicodeTranslateError
├── Warning
├── DeprecationWarning
├── PendingDeprecationWarning
├── RuntimeWarning
├── SyntaxWarning
├── UserWarning
├── FutureWarning
├── ImportWarning
├── UnicodeWarning
├── BytesWarning
├── EncodingWarning
├── ResourceWarning
Objektorienterad design
Introduktion
Vad vill vi göra? / Hur ska vi göra det? (Mönster)¶
- Exempel på "Vad vill vi göra?":
- Dela upp ett problem
- Delegera / skicka vidare uppgiften
- Återanvända kod
- Förenkla användning av kod
Vad vill vi göra? / Hur ska vi göra det? (Mönster)
- Exempel på "Vad vill vi göra?":
- Dela upp ett problem
- Delegera / skicka vidare uppgiften
- Återanvända kod
- Förenkla användning av kod
- Exempel på "Hur ska vi göra det?"
- 1a) Dela upp en funktion i flera funktioner/metoder
- 1b) Skapa olika klasser med olika ansvarsområden
Vad vill vi göra? / Hur ska vi göra det? (Mönster)¶
- Exempel på "Vad vill vi göra?":
- Dela upp ett problem
- Delegera / skicka vidare uppgiften
- Återanvända kod
- Förenkla användning av kod
- Exempel på "Hur ska vi göra det?"
- 2a) Anropa en annan funktion/metod
- 2b) Anropa en metod i ett annat objekt
Vad vill vi göra? / Hur ska vi göra det? (Mönster)¶
- Exempel på "Vad vill vi göra?":
- Dela upp ett problem
- Delegera / skicka vidare uppgiften
- Återanvända kod
- Förenkla användning av kod
- Exempel på "Hur ska vi göra det?"
- 3a) återanvänd klasser som redan definierats
- 3b) skapa nya klasser genom att kombinera redan definierade klasser
- 3c) skapa nya klasser som ärver av redan definierade klasser
Vad vill vi göra? / Hur ska vi göra det? (Mönster)¶
- Exempel på "Vad vill vi göra?":
- Dela upp ett problem
- Delegera / skicka vidare uppgiften
- Återanvända kod
- Förenkla användning av kod
- Exempel på "Hur ska vi göra det?"
- 4a) Dela upp problemet i olika abstraktionsnivåer. Funktioner som anropar andra funktioner.
- 4b) Dela upp problemet i olika abstraktionsnivåer. Objekt som använder andra objekt
Exempel på problem¶
- Hur många filer finns under katalogen "Mina dokument" på min dator?
- Exempel på funktionell lösning:
- Funktion som räknar antalet filer i en given katalog och anropar sig själv med varje underkatalog som argument och summerar det hela.
- Exempel på objektorienterad lösning:
- Representera katalogen som ett objekt som vet hur många filer och underkataloger den innehåller. Katalogen frågar varje underkatalog om hur många filer den har och summerar det hela.
- OOP: Vilka objekt har vi? Vilka frågor/uppgifter kan en instans som agerar som behållare delegera vidare till de instanser som den innehåller?
Delegering av uppgifter i verkliga världen¶
Jämförelse med verkligheten: Beslut från "ledningsgruppen"¶
- Beslut fattas i "ledningsgruppen" om att alla avdelningar ska inventera sina böcker.
- Varje avdelningschef ber varje gruppchef om en inventering av böckerna i deras grupper
- Varje gruppchef ber medlemmarna i gruppen om en inventering av böckerna på deras kontor.
Delegering i OOP
Definiera klasser
Repetition
Metoden __str__
¶
Metoden __str__
¶
- Det finns en uppsättning metoder som vi kan definiera i en klass som Python vet om och använder i specifika situationer.
- Exempel
- Metoden
__str__
används när en instans av en klass (ett objekt) ska visas som en sträng __str__
tar inte emot några argument förrutomself
__str__
ska returnera en sträng (representationen av instansen)
- Metoden
__str__
används av- funktionen
str
för att konvertera ett objekt till en sträng - funktionen
print
när ett objekt ska skrivas ut
- funktionen
- Anropa inte metoden
__str__
direkt, utan använd funktionenstr
om om du vill ha strängen, eller låt den anropas implicit, t.ex. när du vill skriva ut ett objekt medprint
En klass för spelaren i ett spel
class Player(object):
def __init__(self, name):
self.name = name
self.inventory = []
self.hp = 100
def add_item(self, item_name):
self.inventory.append(item_name)
def __str__(self):
return f"Player name: {self.name}. HP: {self.hp}. Items: {','.join(self.inventory)}"
if __name__ == "__main__":
p1 = Player("Nario")
p1.add_item("Mushroom")
p1.add_item("Star")
print(p1)
En person i ett banksystem
class Person(object):
def __init__(self, name):
self.name = name
self.accounts = []
def add_account(self, account_id):
self.accounts.append(account_id)
def __str__(self):
return f"Name: {self.name}. Accounts: {', '.join(self.accounts)}"
if __name__ == "__main__":
p1 = Person("Nario")
p1.add_account("123456")
p1.add_account("123457")
print(p1)
En egen klass som innehåller instanser av en annan egen klass
Befolkning i stad → en stad innehåller många hushåll
Aggregering av värden genom att skicka vidare uppgiften neråt, "delegera vidare"
Exempel på aggregering av data¶
City
kan tillhandahålla befolkningssiffra genom att fråga allaHousehold
-objekt efter hur många som ingår i hushållet.- Vi kan rita detta som en association från
City
tillHousehold
, men lämpligare är att använda en aggregation
Exempel på aggregering av data¶
City
kan tillhandahålla befolkningssiffra genom att fråga allaHousehold
-objekt efter hur många som ingår i hushållet.- Vi säger att
City
aggregerarHousehold
och illustrerar detta med UMLs Aggregations-relation.- En aggregation innebär att det aggregerande objektet samlar ett flertal objekt som existerar oberoende av det aggregerande objektet.
Uppdelning av problem. Exempel på aggregering av data + implicit användning av __str__
class City(object):
def __init__(self, name):
self.households = []
self.name = name
def add_household(self, household):
self.households.append(household)
def get_population(self):
total_population = 0
for household in self.households:
total_population += household.get_num_members()
return total_population
def __str__(self):
output = f"City: {self.name}\nPopulation: {self.get_population()}\n"
for household in self.households:
output += f" * {household}\n"
return output
class Household(object):
def __init__(self, name):
self.name = name
self.members = []
def add_member(self, name):
self.members.append(name)
def get_num_members(self):
return len(self.members)
def __str__(self):
return f"Household '{self.name}', ({self.get_num_members()} ppl)"
flaxtown = City("Linköping")
household1 = Household("Rydsvägen 1")
household1.add_member("A")
household1.add_member("B")
household2 = Household("Rydsvägen 2")
household2.add_member("C")
flaxtown.add_household(household1)
flaxtown.add_household(household2)
print(flaxtown)
In [13]:
class City(object):
def __init__(self, name):
self.households = []
self.name = name
def add_household(self, household):
self.households.append(household)
def get_population(self):
total_population = 0
for household in self.households:
total_population += household.get_num_members()
return total_population
def __str__(self):
output = f"City: {self.name}\nPopulation: {self.get_population()}\n"
for household in self.households:
output += f" * {household}\n"
return output
class Household(object):
def __init__(self, name):
self.name = name
self.members = []
def add_member(self, name):
self.members.append(name)
def get_num_members(self):
return len(self.members)
def __str__(self):
return f"Household '{self.name}', ({self.get_num_members()} ppl)"
In [14]:
flaxtown = City("Linköping")
household1 = Household("Rydsvägen 1")
household1.add_member("A")
household1.add_member("B")
household2 = Household("Rydsvägen 2")
household2.add_member("C")
flaxtown.add_household(household1)
flaxtown.add_household(household2)
print(flaxtown)
City: Linköping Population: 3 * Household 'Rydsvägen 1', (2 ppl) * Household 'Rydsvägen 2', (1 ppl)
Exempel: OOP-väska
Användardefinierad klass som innehåller instanser av andra användardefinierade klasser
Uppdelning av uppgifter genom att dela upp en modell (klass) i flera klasser med olika ansvarsområden
Modell för en väska¶
- En väska kan öppnas och stängas.
- Vi kan stoppa in saker i en väska, men inte hur många saker som helst.
- En väska är ett förvaringsutrymme som har ett blixtlås.
- Om blixtlåset är öppet kan vi stoppa in saker.
- Om blixtlåset är trasigt kan vi inte stoppa in saker.
- Varje gång vi öppnar eller stänger väskan finns det en viss sannolikhet att blixtlåset går sönder.
bagscript.py¶
- Vi har "abstraherat bort" all logistik (alla detaljer kring vad som faktiskt händer) till implementationen av klassen
Bag
. - Vår kod interagerar med instansen av
Bag
på dess modellerade abstraktionsnivå.
from zcb import *
# Bag är en klass definierad i filen zcb.py
# Vad ser koden ut att göra?
bag = Bag()
bag.open()
bag.add("candy")
bag.close()
bag.open()
bag.add("egg")
bag.close()
bag.add("flower")
print(bag)
Klassen Bag
- Klassen
Bag
i sin tur använder sig av andra abstraktioner. Vi har klassernaZipper
ochContainer
som är abstraktioner av (modellerar) blixtlås och förmåga att vara behållare.
zcb.py - klassen Bag¶
class Bag(object):
def __init__(self, color="green"):
self.color = color
self.zipper = Zipper()
self.container = Container()
def open(self):
self.zipper.open()
def close(self):
self.zipper.close()
def add(self, item):
item = item.upper()
print(f"Trying to add '{item}' to bag..")
if self.zipper.is_open():
self.container.add(item)
else:
print("Open zipper first.")
def __str__(self):
return f"A {self.color} bag. {self.zipper} {self.container}"
zcb.py - Klassen Zipper¶
class Zipper(object):
def __init__(self):
self.state = "open"
self.chance_to_break = 0.2
def is_open(self):
return self.state == "open"
def is_broken(self):
return self.state == "broken"
def open(self):
print("Trying to open zipper...")
self.try_to_break()
if not self.is_broken():
self.state = "open"
print(self)
else:
print(f"Cannot open zipper. {self}")
def close(self):
print("Trying to close zipper...")
self.try_to_break()
if not self.is_broken():
self.state = "closed"
print(self)
else:
print(f"Cannot close zipper. {self}")
def try_to_break(self):
if random.random() <= self.chance_to_break:
self.state = "broken"
print("The zipper broke!")
def __str__(self):
return f"The zipper is {self.state}."
zcb.py - klassen Container¶
class Container(object):
def __init__(self, capacity=5):
self.items = []
self.capacity = capacity
def add(self, item):
if len(self.items) < self.capacity:
self.items.append(item)
print(f"{item} added.")
else:
print("f{item} not added. Container full.")
def __str__(self):
if not self.items:
items_str = "None"
else:
items_str = ", ".join(self.items)
fill_level = len(self.items)/self.capacity * 100
return f"The container is {fill_level:.2f}% full. Items: {items_str}."
Klassdiagram i UML med alla klasser¶
Bag
känner till en instans av klassenZipper
.Zipper
har rollenzipper
iBag
.Bag
känner till en instans av klassenContainer
.Container
har rollencontainer
iBag
.
Klassdiagram i UML med alla klasser¶
Bag
känner till en instans av klassenZipper
.Zipper
har rollenzipper
iBag
.Bag
känner till en instans av klassenContainer
.Container
har rollencontainer
iBag
.- Vi säger att
Bag
är en komposition avZipper
ochContainer
och visar detta med UMLs Composition-relation.- En komposition används när delarna inte kan existera oberoende av helheten.
Relationer i klassdiagram¶
- Informerar oss om:
- Vilka klasser känner till vilka andra klasser?
- Vilken typ av relation råder mellan klasserna?
- Hur många instanser deltar i relationen? (multiplicitet)
- Vilken roll har en klass i relation till en annan?
- Varje relation innebär att åtminstone objekt av den ena klassen kan innehålla en eller flera referenser till objekt av den andra klassen.
- (Vid arv är referensen till basklassen implicit och vi kommer åt den genom att anropa funktionen
super
.)
- (Vid arv är referensen till basklassen implicit och vi kommer åt den genom att anropa funktionen
- Exempel på referenser
- en instansvariabel låter oss referera till en enskild instans
- en lista/ett dictionary låter oss referera till flera instanser
Klasser och objekt¶
- Om klassen
Citron
har en metodmumsa
brukar man i text (t.ex. dokumentation) hänvisa till metodenmumsa
somCitron.mumsa
. - Om
Book
ochPage
är klasser så ska "Book innehåller Page-objekt" tolkas som- "
Book
-objekt innehållerPage
-objekt", dvs - "Instanser av klassen
Book
innehåller instanser av klassenPage
", dvs - "Instanser av klassen
Book
har en instansvariabel som refererar till instanser av klassenPage
"
- "
Bank-exempel¶
En första version¶
- En bank har 0 eller fler personer som kunder.
- Varje person har 0 eller fler konton.
- Varje konto har ett kontonummer, en lista över transaktioner och ett saldo.
bank1.py¶
class Bank(object):
def __init__(self):
self.clients = []
def __str__(self):
output = "THE BANK\n"
for client in self.clients:
output += f"{client}\n"
return output
class Client(object):
def __init__(self, name):
self.name = name
self.accounts = []
def __str__(self):
output = f"Client name: {self.name}\n Accounts:\n"
if len(self.accounts) > 0:
for account in self.accounts:
output += f" {account}"
else:
output += " No accounts."
return output
class Account(object):
def __init__(self, account_id):
self.account_id = account_id
self.transactions = []
self.balance = 0.0
def __str__(self):
return f"#{self.account_id}: {self.balance}"
if __name__ == "__main__":
bank = Bank()
c1 = Client("Nario")
bank.clients.append(c1)
bank.clients.append(Client("Duigi"))
a1 = Account(1)
bank.clients[0].accounts.append(a1)
print(bank)
bank1.py¶
- Ser ni "buggen"?
- Som koden är organiserad ser det ut som att kontot
a1
borde tillhöra Duigi, men det kommer tillhöra Nario, då Nario är den första klienten i listanclients
if __name__ == "__main__":
bank = Bank()
c1 = Client("Nario")
bank.clients.append(c1)
bank.clients.append(Client("Duigi"))
a1 = Account(1)
bank.clients[0].accounts.append(a1)
print(bank)
Metoder istället för att jobba direkt med instansvariablerna¶
Bank
-klassennew_client
: skapa kund, se till att varje kund har ett kontonew_account
: skapa nytt konto, måste ha en kund att koppla det till instansvariabennum_accounts
för antal konton som skapats
Client
-klassenadd_account
: lägg till konto till kund- En kund (
Client
-instans) har minst ett konto (enAccount
-instans)
- Relationer
Bank
-instansen känner till allaClient
-instanser och allaAccount
-instanserClient
-instanser har referenser till alla dessAccount
-instanser
bank2.py¶
class Bank(object):
def __init__(self):
self.num_accounts = 0
self.clients = []
self.accounts = []
def new_client(self, client_name):
self.clients.append(Client(client_name))
self.new_account(client_name)
def new_account(self, client_name):
client_found = False
# leta efter kund
for client in self.clients:
if client.name == client_name:
client_found = True
break
# avbryt om inte kunden hittas
if not client_found:
print(f"ERROR: No such client, '{client_name}'")
return
# skapa konto, lägg till banken och kunden
else:
self.num_accounts += 1
new_account = Account(self.num_accounts)
client.add_account(new_account)
self.accounts.append(new_account)
def __str__(self):
output = "THE BANK\n"
for client in self.clients:
output += f"{client}\n"
return output
class Client(object):
def __init__(self, name):
self.name = name
self.accounts = []
def add_account(self, account):
self.accounts.append(account)
def __str__(self):
output = f"Client name: {self.name}\n Accounts:\n"
for account in self.accounts:
output += f" {account}"
return output
class Account(object):
def __init__(self, account_id):
self.account_id = account_id
self.transactions = []
self.balance = 0.0
def __str__(self):
return f"#{self.account_id}: {self.balance}"
if __name__ == "__main__":
bank = Bank()
bank.new_client("Nario")
bank.new_client("Duigi")
print(bank)
Exempel på förändring av implementation av modell/abstaktion: dictionarys istället för listor¶
Bank
-klassen- ingen förändring av klassdiagrammet för att använda dictionaries istället för listor
- instansvariabel för att ha koll på antal kunder,
num_clients
(används som nyckel) client_id
som argument tillnew_account
Client
-klassen- instansvariaben
client_id
- instansvariaben
bank3.py¶
class Bank(object):
def __init__(self):
self.num_accounts = 0
self.num_clients = 0
self.clients = {}
self.accounts = {}
def new_client(self, client_name):
self.num_clients += 1
# endast för att göra efterföljande kod lättare att läsa
client_id = self.num_clients
self.clients[self.num_clients] = Client(client_name, client_id)
self.new_account(client_id)
def new_account(self, client_id):
# avbryt om inte kunden hittas
if client_id not in self.clients:
print(f"ERROR: Client id '{client_id}' not found.")
else:
self.num_accounts += 1
new_account = Account(self.num_accounts)
# samma Account-instans läggs till både banken och kunden
self.clients[client_id].add_account(new_account)
self.accounts[self.num_accounts] = new_account
def __str__(self):
output = "THE BANK\n\n"
for client in self.clients.values():
output += f"{client}\n"
return output
class Client(object):
def __init__(self, name, client_id):
self.name = name
self.client_id = client_id
self.accounts = []
def add_account(self, account):
self.accounts.append(account)
def __str__(self):
output = f"Client name: {self.name} (id: {self.client_id})\nAccounts:\n"
for account in self.accounts:
output += f" {account}"
return output
class Account(object):
def __init__(self, account_id):
self.account_id = account_id
self.transactions = []
self.balance = 0.0
def __str__(self):
return f"#{self.account_id}: {self.balance}"
if __name__ == "__main__":
bank = Bank()
bank.new_client("Nario")
bank.new_client("Duigi")
print(bank)
Interaktiva text-gränssnitt¶
interaktionsloop1.py med interaktionsloop¶
In [ ]:
from bank3 import *
def new_client(bank):
print("new_client() körs")
def show_clients(bank):
print("show_clients() körs")
def main():
bank = Bank()
while True:
user_input = input("Vad vill du göra? ").lower()
if user_input == 'q':
break
elif user_input == "ny kund":
new_client(bank)
elif user_input == "visa kunder":
show_clients(bank)
else:
print("Okänt kommando.")
if __name__ == '__main__':
main()
interaktionsloop2a.py med dictionary och funktionsobjekt¶
In [ ]:
from bank3 import *
def new_client(bank):
print("new_client() körs")
def show_clients(bank):
print("show_clients() körs")
def main():
commands = {'ny kund': new_client,
'visa kunder': show_clients}
bank = Bank()
while True:
user_input = input("Vad vill du göra? ").lower()
if user_input == 'q':
break
elif user_input in commands:
commands[user_input](bank)
else:
print("Okänt kommando.")
if __name__ == '__main__':
main()
interaktionsloop2b.py med dictionary och funktionsobjekt¶
In [ ]:
from bank3 import *
def new_client(bank):
print("new_client() körs")
def show_clients(bank):
print("show_clients() körs")
def main():
commands = {'ny kund': new_client,
'visa kunder': show_clients}
bank = Bank()
while True:
user_input = input("Vad vill du göra? ").lower()
try:
if user_input == 'q':
break
commands[user_input](bank)
except KeyError:
print("Okänt kommando.")
if __name__ == '__main__':
main()
interaktionsloop3.py med interaktiv funktion som skapar ny kund¶
from bank3 import *
def new_client(bank):
while True:
confirmed = ""
client_name = input("Ange kundens namn: ")
while confirmed not in ["j", "n"]:
confirmed = input(f"Du skrev '{client_name}', är det OK? [j/n]: ")
if confirmed == "j":
bank.new_client(client_name)
break
else:
print("Ok, skriv in igen!")
def show_clients(bank):
print(bank)
def main():
commands = {'ny kund': new_client,
'visa kunder': show_clients}
bank = Bank()
while True:
user_input = input("Vad vill du göra? ").lower()
try:
if user_input == 'q':
break
commands[user_input](bank)
except KeyError:
print("Okänt kommando.")
In [ ]:
from bank3 import *
def new_client(bank):
while True:
confirmed = ""
client_name = input("Ange kundens namn: ")
while confirmed not in ["j", "n"]:
confirmed = input(f"Du skrev '{client_name}', är det OK? [j/n]: ")
if confirmed == "j":
bank.new_client(client_name)
break
else:
print("Ok, skriv in igen!")
def show_clients(bank):
print(bank)
def main():
commands = {'ny kund': new_client,
'visa kunder': show_clients}
bank = Bank()
while True:
user_input = input("Vad vill du göra? ").lower()
try:
if user_input == 'q':
break
commands[user_input](bank)
except KeyError:
print("Okänt kommando.")
main()
interaktionsloop4.py med utbruten bekräftelsefunktion¶
from bank3 import *
def get_user_confirmation(message, yes, no):
user_input = ""
while user_input not in [yes, no]:
user_input = input(f"{message} [{yes}/{no}]: ")
return user_input == yes
def new_client(bank):
while True:
client_name = input("Ange kundens namn: ")
message = f"Du skrev '{client_name}', är det OK?"
if get_user_confirmation(message, "j", "n"):
bank.new_client(client_name)
break
else:
print("Ok, skriv in igen!")
def show_clients(bank):
print(bank)
def main():
commands = {'ny kund': new_client,
'visa kunder': show_clients}
bank = Bank()
while True:
user_input = input("Vad vill du göra? ").lower()
try:
if user_input == 'q':
break
commands[user_input](bank)
except KeyError:
print("Okänt kommando.")
if __name__ == '__main__':
main()
In [ ]:
from bank3 import *
def get_user_confirmation(message, yes, no):
user_input = ""
while user_input not in [yes, no]:
user_input = input(f"{message} [{yes}/{no}]: ")
return user_input == yes
def new_client(bank):
while True:
client_name = input("Ange kundens namn: ")
message = f"Du skrev '{client_name}', är det OK?"
if get_user_confirmation(message, "j", "n"):
bank.new_client(client_name)
break
else:
print("Ok, skriv in igen!")
def show_clients(bank):
print(bank)
def main():
commands = {'ny kund': new_client,
'visa kunder': show_clients}
bank = Bank()
while True:
user_input = input("Vad vill du göra? ").lower()
try:
if user_input == 'q':
break
commands[user_input](bank)
except KeyError:
print("Okänt kommando.")
if __name__ == '__main__':
main()
Instans som huvudprogram¶
interaktionsloop4.py¶
from bank3 import *
def get_user_confirmation(message, yes, no):
user_input = ""
while user_input not in [yes, no]:
user_input = input(f"{message} [{yes}/{no}]: ")
return user_input == yes
def new_client(bank):
while True:
client_name = input("Ange kundens namn: ")
message = f"Du skrev '{client_name}', är det OK?"
if get_user_confirmation(message, "j", "n"):
bank.new_client(client_name)
break
else:
print("Ok, skriv in igen!")
def show_clients(bank):
print(bank)
def main():
commands = {'ny kund': new_client,
'visa kunder': show_clients}
bank = Bank()
while True:
user_input = input("Vad vill du göra? ").lower()
try:
if user_input == 'q':
break
commands[user_input](bank)
except KeyError:
print("Okänt kommando.")
if __name__ == '__main__':
main()
interaktionsloop5.py med instans som huvudprogram¶
from bank3 import *
class BankApp(object):
def __init__(self):
self.commands = {'ny kund': self.new_client,
'visa kunder': self.show_clients}
self.bank = Bank()
def run(self):
while True:
user_input = input("Vad vill du göra? ").lower()
try:
if user_input == 'q':
break
self.commands[user_input]()
except KeyError:
print("Okänt kommando.")
def get_user_confirmation(self, message, yes, no):
user_input = ""
while user_input not in [yes, no]:
user_input = input(f"{message} [{yes}/{no}]: ")
return user_input == yes
def new_client(self):
while True:
client_name = input("Ange kundens namn: ")
message = f"Du skrev '{client_name}', är det OK?"
if self.get_user_confirmation(message, "j", "n"):
self.bank.new_client(client_name)
break
else:
print("Ok, skriv in igen!")
def show_clients(self):
print(self.bank)
if __name__ == '__main__':
app = BankApp()
app.run()
In [ ]:
from bank3 import *
class BankApp(object):
def __init__(self):
self.commands = {'ny kund': self.new_client,
'visa kunder': self.show_clients}
self.bank = Bank()
def run(self):
while True:
user_input = input("Vad vill du göra? ").lower()
try:
if user_input == 'q':
break
self.commands[user_input]()
except KeyError:
print("Okänt kommando.")
def get_user_confirmation(self, message, yes, no):
user_input = ""
while user_input not in [yes, no]:
user_input = input(f"{message} [{yes}/{no}]: ")
return user_input == yes
def new_client(self):
while True:
client_name = input("Ange kundens namn: ")
message = f"Du skrev '{client_name}', är det OK?"
if self.get_user_confirmation(message, "j", "n"):
self.bank.new_client(client_name)
break
else:
print("Ok, skriv in igen!")
def show_clients(self):
print(self.bank)
if __name__ == '__main__':
app = BankApp()
app.run()