Podcast Praktisk Python – Din Lydguide til Python Programmering 5 - itnett/FTD02H-N GitHub Wiki

Kapittel 15: Klasser og objekter 🧱👾

I forrige kapittel lærte vi om klasser og funksjoner i Python, men nå skal vi gå dypere inn i hvordan objekter fungerer i Python, samt utforske avanserte konsepter som arv, komposisjon og polymorfisme. Dette vil gi oss en dypere forståelse av objektorientert programmering (OOP).


1. Hva er et objekt? 🧑‍💻

Et objekt er en instans av en klasse. Når vi lager et objekt, tar vi blåkopien (klassen) og gjør den til et faktisk element i programmet vårt som vi kan interagere med. Alle objekter har:

  • Attributter: Dataene som tilhører objektet (f.eks. bilens merke og modell).
  • Metoder: Funksjonene som kan utføres av objektet (f.eks. kjøre eller stoppe bilen).

🎙️ Eksempel:

class Person:
    def __init__(self, navn, alder):
        self.navn = navn
        self.alder = alder

    def si_hei(self):
        print(f"Hei! Jeg heter {self.navn} og er {self.alder} år gammel.")

# Opprett et objekt av Person-klassen
person1 = Person("Ole", 25)
person2 = Person("Lise", 30)

# Bruk metode på objektene
person1.si_hei()
person2.si_hei()

Forklaring:

  • Person er klassen vår, og person1 og person2 er objektene.
  • Objektene har forskjellige verdier for sine attributter (navn og alder), men de deler samme metode si_hei().

2. Arv (Inheritance) 🏗️

Arv lar oss opprette nye klasser basert på eksisterende klasser. Dette lar oss gjenbruke kode uten å måtte skrive den på nytt, noe som gjør koden vår mer organisert og effektiv.

🎙️ Eksempel:

# Superklasse (forelder)
class Dyr:
    def __init__(self, navn):
        self.navn = navn

    def lyd(self):
        pass

# Subklasse (barn)
class Hund(Dyr):
    def lyd(self):
        return f"{self.navn} sier voff!"

# Subklasse (barn)
class Katt(Dyr):
    def lyd(self):
        return f"{self.navn} sier mjau!"

# Opprett objekter av subklassene
hund = Hund("Rex")
katt = Katt("Luna")

# Kalle metoden lyd() på objektene
print(hund.lyd())
print(katt.lyd())

Forklaring:

  • Dyr er en superklasse (forelderen), og Hund og Katt er subklasser som arver fra superklassen.
  • Subklassene overstyrer metoden lyd(), slik at de har sin egen implementasjon.

3. Komposisjon 🧩

Mens arv er en måte å gjenbruke kode på, er komposisjon en annen teknikk. I stedet for å arve funksjonalitet fra en superklasse, kan vi bygge objekter som består av andre objekter.

🎙️ Eksempel:

class Motor:
    def __init__(self, hestekrefter):
        self.hestekrefter = hestekrefter

    def start(self):
        print("Motoren starter.")

class Bil:
    def __init__(self, merke, motor):
        self.merke = merke
        self.motor = motor

    def kjør(self):
        self.motor.start()
        print(f"{self.merke} kjører!")

# Opprette et Motor-objekt
motoren = Motor(150)

# Opprette et Bil-objekt med et Motor-objekt
bil = Bil("Toyota", motoren)

# Kalle metoder på Bil-objektet
bil.kjør()

Forklaring:

  • Her har vi laget en Motor-klasse og brukt den som en komponent i Bil-klassen.
  • Når vi kaller bil.kjør(), starter motoren før bilen kjører. Dette er komposisjon – vi bygger en klasse ved å inkludere andre objekter som deler av den.

4. Polymorfisme 🔀

Polymorfisme betyr at forskjellige objekter kan bruke samme metode, men med ulik oppførsel. Dette er en viktig egenskap i OOP fordi det gir fleksibilitet og utvidbarhet i koden vår.

