729G46 Informationsteknologi och programmering¶

Tema 5, Föreläsning 5¶

Johan Falkenjack, johan.falkenjack@liu.se¶

Besök av styrelsen för KogVet-sektionen direkt efter föreläsningen¶

Översikt¶

  • Temauppgift 5
    • Grafiska gränssnitt, som exempel på tillämpning av OOP
    • Egen algoritm som placerar ut kvadrater efter ett visst mönster.
  • Seminarium / Rapport Tema 5
    • Utbrytning av funktioner
  • Mer om funktioner
    • default-värden för argument
    • funktionsobjekt
  • Storseminarium: Grafiska gränssnitt och händelsedriven programmering
    • Vad är ett GUI?
    • Olika former av programflöde
    • GUI-programmering i Python med grid
  • Bryta ut funktioner
  • Högre ordningens funktioner

Tema 5

Abstraktion/dekomposition i praktiken och grafiska gränssnitt

Temauppgift 5¶

  • Del 1: Grafiska gränssnitt med tkinter.
  • Del 2: Utplacering av kvadrater i rader/kolumner.
  • Utökade kodkrav (PEP8 & PEP257 gäller fortfarande):
    • Variabelnamn
    • Hårdkodade värden
    • Kommentarer för alla satser som är två eller fler rader (dvs alla if-satser, loopkonstruktioner, m.m.)
  • Rekommendation: Dela upp er kod i funktioner för att öva och att visa att ni vet hur man gör.

Utvecklingsseminarium och Rapport Tema 5¶

  • Dela upp problem i delproblem.
  • Seminarium: Öva (förberedelser) och diskutera hur kod kan brytas ut.
  • Rapport: Bryta ut funktioner från befintlig kod.

Repetition, f-strängar¶

  • Sedan Python 3.6 finns f-strängar (f-strings)
  • I f-strängar kan vi skriva pythonuttryck innanför måsvingar med automatisk konvertering till strängar!
    • Mycket smidigt för att skriva ut variabler!
In [1]:
l = ["en", "lista", "med", "värden"]
print(f"första värdet: {l[0]}")
första värdet: en
  • Sedan Python 3.8 kan man även använda = efter ett uttryck för att själva uttrycket också ska skrivas ut.
In [2]:
s = "batteri"
print(f"{s=}")
s='batteri'

Repetition, funktionsobjekt

... även funktioner är objekt i Python

Funktionsobjekt¶

  • Vi såg när vi arbetade med closures att vi kunde returnera funktioner från andra funktioner.
  • Vi kan alltså betrakta funktioner som värden, dvs. som objekt.
  • Precis som med literaler som 1, "kogvet" eller [1, 2, 3] så är instansieringen implicit.
    • Dvs. vi behöver inte skriva int(1) för att skapa ett heltalsobjekt, pythontolken skapar objektet när den stöter på literalen 1.
  • På samma sätt skapas funktionsobjekt när pythontolken stöter på en funktionsdefinition:
    def hejsan():
        print("Hejsan")
    
  • Anrop av funktionen: hejsan()
  • Funktionsobjektet: hejsan

Exempel på funktionsobjekt¶

In [4]:
def print_hello():
    print("Hello World!")

another_function_name = print_hello
another_function_name()
Hello World!

Högre ordningens funktioner¶

Konsekvenser av att funktioner är objekt¶

  • Funktioner är data (minns von Neumann-arkitekturen där både data och program lagras på samma sätt)
  • Funktioner kan returnera funktioner
  • Variabler kan tilldelas funktioner som värden
  • Funktioner kan ta andra funktioner som argument
In [5]:
my_length_fun = len
another_print = print
another_print(my_length_fun(["a","short","list"]))
3

Högre ordningens funktioner¶

  • Funktioner som opererar på funktioner.
    • Dvs. tar en eller flera funktioner som argument, och/eller returnerar funktionsobjekt.
  • Funktionerna som skapade closures var t.ex. högre ordningens funktioner.
  • Centrala i funktionell programmering, men användbara även i andra sammanhang

Bubble sort med flexibel sorteringsordning¶

