Funktioner som argument
Vi är redan bekanta med metoden sort
och funktionen sorted
, som används för att sortera listor i deras naturliga ordning. För siffror och strängar fungerar detta vanligtvis bra. För allt som är mer komplicerat än dem så är dock den naturliga ordningen på föremål enligt Python inte alltid det som vi som programmerare avser.
Till exempel sorteras som standard en lista med tupler baserat på det första objektet i varje tupel:
produkter = [("banan", 5.95), ("äppel", 3.95), ("apelsin", 4.50), ("vattenmelon", 4.95)]
produkter.sort()
for produkt in produkter:
print(produkt)
('apelsin', 4.5) ('banan', 5.95) ('vattenmelon', 4.95) ('äppel', 3.95)
Men vad händer om vi vill sortera listan baserat på priset?
Funktioner som argument
En sorteringsmetod eller -funktion accepterar vanligtvis ett valfritt andra argument som gör att du kan kringgå standardsorteringskriterierna. Detta andra argument är en funktion som definierar hur värdet på varje föremål i listan bestäms. När listan sorteras anropar Python denna funktion när den jämför föremålen med varandra.
Låt oss ta en titt på ett exempel:
def prisordning(foremal: tuple):
# Returnerar tupelns andra föremål, alltså priset
return foremal[1]
if __name__ == "__main__":
produkter = [("banan", 5.95), ("äppel", 3.95), ("apelsin", 4.50), ("vattenmelon", 4.95)]
# Använd funktionen prisordning för sortering
produkter.sort(key=prisordning)
for produkt in produkter:
print(produkt)
('äppel', 3.95) ('apelsin', 4.5) ('vattenmelon', 4.95) ('banan', 5.95)
Nu är listan sorterad utifrån artiklarnas priser, men vad händer egentligen i programmet?
Funktionen prisordning
är faktiskt ganska enkel. Den tar ett objekt som sitt argument och returnerar ett värde för det objektet. Mer specifikt returnerar den det andra objektet i tupeln, som representerar priset. Men sedan har vi den här kodraden, där sort
-metoden anropas:
produkter.sort(key=prisordning)
Här anropas sort
-metoden med en funktion som argument. Detta är inte en referens till funktionens returvärde, utan en referens till själva funktionen. Sort
-metoden anropar denna funktion flera gånger och använder varje objekt i listan som argument i tur och ordning.
Om vi inkluderar en extra print-sats i funktionsdefinitionen för prisordning
kan vi verifiera att funktionen verkligen anropas en gång för varje objekt i listan:
def prisordning(foremal: tuple):
# Skriver ut föremålet
print(f"Anropade prisordning({foremal})")
# Returnerar tupelns andra föremål, alltså priset
return foremal[1]
produkter = [("banan", 5.95), ("äppel", 3.95), ("apelsin", 4.50), ("vattenmelon", 4.95)]
# Använd funktionen prisordning för sortering
produkter.sort(key=prisordning)
for produkt in produkter:
print(produkt)
Anropade prisordning(('banan', 5.95)) Anropade prisordning(('äppel', 3.95)) Anropade prisordning(('apelsin', 4.5)) Anropade prisordning(('vattenmelon', 4.95)) ('äppel', 3.95) ('apelsin', 4.5) ('vattenmelon', 4.95) ('banan', 5.95)
Ordningen kan vändas med ett annat nyckelordsargument; reverse
, som är tillgängligt med både sort
-metoden och funktionen sorted
:
produkter.sort(key=prisordning, reverse=True)
t2 = sorted(produkter, key=prisordning, reverse=True)
En funktionsdefinition inom en funktionsdefinition
Vi kan också inkludera en namngiven funktion för den nya prisbaserade sorteringsfunktionen som vi har skapat. Låt oss lägga till en funktion med namnet sortera_enligt_pris
:
def prisordning(foremal: tuple):
return foremal[1]
def sortera_enligt_pris(foremalen: list):
# Använd funktionen prisordning här
return sorted(foremalen, key=prisordning)
produkter = [("banan", 5.95), ("äppel", 3.95), ("apelsin", 4.50), ("vattenmelon", 4.95)]
for produkt in sortera_enligt_pris(produkter):
print(produkt)
Om vi vet att hjälpfunktionen prisordning
inte används någonstans utanför funktionen sortera_enligt_pris
, kan vi placera den första funktionsdefinitionen inom den senare:
def sortera_enligt_pris(foremalen: list):
# hjälpfunktion definierad inom funktionen
def prisordning(foremal: tuple):
return foremal[1]
return sorted(foremalen, key=prisordning)
Sortering av samlingar av egna objekt
Låt oss med samma princip skriva ett program som sorterar en lista med objekt från vår egen klass Studerande
på två olika sätt:
class Studerande:
""" Klassen modellerar en enkel studerande """
def __init__(self, namn: str, id: str, poang: int):
self.namn = namn
self.id = id
self.poang = poang
def __str__(self):
return f"{self.namn} ({self.id}), {self.poang} sp."
def enligt_id(foremal: Studerande):
return foremal.id
def enligt_poang(foremal: Studerande):
return foremal.poang
if __name__ == "__main__":
s1 = Studerande("Anton", "a123", 220)
s2 = Studerande("Maja", "m321", 210)
s3 = Studerande("Anna", "a999", 131)
studeranden = [s1, s2, s3]
print("Enligt id:")
for studerande in sorted(studeranden, key=enligt_id):
print(studerande)
print()
print("Enligt poäng:")
for studerande in sorted(studeranden, key=enligt_poang):
print(studerande)
Enligt id: Anton (a123), 220 sp. Anna (a999), 131 sp. Maja (m321), 210 sp.
Enligt poäng: Anna (a999), 131 sp. Maja (m321), 210 sp. Anton (a123), 220 sp.
Som du kan se ovan fungerar sortering efter olika kriterier precis som det är tänkt. Om funktionerna enligt_id
och enligt_studiepoang
inte behövs någon annanstans finns det sätt att göra implementeringen enklare. Vi återkommer till detta ämne efter dessa övningar.
Lambda-uttryck
Vi har mest arbetat med funktioner ur modularitetssynpunkt. Det är sant att funktioner spelar en viktig roll när det gäller att hantera komplexiteten i dina program och undvika upprepning av kod. Funktioner skrivs vanligtvis så att de kan användas många gånger.
Men ibland behöver man något som liknar en funktion som man bara använder en gång. Med lambda-uttryck kan du skapa små, anonyma funktioner som skapas (och kasseras) när de behövs i koden. Den allmänna syntaxen är som följer:
lambda <parametrar> : <uttryck>
Att sortera en lista med tupler efter det andra objektet i varje tupel skulle se ut så här implementerat med ett lambda-uttryck:
produkter = [("banan", 5.95), ("äppel", 3.95), ("apelsin", 4.50), ("vattenmelon", 4.95)]
# Funktionen skapas "i farten" med ett lambda-uttryck:
produkter.sort(key=lambda foremal: foremal[1])
for produkt in produkter:
print(produkt)
('äppel', 3.95) ('apelsin', 4.5) ('vattenmelon', 4.95) ('banan', 5.95)
Uttrycket
lambda föremål: föremål[1]
Är ekvivalent med funktionsdefinitionen
def pris(foremal):
return foremal[1]
förutom det faktum att en lambdafunktion inte har något namn. Det är därför lambda-funktioner kallas anonyma funktioner.
I alla andra avseenden skiljer sig inte en lambda-funktion från någon annan funktion, och de kan användas i alla samma sammanhang som en motsvarande namngiven funktion. Följande program sorterar till exempel en lista med strängar i alfabetisk ordning enligt det sista tecknet i varje sträng:
strangar = ["Mikael", "Makke", "Maja", "Markus", "Minna"]
for strang in sorted(strangar, key=lambda strang: strang[-1]):
print(strang)
Maja Minna Makke Mikael Markus
Vi kan också kombinera list comprehensions, join
-metoden och lambda-uttryck. Vi kan till exempel sortera strängar baserat på enbart vokalerna i dem och ignorera alla andra tecken:
strangar = ["Mikael", "Makke", "Maja", "Markus", "Minna"]
for strang in sorted(strangar, key=lambda strang: "".join([m for m in strang if m in "aeiouyäö"])):
print(strang)
Maja Makke Markus Minna Mikael
Anonyma funktioner kan också användas med andra inbyggda Python-funktioner, inte bara de som används för sortering. Till exempel tar funktionerna min
och max
också ett nyckelordsargument som heter key
. Det används som kriterium för att jämföra objekten när det lägsta eller högsta värdet väljs.
I följande exempel handlar det om ljudinspelningar. Först väljer vi den äldsta inspelningen och sedan den längsta:
class Skiva:
""" Klassen modellerar en enkel skiva """
def __init__(self, namn: str, artist: str, ar: int, langd: int):
self.namn = namn
self.artist = artist
self.ar = ar
self.langd = langd
def __str__(self):
return f"{self.namn} ({self.artist}), {self.ar}. {self.langd} min."
if __name__ == "__main__":
l1 = Skiva("Nevermind", "Nirvana", 1991, 43)
l2 = Skiva("Let It Be", "Beatles", 1969, 35)
l3 = Skiva("Joshua Tree", "U2", 1986, 50)
skivor = [l1, l2, l3]
print("Äldsta skiva:")
print(min(skivor, key=lambda skiva: skiva.ar))
print("Längsta skiva: ")
print(max(skivor, key=lambda skiva: skiva.langd))
Äldsta skiva: Let It Be (Beatles), 1969. 35 min. Längsta skiva: U2 (Joshua Tree), 1986. 50 min.
Funktioner som argument inom egna funktioner
Vi konstaterade ovan att det är möjligt att skicka en referens till en funktion som argument till en annan funktion. Som avslutning på detta avsnitt skriver vi en egen funktion som tar en funktion som argument.
# typledtråden callable refererar till en funktion
def utfor_operation(operation: callable):
# Anropa funktionen som passerades som argument
return operation(10, 5)
def summa(a: int, b: int):
return a + b
def produkt(a: int, b: int):
return a * b
if __name__ == "__main__":
print(utfor_operation(summa))
print(utfor_operation(produkt))
print(utfor_operation(lambda x,y: x - y))
15 50 5
Det värde som returneras av funktionen utfor_operation
beror på vilken funktion som skickades som argument. Vilken funktion som helst som tar emot två argument skulle duga, oavsett om den är anonym eller namngiven.
Att skicka referenser till funktioner som argument till andra funktioner är kanske inte något som du kommer att göra dagligen under din programmeringskarriär, men det kan vara en användbar teknik. Följande program väljer ut några rader från en fil och skriver dem till en annan fil. Hur raderna väljs ut bestäms av en funktion som returnerar True endast om raderna ska kopieras:
def kopiera_rader(kalla_namn: str, mal_namn: str, kriterie= lambda x: True):
with open(kalla_namn) as kalla, open(mal_namn, "w") as mal:
for rad in kalla:
# Ta bort all tomrum från början och slutet av raden
rad = rad.strip()
if kriterie(rad):
mal.write(rad + "\n")
# Exempel
if __name__ == "__main__":
# Ifall tredje parametern inte är angiven, kopiera alla rader
kopiera_rader("första.txt", "andra.txt")
# Kopiera alla icke-tomma rader
kopiera_rader("första.txt", "andra.txt", lambda rad: len(rad) > 0)
# Kopierar alla rader som innehåller ordet "Python"
kopiera_rader("första.txt", "andra.txt", lambda rad: "Python" in rad)
# Kopierar alla rader som inte slutar med en punkt
kopiera_rader("första.txt", "andra.txt", lambda rad: rad[-1] != ".")
Funktionsdefinitionen innehåller ett standardvärde för nyckelordsparametern kriterie
: lambda x: True
. Denna anonyma funktion returnerar alltid True
oavsett indata. Standardbeteendet är alltså att kopiera alla rader. Som vanligt gäller att om ett värde anges för en parameter med ett standardvärde, ersätter det nya värdet standardvärdet.
Se dina poäng genom att klicka på cirkeln nere till höger av sidan.