Föreläsningsöversikt, FÖ 6.1-6.2¶
- Halvtidsutvärderingen
- Programmera bättre?
- Typer av relationer mellan klasser
- Aggregation
- Nästlade objekt
- Komposition
- Interaktiva textgränssnitt
- Instans som huvudprogram
Slutsatser av halvtidsutvärderingen¶
- Föreläsningarna går för långsamt/för fort, ca 50/50 split.
- Föreläsningarna är som bäst när jag live-kodar.
- Labbarna och labbassistenterna är jättebra, men lektionerna kunde vara mer givande.
- Föreläsningarna ger mer introduktion till byggstenarna än till hur man sammanfogar dem för att lösa problem.
- Det är väldigt mycket att göra med både pythonuppgifter och labbar.
- Rekursion har varit det svåraste momentet.
Vad menar vi med "bättre"?¶
- Bättre program (som körs)
- Korrekthet
- Effektivitet
- Funktionalitet
- Bättre sätt att programmera
- Minska risken att göra fel
- Minska tiden det tar att programmera
- Minska tiden det tar att underhålla
- Bättre kod = kod som leder till/underlättar ovanstående
Regel 0: Testa ofta¶
- Testkör programmet ofta, helst efter varje ny
- funktion
- klass
- metod
- osv.
- Men VAR LAT
- (Skriv små funktioner som kör samma testkod varje gång.)
In [ ]:
def my_tests():
MyClass(0, 0)
MyClass(-1, 1)
MyClass(0.999, 42)
example1 = MyClass(0, 100)
print(f"{example1=}")
example1.method1()
print(f"{example1=}")
print(f"{example1.method2()=}")
if __name__ == "__main__":
my_tests()
In [1]:
from labb6_del2_uppg1 import TodoApp
def test_cli():
print("\n========== Running test_cli ==========")
app = TodoApp()
app.show_commands()
def test_tasks():
print("\n========== Running test_tasks ==========")
app = TodoApp()
app._tasklist.create_task('labb5')
app._tasklist.create_task('labb6')
app._tasklist.create_task('labb7')
app._tasklist.mark_done(1)
app.show_tasks()
def run_tests():
test_cli()
test_tasks()
run_tests()
========== Running test_cli ========== Kommandon: ny, visa, klar, ? ========== Running test_tasks ========== 0. [ ] labb5 1. [X] labb6 2. [ ] labb7
Skriv lättläst och lättöverskådlig kod¶
- Minska bortkastad tid på att hitta och navigera i koden.
- Hur gör man kod mer lättläst?
- Namngivning
- Doc-strings
- Kommentarer
- Kodstandard
- (Ja, jag vet att jag tjatar.)
- Hur gör man kod mer lättöverskådlig?
- Dela upp problem i olika abstraktionsnivåer och komponenter.
Dela upp ett problem
- Single Responsibility Principle
- Ett objekt ska ha en roll.
- En funktion ska ha en uppgift.
- Två sidor av samma mynt
- Dela upp problem
↔ Delegera / skicka vidare uppgift.
- Dela upp problem
Delegera / skicka vidare uppgiften¶
- Hur gör man det i kod?
- Anropa en metod i ett annat objekt.
- Applicera en annan, kanske mer generell, funktion.
- När gör man det?
- När vi löst samma eller ett väldigt likt delproblem på "ett annat ställe".
Återanvända kod¶
- Organisera kod som modul.
- Bryta ut kod från komplexa klasser till enklare men mer generella klasser som kan kombineras.
- Bryta ut kod från komplexa funktioner till enklare men mer generella funktioner.
- Skapa högre ordningens funktioner som beskriver generella lösningar.
- Alltså: Abstrahera och generalisera!
- Hur man inte återanvänder kod
- Kopiera och klistra in.
DRY-principen¶