In [6]:
def bubble_sort(alist, ascending=True):
    for i in range(len(alist)-1, 0, -1):
        for j in range(i):
            if ascending:
                if alist[j] > alist[j+1]:
                    alist[j], alist[j+1] = alist[j+1], alist[j]
            else:
                if alist[j] < alist[j+1]:
                    alist[j], alist[j+1] = alist[j+1], alist[j]

numbers = [54,26,93,17,77,31,44,55,20]
bubble_sort(numbers, True)
print("Asc:", numbers)
bubble_sort(numbers, False)
print("Desc:", numbers)
Asc: [17, 20, 26, 31, 44, 54, 55, 77, 93]
Desc: [93, 77, 55, 54, 44, 31, 26, 20, 17]

Bubble sort som högre ordningens funktion¶

  • Vi tar in en sorteringsnyckel som säger vad som ligger till grund för sorteringen.
In [10]:
def bubble_sort(alist, comp_fun):
    for i in range(len(alist)-1, 0, -1):
        for j in range(i):
            if comp_fun(alist[j], alist[j+1]):
                alist[j], alist[j+1] = alist[j+1], alist[j]

def ascending(val1, val2):
    return val1 > val2

def descending(val1, val2):
    return val1 < val2
            
numbers = [54,26,93,17,77,31,44,55,20]
bubble_sort(numbers, ascending)
print("Asc:", numbers)
bubble_sort(numbers, descending)
print("Desc:", numbers)
Asc: [17, 20, 26, 31, 44, 54, 55, 77, 93]
Desc: [93, 77, 55, 54, 44, 31, 26, 20, 17]
In [ ]:
def bubble_sort(alist, order_fn):
    for i in range(len(alist)-1, 0, -1):
        for j in range(i):
            # parametern order_fn används för jämförelsen
            if order_fn(alist[j], alist[j+1]):
                alist[j], alist[j+1] = alist[j+1], alist[j]

def asc_order(value1, value2):
    return value1 > value2

def desc_order(value1, value2):
    return value1 < value2

numbers = [54,26,93,17,77,31,44,55,20]
bubble_sort(numbers, asc_order)
print("Asc:", numbers)
bubble_sort(numbers, desc_order)
print("Desc:", numbers)

Insertion sort som högre ordningens funktion¶

In [11]:
def insertionsort(alist, order_fn):
    for i in range(1, len(alist)):
        currentvalue = alist[i]
        pos = i
        # parametern order_fn används för jämförelsen
        while (pos >= 1 and order_fn(alist[pos-1], currentvalue)):
            alist[pos]=alist[pos-1]
            pos -= 1
        alist[pos]=currentvalue

numbers = [54,26,93,17,77,31,44,55,20]
insertionsort(numbers, descending)
print("Desc:", numbers)
insertionsort(numbers, ascending)
print("Asc:", numbers)
Desc: [93, 77, 55, 54, 44, 31, 26, 20, 17]
Asc: [17, 20, 26, 31, 44, 54, 55, 77, 93]

Mer komplexa sorteringsnycklar¶

In [16]:
class Person:
    def __init__(self, given_name, family_name, pnr):
        self.given_name = given_name
        self.family_name = family_name
        self.pnr = pnr
    
    def __repr__(self):
        return f"Person('{self.given_name}', '{self.family_name}', '{self.pnr}')"

teachers = [Person('Alva', 'Söderlind', '20031337-8633'), Person('Charlie', 'Simonsson', '19801337-4359'), Person('Elvira', 'Ling', '20031337-5276'), Person('Johan', 'Falkenjack', '19861337-2255'), Person('Kacper', 'Sieklucki', '20031337-8051'), Person('Simon', 'Ekman', '20011337-2988')]

def asc_pnr(val1, val2):
    return val1.pnr > val2.pnr