🎙️ Eksempel:

class Fugl:
    def lag_lyd(self):
        return "Fugl sier kvitt-kvitt."

class Hund:
    def lag_lyd(self):
        return "Hund sier voff!"

# En funksjon som kan ta hvilket som helst dyr og kalle lag_lyd()
def dyrelyder(dyr):
    print(dyr.lag_lyd())

# Opprette objekter
fugl = Fugl()
hund = Hund()

# Kalle funksjonen med forskjellige objekter
dyrelyder(fugl)
dyrelyder(hund)

Forklaring:

  • Både Fugl og Hund har metoden lag_lyd(), men de implementerer den på forskjellige måter.
  • Funksjonen dyrelyder() kan kalles med forskjellige objekter, og Python vil kalle den riktige implementasjonen av lag_lyd() basert på objektet som blir sendt inn.

5. Enkel objekthåndtering ⚙️

En av de store fordelene med objektorientert programmering er evnen til å håndtere kompleksitet ved å dele opp programmet i små, gjenbrukbare enheter (klasser og objekter). La oss se et eksempel der vi kombinerer flere klasser for å simulere en liten bilbutikk.

🎙️ Eksempel:

class Kunde:
    def __init__(self, navn, bilmodell):
        self.navn = navn
        self.bilmodell = bilmodell

class Bil:
    def __init__(self, merke, modell):
        self.merke = merke
        self.modell = modell

class Butikk:
    def __init__(self):
        self.kunder = []

    def legg_til_kunde(self, kunde):
        self.kunder.append(kunde)

    def vis_kunder(self):
        for kunde in self.kunder:
            print(f"{kunde.navn} har kjøpt en {kunde.bilmodell.merke} {kunde.bilmodell.modell}")

# Opprette biler
bil1 = Bil("Tesla", "Model S")
bil2 = Bil("BMW", "X5")

# Opprette kunder
kunde1 = Kunde("Ole", bil1)
kunde2 = Kunde("Lise", bil2)

# Opprette butikk og legge til kunder
butikken = Butikk()
butikken.legg_til_kunde(kunde1)
butikken.legg_til_kunde(kunde2)

# Vise kundene og bilene deres
butikken.vis_kunder()

Forklaring:

  • Vi har en Butikk-klasse som holder styr på kunder, en Kunde-klasse for kundene og en Bil-klasse for bilene de kjøper.
  • Butikk har en metode legg_til_kunde() som lagrer hver kunde i en liste, og metoden vis_kunder() lister opp alle kundene og bilene deres.

6. Oppsummering av kapittel 15 📜

I dette kapittelet har vi lært om:

  • Hva et objekt er, og hvordan det opprettes fra en klasse.
  • Arv som lar oss opprette nye klasser fra eksisterende klasser, slik at vi kan gjenbruke kode.
  • Komposisjon, som innebærer å bygge klasser ved å sette sammen andre objekter.
  • Polymorfisme, som gjør det mulig for forskjellige klasser å bruke samme metode på forskjellige måter.

Dette gir oss et godt fundament for å bygge komplekse og robuste systemer i Python. I neste kapittel går vi videre til å jobbe med filer og databaser, som vil utvide vår evne til å lagre og hente data fra eksterne kilder.

🎉 Fortsett å eksperimentere med klassene og objektene dine, og prøv å kombinere dem på nye måter for å løse problemer!

Kapittel 16: Arv og gjenbruk av kode 🧬⚙️

I forrige kapittel så vi hvordan objekter fungerer i Python og lærte om konsepter som arv, komposisjon og polymorfisme. Nå skal vi dykke dypere inn i arv, og utforske hvordan dette kraftige verktøyet lar oss gjenbruke kode på en effektiv måte.


1. Hva er arv i Python? 🏗️

Arv er en prosess hvor en klasse (subklasse) kan arve attributter og metoder fra en annen klasse (superklasse). Dette lar oss opprette nye klasser basert på eksisterende klasser, uten å måtte skrive om koden. I arv kan vi også overstyre metoder for å gi spesifikke egenskaper til subklassen.

