Master Pythons magiske metoder: Skriv renere & mer intuitiv kode!

En av Pythons mindre kjente, men likevel essensielle egenskaper, er evnen til å implementere såkalte «magiske metoder» i objekter. Ved å benytte oss av disse magiske metodene, kan vi konstruere kode som ikke bare er renere, men også mer intuitiv og lettere å forstå.

Med magiske metoder får vi muligheten til å skape grensesnitt for interaksjon med objekter på en måte som føles mer naturlig i Python. Denne artikkelen tar sikte på å gi deg en grundig innføring i magiske metoder, diskutere beste praksis for utvikling av dem, og utforske de vanligste magiske metodene du vil støte på.

Hva er egentlig magiske metoder?

Magiske metoder i Python er spesielle funksjoner som bestemmer hvordan Python-objekter oppfører seg når standardoperasjoner blir utført på dem. Disse metodene er lett gjenkjennelige ved deres distinkte navnekonvensjon: de er omsluttet av doble understrekinger før og etter selve metodenavnet.

Som et resultat av dette, blir de ofte referert til som «dunder-metoder», en referanse til det engelske ordet «double underscore». En velkjent dunder-metode som du trolig allerede har brukt, er `__init__()`, som benyttes til å definere konstruktører i klasser.

Det er viktig å merke seg at dunder-metoder vanligvis ikke kalles direkte i koden din. Istedenfor blir de automatisk aktivert av Python-tolkeren under kjøringen av programmet.

Hvorfor er magiske metoder så nyttige?

Magiske metoder utgjør et viktig konsept innenfor objektorientert programmering i Python. Ved å bruke dem, kan du detaljert definere hvordan dine egendefinerte datatyper oppfører seg i forbindelse med vanlige, innebygde operasjoner. Disse operasjonene omfatter:

Aritmetiske operasjoner

Sammenligningsoperasjoner

Livssyklusoperasjoner

Representasjonsoperasjoner

Den kommende delen vil fordype seg i hvordan man implementerer magiske metoder for å definere oppførselen til en applikasjon ved bruk av de ovennevnte kategoriene.

Hvordan definerer man magiske metoder?

Som tidligere nevnt, bestemmer magiske metoder oppførselen til objekter. Derfor defineres de som en integrert del av objektets klasse. Ettersom de er en del av klassen, aksepterer de `self` som det første argumentet, som er en referanse til selve objektet.

Avhengig av hvordan de vil bli kalt av tolkeren, kan de også akseptere flere argumenter. I likhet med navnet, er de lett gjenkjennelige ved deres doble understrekinger før og etter navnet.

Praktisk gjennomføring

Mye av det vi har diskutert til nå kan virke teoretisk og abstrakt. I denne seksjonen skal vi gi et konkret eksempel ved å implementere en enkel rektangelklasse.

Denne klassen vil inneholde attributtene for lengde og bredde. Med bruk av `__init__`-metoden, kan du spesifisere disse egenskapene når du oppretter en instans av klassen. I tillegg vil det være mulig å sammenligne rektangler for å se om de er like, mindre eller større enn hverandre ved å bruke operatorene ==, < og >. Til slutt, skal rektangelet kunne gi en forståelig strengrepresentasjon av seg selv.

Klargjøring av kodeutviklingsmiljø

For å kunne følge dette eksemplet, trenger du et Python-kjøringsmiljø. Du kan benytte et lokalt miljø, eller bruke en nettbasert Python-kompilator som f.eks. tipsbilk.net.

Opprettelse av rektangelklassen

La oss starte med å definere selve rektangelklassen.

class Rectangle:
    pass

Opprettelse av konstruktørmetoden

La oss nå lage vår første magiske metode, klassekonstruktørmetoden. Denne metoden vil ta inn høyde og bredde som argumenter, og lagre dem som attributter for klasseinstansen.

class Rectangle:
    def __init__(self, height, width):
        self.height = height
        self.width = width

Opprettelse av en magisk metode for strengrepresentasjon

Deretter skal vi implementere en metode som gir klassen vår evnen til å generere en lesbar streng for å representere objektet. Denne metoden kalles automatisk når vi bruker `str()`-funksjonen og sender inn en instans av `Rectangle`-klassen som argument. Den vil også bli aktivert når du bruker funksjoner som forventer en streng, som f.eks. `print()`-funksjonen.

class Rectangle:
    def __init__(self, height, width):
        self.height = height
        self.width = width

    def __str__(self):
        return f'Rectangle({self.height}, {self.width})'

Metoden `__str__()` skal returnere en streng som representerer objektet. I vårt tilfelle, returnerer vi en streng på formatet `Rectangle(, )`, der høyde og bredde er de lagrede dimensjonene til rektangelet.

Opprettelse av magiske metoder for sammenligningsoperasjoner

Nå skal vi implementere sammenligningsoperatorene for lik, mindre enn og større enn operasjoner. Dette kalles for overbelastning av operatorer. For å gjøre dette, bruker vi de magiske metodene `__eq__`, `__lt__` og `__gt__`. Disse metodene vil returnere en boolsk verdi etter å ha sammenlignet arealene til rektanglene.

