Använda funktionen super() i Python-klasser

Viktiga insikter

  • Pythons `super()`-funktion möjliggör anrop av metoder från överordnade klasser inifrån en underklass, vilket underlättar arv och åsidosättning av metoder.
  • Funktionen `super()` är nära sammankopplad med metodupplösningsordningen (MRO) i Python, vilken definierar i vilken ordning förfäderklasser genomsöks efter metoder eller attribut.
  • Användning av `super()` i klasskonstruktorer är kutym för att initialisera vanliga attribut i den överordnade klassen och mer specifika attribut i den underordnade klassen. Underlåtenhet att använda `super()` kan orsaka oväntade resultat, som att attribut inte initialiseras.

En grundläggande egenskap i Python är dess objektorienterade programmeringsparadigm (OOP), vilket gör det möjligt att modellera verkliga objekt och deras relationer.

I samband med Python-klasser används ofta arv och åsidosättning av attribut eller metoder från överordnade klasser. Python tillhandahåller funktionen `super()` för att möjliggöra anrop av överordnade klassers metoder från underklasser.

Vad är `super()` och varför är den nödvändig?

Genom arv kan man skapa en ny Python-klass som ärver funktionaliteten från en existerande klass. Det är också möjligt att åsidosätta metoder från överordnade klasser i underklasser, vilket ger alternativa implementationer. Dock kan man vilja använda den nya funktionen i kombination med den gamla istället för att ersätta den. Det är här `super()` kommer in i bilden.

Funktionen `super()` ger tillgång till överordnade klassers attribut och gör det möjligt att anropa deras metoder. `super()` är central inom objektorienterad programmering, eftersom den förenklar implementeringen av arv och åsidosättning av metoder.

Hur fungerar `super()`?

Internt är `super()` nära relaterat till metodupplösningsordningen (MRO) i Python, vilken bestäms av C3-lineariseringsalgoritmen.

Så här fungerar `super()`:

  • Bestäm den aktuella klassen och instansen: När `super()` anropas i en underklassmetod beräknar Python automatiskt den aktuella klassen (den klass som innehåller metoden som anropade `super()`) och instansen av den klassen (dvs. `self`).
  • Bestäm superklassen: `super()` tar två argument – den aktuella klassen och instansen – som man inte behöver specificera explicit. Den använder informationen för att bestämma superklassen metoden ska delegeras till. Detta görs genom att undersöka klasshierarkin och MRO.
  • Anropa metoden i superklassen: När superklassen är bestämd, gör `super()` det möjligt att anropa dess metoder på samma sätt som om de anropades direkt från underklassen. Detta gör att man kan utöka eller åsidosätta metoder, samtidigt som den ursprungliga implementeringen från superklassen används.

Använda `super()` i en klasskonstruktor

Det är vanligt att använda `super()` i en klasskonstruktor eftersom man ofta vill initialisera vanliga attribut i den överordnade klassen och mer specifika i den underordnade.

För att illustrera detta kan man definiera en Python-klass, `Far`, som en klass, `Son`, ärver ifrån:

    class Far:
      def __init__(self, fornamn, efternamn):
        self.fornamn = fornamn
        self.efternamn = efternamn
    
    class Son(Far):
      def __init__(self, fornamn, efternamn, alder, hobby):
          
        super().__init__(fornamn, efternamn)
      
        self.alder = alder
        self.hobby = hobby

      def get_info(self):
        return f"Sonens namn: {self.fornamn} {self.efternamn}, \
        Sonens ålder: {self.alder}, Sonens hobby: {self.hobby}"

    son = Son("Pius", "Effiong", 25, "Spelar gitarr")

    print(son.get_info())
  

I konstruktorn för `Son` anropar `super().__init__()` klasskonstruktorn för `Far` och skickar fornamn och efternamn som parametrar. Detta säkerställer att klassen `Far` fortfarande kan ställa in namnatributen korrekt, även på ett `Son`-objekt.