- Don't Repeat Yourself
- (i motsats till WET, för We Enjoy Typing eller Waste Everyone's Time).
- Om en viss typ av problem endast löses på ett ställe i koden finns endast ett ställe att...
- ...felsöka om det går fel.
- ...förbättra om det är för ineffektivt.
- Men gå inte till överdrift: AHA-principen (Avoid Hasty Abstractions, efter "prefer duplication over the wrong abstraction" — Sandi Metz)
In [51]:
def check_consecutive(lst, pred):
for i in range(1, len(lst)):
if pred(lst[i-1], lst[i]):
return False
return True
def has_pairs(lst):
for i in range(1, len(lst)):
if lst[i-1] == lst[i]:
return True
return False
def is_increasing(lst):
for i in range(1, len(lst)):
if lst[i-1] > lst[i]:
return False
return True
def has_pairs2(lst):
return not check_consecutive(lst, lambda x, y: x == y)
def is_increasing2(lst):
return check_consecutive(lst, lambda x, y: x > y)
print(f'{has_pairs2([1, 2, 3, 4])=}')
print(f'{has_pairs2([1, 2, 3, 4, 3])=}')
print(f'{has_pairs2([1, 2, 3, 3])=}')
print(f'{is_increasing2([1, 2, 3, 4])=}')
print(f'{is_increasing2([1, 2, 3, 3])=}')
print(f'{is_increasing2([1, 2, 3, 5])=}')
print(f'{is_increasing2([1, 2, 3, 2])=}')
has_pairs2([1, 2, 3, 4])=False has_pairs2([1, 2, 3, 4, 3])=False has_pairs2([1, 2, 3, 3])=True is_increasing2([1, 2, 3, 4])=True is_increasing2([1, 2, 3, 3])=True is_increasing2([1, 2, 3, 5])=True is_increasing2([1, 2, 3, 2])=False
In [50]:
from itertools import pairwise
def check_consecutive(lst, predicate):
for x, y in pairwise(lst):
if predicate(x, y):
return True
return False
def has_pairs(lst):
return check_consecutive(lst, lambda x, y: x == y)
def is_increasing(lst):
return not(check_consecutive(lst, lambda x, y: x > y))
print(f'{has_pairs([1, 2, 3, 4])=}')
print(f'{has_pairs([1, 2, 3, 4, 3])=}')
print(f'{has_pairs([1, 2, 3, 3])=}')
print(f'{is_increasing([1, 2, 3, 4])=}')
print(f'{is_increasing([1, 2, 3, 3])=}')
print(f'{is_increasing([1, 2, 3, 5])=}')
print(f'{is_increasing([1, 2, 3, 2])=}')
has_pairs([1, 2, 3, 4])=False has_pairs([1, 2, 3, 4, 3])=False has_pairs([1, 2, 3, 3])=True is_increasing([1, 2, 3, 4])=True is_increasing([1, 2, 3, 3])=True is_increasing([1, 2, 3, 5])=True is_increasing([1, 2, 3, 2])=False
Klassdiagram i UML
repetition innan vi går vidare
Klassdiagram i UML¶
Klassdiagram, Laboration 5, Del 1
Association i klassdiagram¶
- Association: Allmän relation, klass A har en referens till objekt av klass B.
- <roll> namet på instansvariabeln i klass A
- <multiplicitet> hur många objekt av klass B känner objekt av klass A till?
Aggregation¶
Exempel: Städer, hushåll och befolkning
Aggregering av data¶
- Ett
City
-objekt har en lista överHousehold
-objekt som representerar alla hushåll i staden. - Vi kan rita detta som en association från
City
tillHousehold
. - Lämpligare är dock att använda en aggregation.
Aggregation i klassdiagram¶
- Aggregation: Specialfall av association.
- En-till-många-relationer (och ibland många-till-många-relationer).
- Vi säger att klass A aggregerar klass B.
- Svag association
- Samma aggregerade objekt kan aggregeras av flera olika aggregerande objekt (eller av inget aggregerande objekt alls).
- Aggregerade objekt existerar oberoende av det aggregerande objektet.
Aggregering av data¶
- Vi säger att
City
aggregerarHousehold
och illustrerar detta med UMLs Aggregations-relation.- Ett aggregerande objekt,
City
, samlar ett flertal aggregerade objekt,Household
, som existerar oberoende av det aggregerande objektet.
- Ett aggregerande objekt,
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 [9]:
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 [10]:
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 på att delegera vidare uppgift/rollfördelning¶
City
har koll på instanser avHousehold
.Household
har koll på hur många som bor i varje hushåll.City
kan tillhandahålla befolkningssiffra genom att fråga varjeHousehold
-objekt efter hur många som ingår i hushållet, och summera alla svar.
class City:
def get_population(self):
total_population = 0
for household in self.households:
total_population += household.get_num_members()
return total_population
Laboration 6 - Del 2. Uppgift 1¶
- Att-göra-lista/listor
- Interaktivt textgränssnitt
- Uppdelning av problem - olika klasser har olika roller
- Klasser för datamodell och klass för gränssnittet
Laboration 6 - Del 2. Uppgift 2¶
- Gör klart Uppgift 1 innan ni börjar med Uppgift 2 (kod för båda skickas in)
- Uppgift 2: Lägg till klassen
User
. VarjeTask
ska ha en användare kopplad till sig. - Nya kommandon för att lägga till/visa användare
- Uppdatera kommandon för att skapa uppgift/visa uppgifter.
Laboration 6 - Del 2. Uppgift 3¶
- Gör klart Uppgift 2 innan ni börjar med Uppgift 3 (kod för alla tre skickas in)
- Uppgift 3: Låt uppgifter vara deluppgifter till andra uppgifter.
- Nya kommandon för att lägga till/visa deluppgifter.
- Tillämpning av rekursion vid hantering eftersom
Task
aggregerar sig själv.
Nästlade strukturer av objekt¶
Nästlade strukturer, repetition¶
- T.ex. listor i listor:
[4, [8, [3], [1, [7], [4]]]]
- Kan tolkas som träd.
- Rekursion för godtyckligt nästlade strukturer.
In [11]:
nested_list = [4, [8, [3], [1, [7], [4]]]]
def tree_sum(tree):
if not tree:
return 0
if type(tree[0]) == list:
return tree_sum(tree[0]) + tree_sum(tree[1:])
return tree[0] + tree_sum(tree[1:])
print(tree_sum(nested_list))
27
Trädstrukturer av objekt¶
- Nästlade objekt.
- Klasser som aggregerar sig själva.
- T.ex. klassen
Node
enligt UML-diagrammet till höger.
In [ ]:
class Node:
def __init__(self, value):
self.value = value
self._children = []
n1 = Node(4)
n2 = Node(8)
n1._children.append(n2)
En klass för trädstrukturer¶
add_child
för att skapa barn utan att bryta inkapsling.- Returnerar nya noden för enkelhet.
In [ ]:
class Node:
def __init__(self, value):
self.value = value
self._children = []
def add_child(self, value):
child = Node(value)
self._children.append(child)
return child
n1 = Node(4)
n2 = n1.add_child(8)
En klass för trädstrukturer¶
- Vi vill kunna navigera både uppåt och nedåt i trädet.
- Lagra föräldranoden också!
In [1]:
class Node:
def __init__(self, value, parent):
self.value = value
self._parent = parent
self._children = []
def add_child(self, value):
child = Node(value, self)
self._children.append(child)
return child
n1 = Node(4, None)
n2 = n1.add_child(8)
En klass för trädstrukturer¶
- Alla noder måste per definition hänga ihop, alltså:
- Ingen klass
Tree
behövs, det räcker medNode
.
Träd med Node
¶
- Vårt träd som noder istället för nästlade listor.
In [11]:
nested_list = [4, [8, [3], [1, [7], [4]]]]
tree_root = Node(4, None)
node_8 = tree_root.add_child(8)
node_3 = node_8.add_child(3)
node_1 = node_8.add_child(1)
node_7 = node_1.add_child(7)
node_4 = node_1.add_child(4)
Trädsumma med Node
¶
- Funktion
tree_sum
som traverserar trädet och beräknar summan. - Även här använder vi rekursion.
In [9]:
def tree_sum(root):
result = root.value
for child in root._children:
result += tree_sum(child)
return result
tree_sum(tree_root)
Out[9]:
27
Vad gjorde jag för fel i tree_sum
?¶
Hur kommer vi runt det?¶
def tree_sum(root):
result = root.value
for child in root._children:
result += tree_sum(child)
return result
Vi gör tree_sum
till en metod!¶
- Vi definierar en ny rekursiv metod i
Node
-klassen:
In [13]:
class Node:
def __init__(self, value, parent):
self.value = value
self._parent = parent
self._children = []
def add_child(self, value):
child = Node(value, self)
self._children.append(child)
return child
def tree_sum(self):
child_sums = []
for child in self._children:
child_sums.append(child.tree_sum())
return self.value + sum(child_sums)
tree_root.tree_sum()
Out[13]:
27
Är tree_sum
-metoden bra?¶
- Om vårt träd inte innehåller tal?
- Om vi vill ha produkten istället för summan?
- Om vi vill ha en strängkonkatenering av talen istället för den matematiska summan?
- Kan
tree_sum
generaliseras på samma sätt somcheck_consecutive
?
- Ja! Vi skapar den högre ordningens funktionen
reduce
.
Mellanspel: Varför reduce
?¶
- Tidigare högre ordningens funktioner, t.ex.:
sort
max
min
check_consecutive
- Från funktionell programmering kommer 3 klassiska högre ordningens funktioner för att hantera samlingar (t.ex. listor, tupler, mängder, etc.).
map
filter
reduce
map
¶
- Tar en en funktion $f$ och en samling $X$ och beräknar resultatet av att applicera $f$ på varje element i $X$.
- Dvs. om vi antar att $X$ är en mängd:
- Om $X$ är en t.ex. en ordnad mängd, tupel, vektor, tensor, etc. så bevaras ordningen och eventuella dimensioner.
map
, exempel¶
In [15]:
nums = [1, 2, 3, 4, 5]
list(map(lambda x: x**3, nums))
Out[15]:
[1, 8, 27, 64, 125]
Kärt barn har många namn¶
- Heter
map
i de flesta programmeringsspråk men kallas iblandapply
,transform
, ellercollect
filter
¶
- Tar ett predikat* $p$ och en samling $X$ och returnerar samlingen av värden i $X$ för vilka predikatet är sant.
- (* en funktion vars värde kan tolkas som ett sanningsvärde)
- Dvs. om vi antar att $X$ är en mängd:
- Om $X$ är en ordnad mängd, en tupel, etc. så bevaras ordningen men inte antalet element.
- (Man kan enkelt tänka sig versioner av filter som opererar på vektorer, tensorer, etc. och bevarar de ursprungliga dimensionerna genom att sätta element $x$ för vilka $p(x)$ inte är sant till t.ex. $0$ eller $\varnothing$ beroende på vad som är rimligt.)
filter
, exempel¶
In [16]:
nums = [1, 2, 3, 4, 5]
list(filter(lambda x: x%2==0, nums))
Out[16]:
[2, 4]
Kärt barn har många namn¶
- Heter
filter
i de flesta programmeringsspråk men kallas iblandwhere
,select
ellercopy-if
.
reduce
¶
- Tar en funktion $f$ av två värden, en ordnad samling $X$, och eventuellt ett startvärde $z$, och returnerar resultatet av att rekursivt ersätta de två första elementen i sekvensen med resultatet av att applicera funktionen på de elementen tills alla element reducerats till ett element.
- Om $X$ är en ordnad samling av 5 element*:
- (* En mer generell formel för samlingar av godtycklig längd har utelämnats för allas vår mentala hälsas skull. Redan konkateneringsoperatorn som vi skulle behöva introducera, $\frown$, kallas "frown" och det är det minst jobbiga i sammanhanget.)
reduce
, exempel¶
reduce
måste importeras från den inbyggda modulenfunctools
In [18]:
from functools import reduce
nums = [1, 2, 3, 4, 5]
reduce(lambda x, y: x*y, nums)
Out[18]:
120
Kärt barn har många namn¶
- Mer generellt kallas operationen för fold.
- Fold left när vi arbetar från början av sekvensen, och fold right från slutet av sekvensen.
- Själva funktionen kallas oftas just för
fold
ellerfoldl
/foldr
men man ser också t.ex.reduce
(Yay, Python!),accumulate
,aggregate
,compress
, ellerinject
.- (Det handlar alltså om ett extremt kärt, men väldigt förvirrat, barn.)
Kan vi generalisera tree_sum
-metoden?¶
- Högre ordningens funktioner till räddning!
- Vi skapar en metod
reduce
som tar en funktion av två argument, det första argumentet kommer alltid varaself.value
, det andra argumentet kommer vara en lista med resultaten av att ha anropatreduce
-metoden rekursivt på vart och ett av nodens barn.
In [48]:
class Node:
def reduce(self, fn):
reduced_children = []
for child in self._children:
reduced_children.append(child.reduce(fn))
return fn(self.value, reduced_children)
def apply(self, fn, depth=0):
self.value = fn(self.value, depth)
for child in self._children:
child.apply(fn, depth+1)
return self
def __init__(self, value, parent):
self.value = value
self._parent = parent
self._children = []
def add_child(self, value):
child = Node(value, self)
self._children.append(child)
return child
tree_root = Node(4, None)
node_8 = tree_root.add_child(8)
node_3 = node_8.add_child(3)
node_1 = node_8.add_child(1)
node_7 = node_1.add_child(7)
node_4 = node_1.add_child(4)
def depth_gauge(x, y):
if not y:
return 0
else:
return max(y)+1
print('tree_sum:', tree_root.reduce(lambda x, y: x + sum(y)))
print('list_representation:', tree_root.reduce(lambda x, y: [x] + y))
print('tree_depth:', tree_root.reduce(lambda x, y: depth_gauge(x, y)))
def printer(x, depth):
print(x, end=', ')
return x
def depth_printer(x, depth):
print(' '*depth + str(x))
print('Adding 1 to every node.')
tree_root.apply(lambda x, y: x+1)
print('Printing node values comma-separated:')
tree_root.apply(printer)
print('\nPrinting node values with depth to indicate nesting:')
tree_root.apply(depth_printer)
tree_sum: 27 list_representation: [4, [8, [3], [1, [7], [4]]]] tree_depth: 3 Adding 1 to every node. Printing node values comma-separated: 5, 9, 4, 2, 8, 5, Printing node values with depth to indicate nesting: 5 9 4 2 8 5
Out[48]:
<__main__.Node at 0x7fc3b0540c70>
Komposition¶
Exempel: En väska med dess delar och funktionalitet
Modell av 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.
Komposition av objekt¶
Bag
känner till en instans av klassenZipper
.Zipper
har rollen_zipper
iBag
.Bag
känner till en instans av klassenContainer
.Container
har rollen_container
iBag
.- Lämpligare här är dock att använda en komposition.
Komposition i klassdiagram¶
Komposition: Specialfall av association
- En-till-en- eller en-till-många-relationer. där de komponerade objekten inte kan existera utan kompositionen.
Vi säger att klass A komponerar (eng. composes) klass B och att klass B utgör en komponent i klass A.
Stark association
- En komponent kan inte existera utan kompositionen och kan bara ingå i en komposition i taget.*
- Vi kan jämföra en komposition och dess komponenter med en helhet och dess delar.
- (* Här avser vi i kontexten av vårt program. En komponent kan representera ett föremål som kan existera oberoende av kompositionen i en annan kontext, men det är hur programmet använder det som är det viktiga.)
Klassdiagram i UML med alla klasser¶
Bag
använder klassenZipper
för att representera funktionaliteten kring öppning och stängning av väskan.Bag
använder klassenContainer
för att representera funktionaliteten att kunna innehålla föremål.- Det är inte en regel att komponenter måste modellera funktionalitet snarare än fysiska föremål (
Zipper
kan ju sägas modellera båda) men det är förhållandevis vanligt.
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)
bagscript.py
¶
In [37]:
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)
Trying to open zipper... The zipper is open. Trying to add 'CANDY' to bag.. CANDY added. Trying to close zipper... The zipper is closed. Trying to open zipper... The zipper broke! Cannot open zipper. The zipper is broken. Trying to add 'EGG' to bag.. Open zipper first. Trying to close zipper... Cannot close zipper. The zipper is broken. Trying to add 'FLOWER' to bag.. Open zipper first. A green bag. The zipper is broken. The container is 20.0 percent full. Items: CANDY.
zcb.py
- 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. - När vi enbart ritar klassdiagrammet för
Bag
utan dess relationer kan vi ta med instansvariablerna_zipper
och_container
för tydlighets skull.
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}."
Demo¶
In [38]:
#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)
Trying to open zipper... The zipper broke! Cannot open zipper. The zipper is broken. Trying to add 'CANDY' to bag.. Open zipper first. Trying to close zipper... Cannot close zipper. The zipper is broken. Trying to open zipper... Cannot open zipper. The zipper is broken. Trying to add 'EGG' to bag.. Open zipper first. Trying to close zipper... Cannot close zipper. The zipper is broken. Trying to add 'FLOWER' to bag.. Open zipper first. A green bag. The zipper is broken. The container is 0.0 percent full. Items: None.
Laboration 6 - Del 1¶
- Uppdelning av problem - olika klasser har olika roller
- Representation av text, klasser för meningar och ord/skiljetecken
- Instans av
Text
innehåller instanser avSentence
som innehåller instanser avToken
, som innehåller sträng med ord/skiljetecken
Sammansättning och wrapper-metoder¶
- Wrapper-metod: En instans av klassen
Bag
innehåller en instans av klassenZipper
. MetodenBag.open
utgör en så kallad wrapper-metod eftersom dess enda syfte är att anropa metodenZipper.open
. - Användare av klassen
Bag
behöver inte känna till att klassenZipper
ens existerar eftersom enBag
-instans själv delegerar vidare uppgiften att öppna till sinZipper
-komponent.
class Bag(object):
def open(self):
self._zipper.open()
class Zipper(object):
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}")
Exempel, flera nivåer (3 klasser)¶
Bank har kunder. Kunder har konton
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)
In [ ]:
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}"
In [ ]:
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)
bank2.py
¶
In [ ]:
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)
bank3.py
¶
In [ ]:
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()