bubble_sort(teachers, asc_pnr)
print("Asc:", teachers)
Asc: [Person('Charlie', 'Simonsson', '19801337-4359'), Person('Johan', 'Falkenjack', '19861337-2255'), Person('Simon', 'Ekman', '20011337-2988'), Person('Elvira', 'Ling', '20031337-5276'), Person('Kacper', 'Sieklucki', '20031337-8051'), Person('Alva', 'Söderlind', '20031337-8633')]
In [ ]:
class Person:
    def __init__(self, given_name, family_name, pnr):
        self.given_name = given_name
        self.family_name = family_name
        self.pnr = pnr
        
    def __str__(self):
        return f"{self.pnr}: {self.family_name}, {self.given_name}"
    
    def __repr__(self):
        return f"Person('{self.given_name}', '{self.family_name}', '{self.pnr}')"
    
teachers = [Person('Alva', 'Söderlind', '20031337-8633'), Person('Charlie', 'Simonsson', '19801337-4359'), Person('Elvira', 'Ling', '20031337-5276'), Person('Johan', 'Falkenjack', '19861337-2255'), Person('Kacper', 'Sieklucki', '20031337-8051'), Person('Simon', 'Ekman', '20011337-2988')]

def pnr_asc_order(value1, value2):
    return value1.pnr > value2.pnr

Det blir ju sjukt många små funktioner som bara används en gång!¶

Anonyma funktioner¶

  • Vi behöver inte alltid spara ett värde i en variabel för att kunna använda det.
    • sorted([2, 3, 1])
    • print(str(1))
  • Vi har konstaterat att funktioner också är objekt.
  • Funktioner som vi skapat med def måste vi dock ge ett namn, det kräver syntaxen för def.
  • Vi kan dock skapa enklare funktioner utan namn.

Lambda-uttryck¶

  • Lambda-uttryck är uttryck som returnerar ett funktionsobjekt med följande begränsningar:
    • Funktionskroppen måste bestå av exakt ett uttryck (men det uttrycket får vara hur komplext som helst).
    • Värdet av det uttrycket kommer att returneras när funktionen anropas.
  • Syntaxen är:
    lambda arg1, arg2, ...: do_something(arg1, arg2, ...)
    
  • Vi kan använda lambda-uttryck för att snabbt skapa små funktioner som bara används på ett ställe.
  • T.ex. funktion som returnerar det första elementet i en sekvens
In [17]:
print(lambda x: x[0]) # skapa anonym funktion
print(lambda x: x[0]('abc')) # otydligt, är ('abc') en del av funktionskroppen?
print((lambda x: x[0])('abc')) # evaluera lambda-uttrycket till ett funktionsobjekt först, sedan anropar vi det
<function <lambda> at 0x7f0991893490>
<function <lambda> at 0x7f0991893490>
a

Lambda-uttryck för sortering¶

  • Kanske en funktion som jämför två värden?
In [18]:
# class Person:
#     def __init__(self, given_name, family_name, pnr):
#         self.given_name = given_name
#         self.family_name = family_name
#         self.pnr = pnr

bubble_sort(teachers, lambda val1, val2: val1.family_name > val2.family_name)
print(teachers)
[Person('Simon', 'Ekman', '20011337-2988'), Person('Johan', 'Falkenjack', '19861337-2255'), Person('Elvira', 'Ling', '20031337-5276'), Person('Kacper', 'Sieklucki', '20031337-8051'), Person('Charlie', 'Simonsson', '19801337-4359'), Person('Alva', 'Söderlind', '20031337-8633')]

Inbyggda högre ordningens funktioner: sorted¶

In [19]:
# Vi har en lista av ord-ordfrekvenspar
wordlist = [("ord1", 10), ("ord2", 32), ("ord3", 5)]

# Vi skriver en funktion som tar ett ordfrekvenspar och returnerar dess frekvens
def get_freq(word_and_freq):
    return word_and_freq[1]

# vi kan nu skicka med funktionen till sort för att sortera listan efter frekvens
print(f"{sorted(wordlist, key=get_freq)=}")

# eller med lambda
print(f"{sorted(wordlist, key=lambda word_and_freq: word_and_freq[1])=}")
sorted(wordlist, key=get_freq)=[('ord3', 5), ('ord1', 10), ('ord2', 32)]
sorted(wordlist, key=lambda word_and_freq: word_and_freq[1])=[('ord3', 5), ('ord1', 10), ('ord2', 32)]