Om man inte anropar `super()` i en klasskonstruktor, kommer konstruktorn för den överordnade klassen inte att köras. Detta kan leda till oväntade problem, såsom att attribut inte initialiseras eller att den överordnade klassens tillstånd inte ställs in korrekt:

    ...
    class Son(Far):
      def __init__(self, fornamn, efternamn, alder, hobby):
        self.alder = alder
        self.hobby = hobby
    ...
  

Om man nu försöker anropa `get_info`-metoden kommer ett `AttributeError` att genereras eftersom attributen `self.fornamn` och `self.efternamn` inte har initialiserats.

Använda `super()` i klassmetoder

Man kan använda `super()` i andra metoder än konstruktorer på samma sätt. Detta ger möjlighet att utöka eller åsidosätta beteendet hos en metod i den överordnade klassen.

    class Far:
      def prata(self):
        return "Hälsning från Far"

    class Son(Far):
      def prata(self):

        foralderns_halsning = super().prata()
        return f"Hälsning från Son\n{foralderns_halsning}"

    son = Son()

    son_halsning = son.prata()

    print(son_halsning)
  

Klassen `Son` ärver från `Far` och har sin egen metod `prata`. Metoden `prata` för klassen `Son` använder `super().prata()` för att anropa metoden `prata` för klassen `Far`. På så sätt kan man inkludera meddelandet från den överordnade klassen samtidigt som det utökas med ett meddelande som är specifikt för underklassen.

Att inte använda `super()` i en metod som åsidosätter en annan metod leder till att funktionaliteten i den överordnade klassens metod inte träder i kraft. Istället ersätts metodens beteende fullständigt, vilket kan leda till oavsiktliga effekter.

Förstå metodupplösningsordningen

Metodupplösningsordningen (MRO) är den ordning i vilken Python söker igenom förfäderklasser när man anropar en metod eller ett attribut. MRO hjälper Python att avgöra vilken metod som ska anropas om det finns flera arvshierarkier.

    class Nigeria():
      def kultur(self):
        print("Nigerias kultur")

    class Afrika():
      def kultur(self):
        print("Afrikas kultur")
  

Här är vad som sker när man skapar en instans av klassen `Lagos` och anropar metoden `kultur`:

  • Python börjar med att söka efter metoden `kultur` i själva klassen `Lagos`. Om metoden hittas anropas den. Om inte fortsätter man till steg två.
  • Om metoden `kultur` inte hittas i klassen `Lagos`, undersöker Python basklasserna i den ordning de anges i klassdefinitionen. I det här fallet ärver `Lagos` först från `Afrika` och sedan från `Nigeria`. Python letar alltså efter metoden `kultur` i klassen `Afrika` först.
  • Om metoden `kultur` inte hittas i klassen `Afrika` fortsätter Python sökningen i klassen `Nigeria`. Detta beteende fortsätter tills man når slutet av hierarkin och ett fel genereras om metoden inte hittas i någon av superklasserna.

Utdata visar `Lagos` metodupplösningsordning, från vänster till höger.

Vanliga fallgropar och bästa praxis

När man arbetar med `super()` finns det några vanliga fallgropar man bör undvika.

  • Var uppmärksam på metodupplösningsordningen, särskilt vid scenarion med multipelt arv. Om komplext multipelt arv används bör man vara bekant med C3-lineariseringsalgoritmen som Python använder för att bestämma MRO.
  • Undvik cirkulära beroenden i klasshierarkin, vilket kan leda till oförutsägbart beteende.
  • Dokumentera koden tydligt, särskilt när `super()` används i komplexa klasshierarkier, för att göra koden lättare att förstå för andra utvecklare.

Använd `super()` på rätt sätt

Pythons `super()`-funktion är en kraftfull funktion när man arbetar med arv och åsidosättning av metoder. Genom att förstå hur `super()` fungerar och följa bästa praxis kan man skapa mer underhållbar och effektiv kod i Python-projekt.