🎙️ Eksempel:

# Superklasse (Forelderklasse)
class Kjøretøy:
    def __init__(self, merke, modell):
        self.merke = merke
        self.modell = modell

    def start(self):
        print(f"{self.merke} {self.modell} starter.")

# Subklasse (Barnklasse)
class Bil(Kjøretøy):
    def kjør(self):
        print(f"{self.merke} {self.modell} kjører.")

# Subklasse (Barnklasse)
class Motorsykkel(Kjøretøy):
    def kjør(self):
        print(f"{self.merke} {self.modell} kjører på to hjul.")

# Opprette objekter av subklassene
bil = Bil("Tesla", "Model S")
motorsykkel = Motorsykkel("Yamaha", "R1")

# Bruke metoder fra både superklasse og subklasser
bil.start()
bil.kjør()
motorsykkel.start()
motorsykkel.kjør()

Forklaring:

  • Klassen Kjøretøy er superklassen som inneholder felles funksjonalitet (som start()).
  • Klassene Bil og Motorsykkel er subklasser som arver fra Kjøretøy, men de har også sine egne metoder som gjør dem unike (som kjør()).

2. Overstyring av metoder 🔄

Noen ganger ønsker vi at subklassen skal bruke en annen implementasjon av en metode enn den som finnes i superklassen. Dette kalles overstyring av metoder. Subklassen kan da tilpasse metoden etter sitt eget behov.

🎙️ Eksempel:

class Dyr:
    def lyd(self):
        return "Dyr lager en lyd."

class Hund(Dyr):
    def lyd(self):
        return "Hund sier voff!"

class Katt(Dyr):
    def lyd(self):
        return "Katt sier mjau!"

# Opprette objekter
hund = Hund()
katt = Katt()

# Kalle metoden lyd() på objektene
print(hund.lyd())
print(katt.lyd())

Forklaring:

  • Selv om både Hund og Katt arver fra Dyr, overstyrer de metoden lyd() slik at den gir forskjellige resultater avhengig av hvilket dyr vi jobber med.

3. Bruke super() for å utvide superklassemetoder 💡

Noen ganger ønsker vi å bruke superklassens metode i tillegg til å legge til funksjonalitet i subklassen. Dette kan gjøres ved hjelp av funksjonen super().

🎙️ Eksempel:

class Ansatt:
    def __init__(self, navn, lønn):
        self.navn = navn
        self.lønn = lønn

    def informasjon(self):
        print(f"Ansatt: {self.navn}, Lønn: {self.lønn}")

class Leder(Ansatt):
    def __init__(self, navn, lønn, team):
        super().__init__(navn, lønn)  # Kall til superklassens init-metode
        self.team = team

    def informasjon(self):
        super().informasjon()  # Kall superklassens informasjon-metode
        print(f"Teamleder for: {self.team}")

# Opprette objekt av Leder-klassen
leder = Leder("Kari", 75000, "Utvikling")

# Kalle metoden informasjon()
leder.informasjon()

Forklaring:

  • Ved hjelp av super() kan vi kalle metoden fra superklassen og deretter legge til mer spesifikk funksjonalitet i subklassen.
  • Leder arver fra Ansatt, men legger til et nytt attributt (team) og utvider metoden informasjon() med flere detaljer.

4. Flervalg av arv (Multiple Inheritance) 🌐

Python støtter flervalg av arv, noe som betyr at en klasse kan arve fra mer enn én superklasse. Dette kan være nyttig når vi ønsker at en klasse skal kombinere egenskapene til flere klasser.

🎙️ Eksempel:

class Flygende:
    def fly(self):
        print("Kan fly!")

class Svømmende:
    def svøm(self):
        print("Kan svømme!")

class And(Flygende, Svømmende):
    pass

# Opprette et objekt av And-klassen
and = And()

# Kalle metoder fra begge superklassene
and.fly()
and.svøm()