Inbyggda högre ordningens funktioner: max och min¶

  • Funktionerna max() och min() kan ta in en funktion som används för att ta fram det värde som ska jämföras:
In [20]:
wordlist = [("ord1", 10), ("ord2", 32), ("ord3", 5)]

# Mest frekventa ordet, funktionsargumentet ska returnera det värde
# som max() ska använda för att jämföra element med varandra
print(f"max_word = {max(wordlist, key=lambda wf: wf[1])}")

# Minst frekventa ordet, samma som max()
print(f"min_word = {min(wordlist, key=lambda wf: wf[1])}")
 
# Vi kan använda samma princip för att hitta nyckeln med det största värdet i en dict
contestants = {"ada": 1, "boris": -10, "cissi": 5, "doris": 3}
print(f"winner = {max(contestants, key=lambda name: contestants[name])}")
max_word = ('ord2', 32)
min_word = ('ord3', 5)
winner = cissi

Inbyggda högre ordningens funktioner: filter()¶

  • Argument till funktionen filter():
    • en funktion
    • en iterable (något itererbart, t.ex. en lista, en dictionary-vy, etc.)
  • Ger oss en iterator över alla element som funktionen returnerar True för
    • En iterator är ett objekt som stegvis genererar en sekvens element för element
In [21]:
wordlist = [["apa", 10], ["fisk", 32], ["alligator", 5] ]
# ta fram alla ord i wordlist som börjar på "a"
# första argumentet är en funktion som returnerar True för de element som ska behållas
print(f"a_list = {filter(lambda wf: wf[0].startswith('a'), wordlist)}")

for val in filter(lambda wf: wf[0].startswith('a'), wordlist):
    print(val)

# ta fram alla ord i words vars sista tecken är char
words = ["bror", "bok", "bar", "barberare"]
char = "r"
print(f"ends_with_char = {list(filter(lambda word: word[-1] == char, words))}")

# titta bara på rader som inte har "N/A" som värde på index 2
#for row in filter(lambda r: r[2] != "N/A", csv_data):
#    pass # gör grejer
a_list = <filter object at 0x7f09924511e0>
['apa', 10]
['alligator', 5]
ends_with_char = ['bror', 'bar']

Lambda-uttryck som callbacks¶

  • I grafiska gränssnitt används ofta specifika funktioner, så kallade callbacks, som bara anropas från en viss komponent när en viss interaktion sker.
    • (Mer om callbacks i förberedelsematerialet inför storseminarium 5.)
  • Lambda-uttryck lämpar sig särskilt bra för den här tillämpningen eftersom callbacks ofta skapas för och lagras tillsammans med en specifik komponent och inte behöver ett särskilt namn utöver det.

Flera sätt att binda värden till funktionsparametrar

Fem olika metoder

Enskilda parameterbindningar har vi sett förut¶

  • Positionella argument:
    • Argumenten anges i en viss ordning och binds till motsvarande parameter.
    • Måste alltid komma först vid funktionsanrop
    • Parameternamn behöver inte anges vid anrop.
    • (Det vi sett mest av sedan tidigare.)
  • Default-argument:
    • Argument som inte behöver anges då motsvarande parameter tilldelats ett standardvärde när funktionen definierades.
    • Många inbyggda funktioner har dessa, men de används bara i specialfall.
    • När man vill ha ett standardbeteende, men ge utrymme för variation. Exempel: print, open, sorted, max, min
    • (Har vi använt, men oftast inte sett, av naturliga skäl.)
  • Nyckelordsargument:
    • Alla argument binds till någon parameter, med nyckelordsargument kan vi explicit säga vilket argument som ska bindas till vilken namngiven parameter.
    • Används främst för att
    • Nästan alla parametrar (med undantaget self i metoddefinitioner) kan bindas mha. nyckelordsargument (sedan Python 3.8 kan vi i undantagsfall indikera parametrar som bara får bindas positionellt, men det är jätteöverkurs).

Enskilda parameterbindningar¶

In [22]:
def happy_anniversary(name, occasion="födelsedagen", num=1):
    print(f"Grattis på {occasion}, {name}!")
    print(f"Tänk att du firat detta {num} gånger nu!")