class Rectangle:
    def __init__(self, height, width):
        self.height = height
        self.width = width

    def __str__(self):
        return f'Rectangle({self.height}, {self.width})'

    def __eq__(self, other):
        """ Sjekker om rektanglene er like """
        return self.height * self.width == other.height * other.width

    def __lt__(self, other):
        """ Sjekker om rektangelet er mindre enn det andre """
        return self.height * self.width < other.height * other.width

    def __gt__(self, other):
        """ Sjekker om rektangelet er større enn det andre """
        return self.height * self.width > other.height * other.width

Som du ser, aksepterer disse metodene to parametere. Den første er det aktuelle rektangelet, og den andre er verdien det sammenlignes med. Denne verdien kan være en annen rektangelinstans eller en helt annen verdi. Logikken for hvordan sammenligningen gjøres og betingelsene for returverdien er helt opp til deg.

Vanlige magiske metoder

I denne seksjonen skal vi se nærmere på de vanligste magiske metodene du vil møte og bruke.

#1. Aritmetiske operasjoner

Aritmetiske magiske metoder kalles når en instans av klassen din plasseres på venstre side av et aritmetisk tegn. Metoden kalles med to argumenter: det første er en referanse til instansen, og det andre er objektet til høyre for tegnet. Metodene og deres tilhørende tegn er som følger:

Navn Metode Tegn Beskrivelse
Addisjon `__add__` + Implementerer addisjon.
Subtraksjon `__sub__` Implementerer subtraksjon.
Multiplikasjon `__mul__` * Implementerer multiplikasjon.
Divisjon `__div__` / Implementerer divisjon.
Gulvdivisjon `__floordiv__` // Implementerer gulvdivisjon.

#2. Sammenligningsoperasjoner

På samme måte som med de aritmetiske magiske metodene, kalles disse metodene når en instans av klassen de er definert for, plasseres til venstre for en sammenligningsoperator. Akkurat som med de aritmetiske metodene, kalles de med to parametere: den første er en referanse til objektinstansen, og den andre er en referanse til verdien på høyre side av tegnet.

Navn Metode Tegn Beskrivelse
Mindre enn `__lt__` < Implementerer mindre enn sammenligning
Større enn `__gt__` > Implementerer større enn sammenligning
Lik `__eq__` == Implementerer likhetssammenligning
Mindre enn eller lik `__le__` <= Implementerer mindre enn eller lik sammenligning
Større enn eller lik `__ge__` >= Implementerer større enn eller lik sammenligning

#3. Livssyklusoperasjoner

Disse metodene aktiveres som respons på de ulike livssyklushendelsene til et objekt, for eksempel når det opprettes eller slettes. Konstruktøren `__init__` er et eksempel på en slik metode. De mest brukte metodene i denne kategorien er listet opp i tabellen under:

Navn Metode Beskrivelse
Konstruktør `__init__` Denne metoden kalles når et objekt av klassen det tilhører opprettes.
Sletting `__del__` Denne metoden kalles når et objekt av klassen det tilhører slettes. Den kan brukes til å rydde opp, f.eks. stenge filer den hadde åpnet.
Nytt `__new__` `__new__`-metoden kalles aller først når et objekt av en spesifisert klasse blir instansiert. Denne metoden kalles før konstruktøren og aksepterer klassen samt eventuelle tilleggsargumenter. Den returnerer en instans av klassen. Selv om den ikke er så ofte brukt, er den omtalt her for fullstendighetens skyld.

#4. Representasjonsoperasjoner

Navn Metode Beskrivelse
String `__str__` Returnerer en lesbar strengrepresentasjon av objektet. Denne metoden kalles når du bruker `str()`-funksjonen og sender inn en instans av klassen som argument. Den kalles også når du sender instansen til funksjoner som `print()` og `format()`. Hensikten er å generere en streng som er lett forståelig for sluttbrukeren av applikasjonen.
Repr `__repr__` Returnerer en strengrepresentasjon av objektet som er ment for bruk av utviklere. Ideelt sett bør strengen inneholde nok informasjon til at man kan konstruere en identisk instans av objektet bare basert på strengen.

Beste praksis for å lage magiske metoder

Magiske metoder er kraftige verktøy som kan forenkle koden din. Det er imidlertid viktig å ha følgende retningslinjer i bakhodet når du bruker dem:

  • Bruk dem med måte: Implementering av for mange magiske metoder i klassene dine kan gjøre koden vanskeligere å forstå. Prøv å begrense deg til å implementere de mest nødvendige.
  • Vær oppmerksom på ytelseskonsekvensene: Forsikre deg om at du er klar over ytelsesimplikasjonene ved bruk av metoder som `__setattr__` og `__getattr__` før du implementerer dem.
  • Dokumenter metodene dine: God dokumentasjon av dine magiske metoder er viktig for at andre utviklere skal forstå hva de gjør. Dette gjør det enklere for dem å bruke metodene dine og feilsøke eventuelle problemer.

Avsluttende ord

I denne artikkelen har vi presentert magiske metoder som et middel for å lage klasser som kan samhandle med innebygde operasjoner. Vi diskuterte også hvordan de defineres og gjennomgikk et eksempel på en klasse som implementerer magiske metoder. Vi har sett på de forskjellige metodene du mest sannsynlig vil støte på, og vi har delt noen retningslinjer for god praksis.

Etter dette, kan det være interessant å lære mer om implementeringen av `Counter`-klassen i Python.