Forklaring:

  • Klassen And arver fra både Flygende og Svømmende, slik at den kan utføre begge funksjonene (fly og svømme).
  • Flervalg av arv kan noen ganger føre til kompleksitet, spesielt hvis superklassene har metoder med samme navn.

5. Komposisjon kontra arv 🧩

En viktig avveining i objektorientert design er valget mellom arv og komposisjon. Som vi så i forrige kapittel, innebærer komposisjon å bruke objekter inni andre objekter i stedet for å arve fra dem. Dette gir ofte større fleksibilitet.

🎙️ Eksempel:

I stedet for å la en Bil-klasse arve fra en Motor-klasse, kan vi bruke komposisjon ved å la bilen "ha" en motor.

class Motor:
    def start(self):
        print("Motoren starter.")

class Bil:
    def __init__(self, merke):
        self.merke = merke
        self.motor = Motor()  # Komposisjon: Bil har en motor

    def kjør(self):
        self.motor.start()
        print(f"{self.merke} kjører!")

# Opprette Bil-objekt og kjøre
bil = Bil("Tesla")
bil.kjør()

Forklaring:

  • I dette tilfellet er Bil og Motor uavhengige klasser, men Bil "har" en motor. Dette er en bedre løsning når Bil ikke direkte "er" en motor, men snarere bruker en motor.

6. Polymorfisme og arv 🔀

Polymorfisme fungerer spesielt godt med arv. Når en subklasse overstyrer en metode fra superklassen, kan vi bruke polymorfisme for å behandle objekter fra forskjellige subklasser på samme måte, men med ulike resultater.

🎙️ Eksempel:

class Fugl:
    def lyd(self):
        return "Kvitt kvitt."

class Hund:
    def lyd(self):
        return "Voff voff."

def lag_dyrelyd(dyr):
    print(dyr.lyd())

# Opprette objekter
fugl = Fugl()
hund = Hund()

# Kalle funksjonen med forskjellige objekter
lag_dyrelyd(fugl)
lag_dyrelyd(hund)

Forklaring:

  • Funksjonen lag_dyrelyd() tar et dyr som parameter, og takket være polymorfisme kan den kalle den riktige metoden for både fugl og hund, selv om metodene har samme navn.

7. Eksempler på bruk av arv og komposisjon i virkelige prosjekter 🌍

1. Spillsystemer:

  • Arv brukes ofte i spillsystemer for å opprette hierarkier av spillobjekter. For eksempel kan vi ha en superklasse Spillobjekt, og subklasser som Spiller, Fiende, og Gjenstander.

2. UI-komponenter:

  • I utvikling av grafiske brukergrensesnitt (GUI) kan vi bruke arv til å lage nye komponenter som arver fra eksisterende komponenter, som knapper, tekstfelt og vinduer.

3. Databaser:

  • I objektorienterte databasesystemer kan arv brukes til å representere relasjoner mellom forskjellige typer data.

8. Oppsummering av kapittel 16 📜

I dette kapittelet har vi utforsket:

  • Arv som lar oss gjenbruke kode ved å opprette subklasser fra superklasser.
  • Overstyring av metoder for å gi subklasser sine egne versjoner

av metoder fra superklassen.

  • Hvordan vi kan bruke super() for å utvide funksjonaliteten til superklassen i subklassen.
  • Flervalg av arv, som lar en klasse arve fra flere superklasser samtidig.
  • Komposisjon som et alternativ til arv, der vi setter sammen objekter for å lage komplekse strukturer.
  • Polymorfisme, som lar oss bruke samme metode på forskjellige objekter med forskjellige resultater.

Neste kapittel tar oss dypere inn i Python-ekstrautstyr, som vil dekke flere avanserte funksjoner og verktøy i språket.

🎉 Gratulerer med enda et steg i din Python-reise! Fortsett å eksperimentere med arv og komposisjon, og prøv å bygge ditt eget prosjekt som bruker begge disse konseptene.