Programmering nivå 2
Kap 3.8 – Generiska klasser och metoder
Skriv återanvändbar kod som kan fungera med flera olika datatyper utan att tappa tydlighet.
Mål med lektionen
När du har arbetat klart med denna lektion ska du:
- Förstå vad generisk kod betyder i Python.
- Kunna skriva enkla generiska funktioner och metoder med
TypeVar. - Kunna skapa en enkel generisk klass med
Generic. - Förstå när generiska klasser och metoder gör kod mer återanvändbar och testbar.
Så här lär du dig bäst
Börja med att jämföra två funktioner som gör samma sak för olika datatyper. Fundera sedan på vad som egentligen är gemensamt. Generiska typer är inte främst till för att göra koden svårare, utan för att beskriva tydligare vilka typer som hänger ihop.
Centrala begrepp
- Generisk kod: kod som kan användas med flera datatyper.
- Typvariabel: en symbolisk typ, ofta
T, som betyder "samma typ som används här". TypeVar: används för att skapa en typvariabel i Python.Generic: används när en klass ska vara generisk.- Typhint: en markering som hjälper läsaren och editorn förstå vilka typer koden arbetar med.
Varför behövs generiska typer?
Tänk att du vill skriva en funktion som hämtar första värdet ur en lista. Funktionen fungerar på samma sätt oavsett om listan innehåller tal, text eller objekt.
def första_text(värden: list[str]) -> str:
return värden[0]
def första_tal(värden: list[int]) -> int:
return värden[0]
Det här ger duplicerad kod. Logiken är identisk. Det enda som skiljer är typen. Med en generisk funktion kan vi beskriva samma idé en gång.
Generisk funktion
from typing import TypeVar
T = TypeVar("T")
def första(värden: list[T]) -> T:
return värden[0]
namn = första(["Ali", "Bo", "Cia"])
poäng = första([10, 20, 30])
print(namn)
print(poäng)
T betyder att funktionen ska returnera samma typ som listan innehåller. Om listan är
list[str] blir returvärdet str. Om listan är list[int] blir
returvärdet int.
Generisk metod
En metod kan också vara generisk. Här används samma metod för att kontrollera om ett värde finns i en lista, oavsett vilken typ listan innehåller.
from typing import TypeVar
T = TypeVar("T")
def finns_i(värde: T, värden: list[T]) -> bool:
return värde in värden
print(finns_i("Ali", ["Ali", "Bo"]))
print(finns_i(3, [1, 2, 3]))
Det viktiga är kopplingen mellan värde och värden: de ska höra ihop
typmässigt. Det gör koden lättare att förstå och bättre för editorstöd.
Generisk klass
En generisk klass passar när klassen ska kunna lagra eller hantera olika typer av objekt, men med samma logik. Här är en enkel låda som kan innehålla vad som helst.
from typing import Generic, TypeVar
T = TypeVar("T")
class Låda(Generic[T]):
def __init__(self, innehåll: T):
self.innehåll = innehåll
def hämta(self) -> T:
return self.innehåll
textlåda = Låda("Hej")
tallåda = Låda(42)
print(textlåda.hämta())
print(tallåda.hämta())
Låda[str] betyder en låda som innehåller text. Låda[int] betyder en låda
som innehåller heltal. Python tvingar normalt inte detta vid körning, men typhintarna hjälper
programmeraren, editorn och testningen.
Exempel: generiskt register
I tidigare kapitel har du arbetat med böcker, elever och recept. Ett register kan ofta ha samma grundlogik oavsett vilken typ av objekt som lagras.
from typing import Generic, TypeVar
T = TypeVar("T")
class Register(Generic[T]):
def __init__(self):
self._saker: list[T] = []
def lägg_till(self, sak: T) -> None:
self._saker.append(sak)
def alla(self) -> list[T]:
return self._saker
class Bok:
def __init__(self, titel: str):
self.titel = titel
bokregister = Register[Bok]()
bokregister.lägg_till(Bok("Python"))
första_boken = bokregister.alla()[0]
print(första_boken.titel)
Samma Register-klass kan användas för Bok, Elev,
Recept eller andra objekt. Det minskar duplicering och gör klassens ansvar tydligt:
den hanterar en samling objekt.
När ska du använda generiska klasser och metoder?
- När samma logik ska fungera för flera datatyper.
- När du vill undvika duplicerade funktioner som bara skiljer sig i typ.
- När du vill att editorn ska kunna ge bättre stöd och varna för fel typ.
- När du bygger egna samlingar, register, köer eller lagringsklasser.
När behövs det inte?
- När koden bara används på ett enda ställe.
- När en vanlig funktion eller klass är tydligare.
- När generiska typer gör lösningen svårare att läsa än problemet kräver.
Öva själv
- Skriv en generisk funktion
sista(värden)som returnerar sista värdet i en lista. - Skapa en generisk klass
Parsom lagrar två värden av samma typ. - Skapa en klass
Elevoch användRegister[Elev]för att lagra elever. - Fundera: när blir generisk kod tydligare än duplicerad kod?
Sammanfattning
- Generisk kod kan återanvändas med flera datatyper.
TypeVarbeskriver en typ som återkommer på flera platser.Generic[T]används när en klass ska fungera med valfri men sammanhängande typ.- Generiska klasser och metoder kan göra kod mer läsbar, testbar och återanvändbar.