TDDE44 Programmering, grundkurs¶

Föreläsning 6.1-6.2¶

Johan Falkenjack, johan.falkenjack@liu.se¶

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¶

  1. Föreläsningarna går för långsamt/för fort, ca 50/50 split.
  2. Föreläsningarna är som bäst när jag live-kodar.
  3. Labbarna och labbassistenterna är jättebra, men lektionerna kunde vara mer givande.
  4. Föreläsningarna ger mer introduktion till byggstenarna än till hur man sammanfogar dem för att lösa problem.
  5. Det är väldigt mycket att göra med både pythonuppgifter och labbar.
  6. Rekursion har varit det svåraste momentet.
  1. (Detta är den bästa feedback:en jag kan få, det betyder att jag i snitt antagligen håller rätt lagom tempo.)
  2. (Då ska jag försöka göra det ännu mer.)
  3. (Vi tar med oss det. Förhoppningsvis ska lektion 6 vara lite mer intressant.)
  4. (Detta köper jag helt. Jag skulle vilja ägna mer tid åt problemlösning, men det är också något som man utvecklar över tid med mer övning.)
  5. (Japp!)
  6. (Vilken tur att vi återkommer till det nu i föreläsning och labb 6!)

Att programmera bättre?¶


generella principer

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
  • Bättre program (som körs)

    • Korrekthet: ger rätt resultat
    • Effektivitet: använder färre resurser (tid, minne, bandbredd)
    • Funktionalitet: löser fler olika problem
  • 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

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.)
  • Som lite mer erfaren, från högsta till lägsta abstraktionsnivå (och svårast till lättast att automatisera):
    • Acceptanstestning: Är beställaren nöjd med programmet? (kan per definition inte automatiseras.)
    • Systemtestning, i form av:
      • Funktionstestning: Uppfyller programmet de funktionella krav som ställts?
      • Återstartstestning: Klarar programmet att starta upp och fortsätta där det var efter en krasch?
      • Prestandatestning: Uppfyller programmet de funktionella krav som ställts på ett effektivt sätt?
      • Stresstestning: Klarar programmet överbelastning på ett bra sätt?
    • Integrationstestning: Fungerar olika komponenter med varandra?
    • Enhetstestning: Fungerar enskilda komponenter? (mer om detta i Föreläsning 7).
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()
  • Vi behöver inte spara alla varianter bara för att testa att det fungerar att skapa dem.
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
  • Det är okej att bryta inkapslingen FÖR ATT TESTA!!

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.
  • Hierarkisk struktur hos funktioner.
  • Objekt som använder andra objekt.
  • Klasser som ärver egenskaper av mer generella klasser.

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.
  • ___Single Responsibility Principle___
    • Ett objekt ska ha en roll. Komplicerade roller modelleras genom att sätta samman enklare objekt som interagerar med varandra via meddelanden och/eller delar funktionalitet med varandra.
    • En funktion ska ha en uppgift. Komplicerade problem hanteras genom funktioner som anropar varandra eller högre ordningens funktioner som beskriver generella mönster.

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".
  • Hur gör man det i kod?
    • Anropa en metod i ett annat objekt.
    • Applicera en annan, kanske mer generell, funktion.

Å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¶

No description has been provided for this image
  • 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
def check_consecutive(lst, predicate):
    for i in range(1, len(lst)):
        if predicate(lst[i-1], lst[i]):
            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])=}')
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¶

No description has been provided for this image

Klassdiagram, Laboration 5, Del 1


No description has been provided for this image
No description has been provided for this image

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?
  • Association: Allmän relation som indikerar att ett objekt på något sätt känner till ett annat objekt (dvs har en referens till det andra objektet).
  • Vi säger att klass A har en association till klass B
  • roll (i praktiken namn på instansvariabler), t.ex. "squares" i relationen mellan LayoutTester och tkinter.Label i labb 5.
    • Instansen/-erna av klassen B har rollen <roll> i klassen A.
  • 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, eller reflexiv, association ritas oftast utan pilar.
  • Exempel på roller:
    • lästa_böcker
    • squares
    • contacts
  • Exempel på multiplicitet
    • 0..1: noll eller en
    • 0..*: noll eller flera
    • 1..*: en eller fler
    • 3: exakt tre
    • 0..3: noll till tre
    • 5..9: fem till nio