happy_anniversary("Ragiähl")
Grattis på födelsedagen, Ragiähl!
Tänk att du firat detta 1 gånger nu!
In [23]:
happy_anniversary("Önde", "namnsdagen, eller inte", 0)
Grattis på namnsdagen, eller inte, Önde!
Tänk att du firat detta 0 gånger nu!
In [24]:
happy_anniversary(occasion="examensdagen", name="Gjohl")
Grattis på examensdagen, Gjohl!
Tänk att du firat detta 1 gånger nu!

Sammansatta parameterbindningar har vi inte sett¶

  • *args:
    • Ett obegränsat antal positionella argument som inte behöver ha definierats när funktionen definierades.
    • Representeras inne i funktionen av en tupel.
    • Oftast *args men man ser ibland varianter som *objects.
    • "Övriga positionella argument."
  • **kwargs:
    • Ett obegränsat antal nyckelordsargument som inte behöver ha definierats när funktionen definierades.
    • Representeras inne i funktionen av ett dictionary.
    • I princip alltid **kwargs.
    • "Övriga nyckelordsargument."

Sammansatta parameterbindningar¶

In [30]:
def add(term1, term2):
    return term1 + term2

def multiply(factor1, factor2):
    return factor1 * factor2

def aggregate(*args, function=add):
    result = args[0]
    for arg in args[1:]:
        result = function(result, arg)
    return result

aggregate(1,2,3,4,5,6)
Out[30]:
21
In [29]:
aggregate(1,2,3,4,5, function=multiply)
Out[29]:
120

Sammansatta parameterbindningar¶

