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å literalen1.
- Dvs. vi behöver inte skriva
- 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]
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')]
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
defmåste vi dock ge ett namn, det kräver syntaxen fördef. - 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()ochmin()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
Truefö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
selfi 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
*argsmen 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
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
Fallet open, forts.
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
AssertionErrorom 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 (argumentetvalue_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
- Varför blir det fel: localvars.py
- Varför blir det fel: globalvars.py
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