Exempel¶


No description has been provided for this image
  • Ett TodoApp-objekt känner till exakt ett TaskList-objekt
  • Ett TaskList-objekt känner till 0 eller fler Task-objekt
  • "känner till" i kod = har en referens till

Aggregation¶

Exempel: Städer, hushåll och befolkning

Aggregering av data¶

  • Ett City-objekt har en lista över Household-objekt som representerar alla hushåll i staden.
  • Vi kan rita detta som en association från City till Household.
  • Lämpligare är dock att använda en aggregation.

UML_City_Household.svg

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 aggregerar Household och illustrerar detta med UMLs Aggregations-relation.
    • Ett aggregerande objekt, City, samlar ett flertal aggregerade objekt, Household, som existerar oberoende av det aggregerande objektet.

UML_City_Household_Aggregates.svg
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 av Household.
  • Household har koll på hur många som bor i varje hushåll.
  • City kan tillhandahålla befolkningssiffra genom att fråga varje Household-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. Varje Task 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¶

No description has been provided for this image
  • 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
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:])
    else:
        return tree[0] + tree_sum(tree[1:])

print(tree_sum(nested_list))

Trädstrukturer av objekt¶

No description has been provided for this image
  • 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)
# - Ofta vill vi representera liknande hierarkiska strukturer av objekt.
#- Vi börjar väldigt explicit genom att definiera klassen `Node` enligt UML-diagrammet till höger.
#- UML-diagrammet visar att `Node` aggregerar sig själv, dvs. varje `Node`-objekt kommer ha en samling "barn-noder".
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¶

No description has been provided for this image
  • 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)
# Notera att vi inte skapar en ny `Node`-instans först och lägger till den med `add_child`.
#    - Istället låter vi `add_child` ta ett godtyckligt värde och skapa en ny nod med det värdet.
#- Vi låter också `add_child` returnera den nya `Node`-instansen så att vi enkelt kan fortsätta arbeta med den.
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¶

No description has been provided for this image
  • 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)
# - Ofta vill vi lagra information både om en nods "förälder" och om dess "barn" så att vi enkelt kan navigera både uppåt och nedåt i trädet?
#- Ett vanligt mönster om man vill att barnet ska känna till föräldern är att skicka med föräldern när man skapar barnet.
#    - Vi låter rot-noden ha föräldern `None`.
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¶

No description has been provided for this image
  • Alla noder måste per definition hänga ihop, alltså:
  • Ingen klass Tree behövs, det räcker med Node.

Träd med Node¶

No description has been provided for this image
  • 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)
root = Node(4, None)
node_8 = root.add_child(8)
node_8.add_child(3)
node_1 = node_8.add_child(1)
node_1.add_child(7)
node_1.add_child(4)

Trädsumma med Node¶

No description has been provided for this image
  • 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
def tree_sum(root):
    result = root.value
    for child in root._children:
        result += tree_sum(child)
    return result

def tree_sum_rec(root):
    def recurse(children):
        if not children:
            return 0
        else:
            return tree_sum_rec(children[0]) + recurse(children[1:])
    return root.value + recurse(root._children)

print(tree_sum(root))
print(tree_sum_rec(root))

Vad gjorde jag för fel i tree_sum?¶

Jag bröt inkapslingen!¶


(och det var inte i en testfunktion)

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!¶

No description has been provided for this image
  • 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
class Node:

    def tree_sum(self):
        child_sums = []
        for child in self._children:
            child_sums.append(child.tree_sum())
        return self.value + sum(child_sums)

    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

root = Node(4, None)
node_8 = 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)

print(root.tree_sum())

Ä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 som check_consecutive?
  • Ja! Vi skapar den högre ordningens funktionen reduce.
  • Vi kan specificera att vår nod-klass är just för heltal, men då blir hela klassen mindre generell.
  • Vi kan implementera massor olika special-metoder för att få ut olika aggregerade värden över grafer, men det blir snabbt otympligt.

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:
$$ map(f, X) = \{y | y = f(x), x \in X\}\\ \\ \text{m.a.o.}\\ \\ map(f, X) = \{f(x_1), f(x_2), f(x_3), ..., f(x_n)\} $$
  • 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]
print(list(map(lambda x: x**3, nums)))