In [31]:
def print_pairs(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")
        
print_pairs(name="Norville Rogers", hometown="Coolsville, OH", company="Mystery, Inc.")
print_pairs(course_name="Graph Theory", exam1="0 points", exam2="3 points", grade="Let's never talk about this again.")
name: Norville Rogers
hometown: Coolsville, OH
company: Mystery, Inc.
course_name: Graph Theory
exam1: 0 points
exam2: 3 points
grade: Let's never talk about this again.

Exempel med print

No description has been provided for this image

Exempel med print¶

In [33]:
print("We", "can", "actually", "add", "however", "many", "arguments", "we", "want")
We can actually add however many arguments we want
In [34]:
print("We can", "also change", "the separator", sep="\n")
We can
also change
the separator
In [35]:
print("or the", end=' ')
print("end")
or the end
In [36]:
with open("printtest.txt", 'w') as outfile:
    print("Or print to a file rather than stdout!", file=outfile)

Fallet open


No description has been provided for this image

Fallet open, forts.


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

Undersöka/testa kod¶

Kod-disposition¶

  • importer
  • globala variabler och konstanter
  • definitioner (funktioner, klasser och metoder)
  • delen av koden som faktiskt gör något, helst innanför en
if __name__ == "__main__":
 # kod här

Olika sätt att undersöka sin kod¶

  • Skriv ut variabler, delresultat, resultat, etc. (spårutskrifter)
    • Strössla gärna med spårutskrifter, men gör dem tydliga
    • Skriver ni ut variabler med samma namn eller liknande värden, var gärna övertydliga
def my_clever_function(an_argument, another_argument):
    print("\n--------------------\n")
    print("In my_clever_function:")
    print(f"{an_argument=}")
    print(f"{another_argument=}")
  • Testa med assertions

    • syntax:
    assert sanningsuttryck
    
    • ger ett AssertionError om sanningsuttrycket är falskt
assert my_function(5) > 100
assert my_sorted([5, 2, 6]) == [2, 5, 6]

Exempel, assert¶

  • Skriv funktionen count_integers(value_list) som returnerar antalet heltal i en lista (argumentet value_list)
In [38]:
def count_integers(value_list):
    num_ints = 0
    for value in value_list:
        if type(value) == int:
            num_ints += 1
    return num_ints

print(f"{count_integers([1, 2, 3, 4])=}")
assert count_integers([1, 2, 3, 4, 5]) == 5
assert count_integers([1, 2, 3, 4, 5]) == 4
count_integers([1, 2, 3, 4])=4
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[38], line 10
      8 print(f"{count_integers([1, 2, 3, 4])=}")
      9 assert count_integers([1, 2, 3, 4, 5]) == 5
---> 10 assert count_integers([1, 2, 3, 4, 5]) == 4

AssertionError: 

Bryta ut funktioner¶

Bryta ut funktioner, saker att tänka på¶

  • Scope
    • globala variabler
    • lokala variabler
  • Lokala variabler
    • alla variabler som definieras inne i en funktion (inkl. argumenten) är bara tillgängliga inuti funktionen
  • Globala variabler
    • variabler som definieras utanför funktioner är globala
    • om de ska användas inuti en funktion bör nyckelordet global användas, men...
  • Undvik att använda globala variabler. Konstanter är ok, använd i så fall variabelnamn med endast versaler.

Exempel, scope¶

  • Globala och lokala variabler: localglobal.py
    • https://trinket.io/python3/923b3c723d
  • Varför blir det fel: localvars.py
    • https://trinket.io/python3/cfcddbf3ce
  • Varför blir det fel: globalvars.py
    • https://trinket.io/python3/f27c4c8c94
In [39]:
# localglobal.py

def print_global_name():
    """Skriv ut globala variabeln name."""
    global name
    print(f"global name: {name}")

def change_and_print_local_name(name):
    """Ändra och skriv utlokala variabeln name."""
    # alla variabler i en funktion är lokala om inte annat anges
    secret = "When this function returns, I am gone."
    name = name * 2
    print(f"changed local name: {name}")

def change_and_print_global_name():
    """Ändra och skriv ut globala variabeln name."""
    global name
    name = name * 2
    print(f"changed global name: {name}")

name = "Ada"
print_global_name()
change_and_print_local_name(name)
print_global_name()
change_and_print_global_name()
print_global_name()
global name: Ada
changed local name: AdaAda
global name: Ada
changed global name: AdaAda
global name: AdaAda
In [40]:
# localvars.py

def find_min_and_max(values):
    min_val = None
    max_val = None
    for value in values:
        if type(value) != int:
            continue
        if min_val is None and max_val is None:
            min_val = value
            max_val = value
            continue

        if value > max_val:
            max_val = value
        if value < min_val:
            min_val = value

    print(f"min: {min_val}, max: {max_val}")

my_values1 = ["a", 10, 90, -100, 80, 900, "nehepp"]
my_values2 = ["a", 1, 9, -10, 8, 90, "jahapp"]
find_min_and_max(my_values1)
find_min_and_max(my_values2)

# Här går koden sönder, varför?
print(max_val)
print(min_val)
min: -100, max: 900
min: -10, max: 90
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[40], line 27
     24 find_min_and_max(my_values2)
     26 # Här går koden sönder, varför?
---> 27 print(max_val)
     28 print(min_val)

NameError: name 'max_val' is not defined
In [41]:
# globalvars.py

min_val = None
max_val = None

def find_min_and_max(values):
    global min_val, max_val
    for value in values:
        if type(value) != int:
            continue
        # om vi inte har något min/max än
        if min_val is None and max_val is None:
            min_val = value
            max_val = value
            continue

        # uppdatera när vi hittar nytt min/max
        if value > max_val:
            max_val = value
        if value < min_val:
            min_val = value

    print(f"min: {min_val}, max: {max_val}")

my_values1 = ["a", 10, 90, -100, 80, 900, "nehepp"]
my_values2 = ["a", 1, 9, -10, 8, 90, "jahapp"]

find_min_and_max(my_values1)
print(max_val)
print(min_val)

# varför blir det fel här? Minsta heltalet är ju -10 i my_values2
find_min_and_max(my_values2)
print(max_val)
print(min_val)
min: -100, max: 900
900
-100
min: -100, max: 900
900
-100

Varför bryta ut funktion?¶

  • Minska upprepad kod
    • lättare att underhålla kod och uppdatera kod
    • lättare att läsa kod, inte lika lång
  • Minska väldigt snarlik kod
    • ersätt snarlika kodavsnitt med funktion som anpassar beteende givet argumenten
  • Underlätta läsning av kod – givet att bra funktionsnamn används