Kärt barn har många namn¶

  • Heter map i de flesta programmeringsspråk men kallas ibland apply, transform, eller collect

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:
$$ filter(p, X) = \{ x | x \in X \text{ och } p(x) \text{ är sant}\} $$
  • 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]
print(list(filter(lambda x: x%2==0, nums)))

Kärt barn har många namn¶

  • Heter filter i de flesta programmeringsspråk men kallas ibland where, select eller copy-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(f, X) = f(f(f(f(x_1, x_2), x_3), x_4), x_5)\\ \text{ eller, om }z\text{ angetts}\\ reduce(f, X, z) = f(f(f(f(f(z, x_1), x_2), x_3), x_4), x_5) $$

reduce, exempel¶

  • reduce måste importeras från den inbyggda modulen functools
In [18]:
from functools import reduce

nums = [1, 2, 3, 4, 5]

reduce(lambda x, y: x*y, nums)
Out[18]:
120
print(reduce(lambda x, y: x*y, nums))

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 eller foldl/foldr men man ser också t.ex. reduce (Yay, Python!), accumulate, aggregate, compress, eller inject.
    • (Det handlar alltså om ett extremt kärt, men väldigt förvirrat, barn.)

Kan vi generalisera tree_sum-metoden?¶

No description has been provided for this image
  • 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 vara self.value, det andra argumentet kommer vara en lista med resultaten av att ha anropat reduce-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>
def reduce(self, fn):
        reduced_children = []
        for child in self._children:
            reduced_children.append(child.reduce(fn))
        return fn(self.value, reduced_children)

    def tree_apply(self, fn, depth=0):
        self.value = fn(self.value, depth)
        for child in self._children:
            child.tree_apply(fn, depth+1)

def depth_gauge(x, y):
    if not y:
        return 0
    else:
        return max(y)+1

print(root.tree_reduce(lambda x, y: x + sum(y)))
print(root.tree_reduce(lambda x, y: [x] + y))
print(root.tree_reduce(lambda x, y: depth_gauge(x, y)))

def printer(x, depth):
    print(x)
    return x

def depth_printer(x, depth):
    print('  '*depth + str(x))

#root.tree_apply(lambda x, y: x+1)
#root.tree_apply(printer)
root.tree_apply(depth_printer)

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 klassen Zipper. Zipper har rollen _zipper i Bag.
  • Bag känner till en instans av klassen Container. Container har rollen _container i Bag.
  • Lämpligare här är dock att använda en komposition.
UML_Zipper_Container_Bag_Association_encapsulated.svg

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 klassen Zipper för att representera funktionaliteten kring öppning och stängning av väskan.
  • Bag använder klassen Container 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.
UML_Zipper_Container_Bag_Composition_encapsulated.svg

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¶

No description has been provided for this image
  • Klassen Bag i sin tur använder sig av andra abstraktioner. Vi har klasserna Zipper och Container 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¶

No description has been provided for this image
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¶

No description has been provided for this image
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¶

No description has been provided for this image
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 av Sentence som innehåller instanser av Token, 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 klassen Zipper. Metoden Bag.open utgör en så kallad wrapper-metod eftersom dess enda syfte är att anropa metoden Zipper.open.
  • Användare av klassen Bag behöver inte känna till att klassen Zipper ens existerar eftersom en Bag-instans själv delegerar vidare uppgiften att öppna till sin Zipper-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.
No description has been provided for this image

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 listan clients
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-klassen
    • new_client: skapa kund, se till att varje kund har ett konto
    • new_account: skapa nytt konto, måste ha en kund att koppla det till instansvariaben num_accounts för antal konton som skapats
  • Client-klassen
    • add_account: lägg till konto till kund
    • En kund (Client-instans) har minst ett konto (en Account-instans)
  • Relationer
    • Bank-instansen känner till alla Client-instanser och alla Account-instanser
    • Client-instanser har referenser till alla dess Account-instanser

Metoder istället för att jobba direkt med instansvariablerna¶


No description has been provided for this image

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)
  • Logistik för Client- och Accountinstanser i Bank-klassen

  • Osynlig logistik vid användning - delegerats till Bank-klassen

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 till new_account
  • Client-klassen
    • instansvariaben client_id

Exempel på förändring av implementation av modell/abstaktion: dictionarys istället för listor¶


No description has been provided for this image

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()