Używanie funkcji super() w klasach Pythona

Photo of author

By maciekx

Najważniejsze informacje o `super()` w Pythonie

  • W języku Python funkcja `super()` umożliwia dostęp do metod z klasy nadrzędnej w ramach klasy potomnej. Jest to kluczowe dla efektywnego wykorzystania dziedziczenia i nadpisywania metod.
  • Działanie `super()` jest nierozerwalnie związane z kolejnością przeszukiwania klas (MRO), która określa sekwencję przeszukiwania klas bazowych w celu znalezienia odpowiednich metod i atrybutów.
  • Powszechną praktyką jest użycie `super()` w konstruktorach klas, by inicjalizować wspólne atrybuty w klasie bazowej, a następnie dodawać specyficzne dla klasy pochodnej. Pominięcie `super()` może prowadzić do nieoczekiwanych błędów, takich jak brak inicjalizacji niezbędnych atrybutów.

Paradygmat programowania obiektowego (OOP) jest fundamentalnym elementem Pythona, umożliwiającym reprezentowanie obiektów z rzeczywistości i ich wzajemnych zależności.

Podczas pracy z klasami w Pythonie, często wykorzystuje się dziedziczenie i modyfikację metod lub atrybutów z klas nadrzędnych. Funkcja `super()` w Pythonie umożliwia wygodne wywoływanie metod klasy bazowej z poziomu klasy dziedziczącej.

Czym jest `super()` i dlaczego jest niezbędne?

Dziedziczenie umożliwia tworzenie nowych klas na bazie istniejących, co sprzyja ponownemu wykorzystaniu kodu. Często zachodzi potrzeba zmiany sposobu działania metod odziedziczonych z klasy bazowej. W takiej sytuacji można nadpisać te metody w klasie dziedziczącej. Jednak w wielu przypadkach nie chcemy całkowicie zastępować oryginalnej metody, lecz jedynie ją rozszerzyć o nowe funkcjonalności. W takich sytuacjach funkcja `super()` okazuje się nieoceniona.

Za pomocą `super()` można odwoływać się do atrybutów i wywoływać metody klasy nadrzędnej. Jest to fundamentalne narzędzie w programowaniu obiektowym, ułatwiające implementację dziedziczenia oraz nadpisywania i rozszerzania metod.

Jak działa `super()`?

W swoim działaniu funkcja `super()` jest głęboko powiązana z mechanizmem kolejności rozpoznawania metod (MRO) w Pythonie, który jest ustalany za pomocą algorytmu linearyzacji C3.

Oto zasada działania `super()`:

  • Ustalenie aktualnej klasy i instancji: Gdy wywołujemy `super()` w metodzie klasy dziedziczącej, Python automatycznie identyfikuje klasę w której znajduje się wywołanie `super()`, oraz instancję tej klasy (czyli `self`).
  • Określenie klasy bazowej: Funkcja `super()`, choć nie wymaga jawnego podawania argumentów (klasy i instancji), wykorzystuje te informacje do ustalenia klasy nadrzędnej, do której powinno zostać przekazane wywołanie metody. Python analizuje strukturę dziedziczenia i MRO, aby określić odpowiednią klasę.
  • Wywołanie metody z klasy nadrzędnej: Po zidentyfikowaniu klasy bazowej, `super()` umożliwia wywołanie jej metod tak, jakby były one wywoływane bezpośrednio z klasy potomnej. Daje to możliwość rozszerzenia lub modyfikacji zachowania metod, zachowując jednocześnie dostęp do ich pierwotnej implementacji w klasie nadrzędnej.
  • Użycie `super()` w konstruktorze klasy

    Wykorzystanie `super()` w konstruktorze klasy jest bardzo popularne, gdyż często zachodzi potrzeba inicjalizacji wspólnych atrybutów w klasie bazowej, a następnie dodania atrybutów charakterystycznych dla klasy potomnej.

    Poniżej przykład z klasą `Father` (Ojciec), po której dziedziczy klasa `Son` (Syn):

    class Father:
        def __init__(self, first_name, last_name):
            self.first_name = first_name
            self.last_name = last_name
    
    class Son(Father):
        def __init__(self, first_name, last_name, age, hobby):
            
            super().__init__(first_name, last_name)
    
            self.age = age
            self.hobby = hobby
    
        def get_info(self):
            return f"Son's Name: {self.first_name} {self.last_name}, \
    Son's Age: {self.age}, Son's Hobby: {self.hobby}"
    
    
    son = Son("Pius", "Effiong", 25, "Playing Guitar")
    
    print(son.get_info())
    

    W konstruktorze klasy `Son`, wywołanie `super().__init__()` przekazuje imię i nazwisko do konstruktora klasy `Father`. To zapewnia prawidłowe ustawienie atrybutów imienia i nazwiska, nawet w kontekście obiektu `Son`.

    Brak wywołania `super()` w konstruktorze klasy spowoduje, że konstruktor klasy bazowej nie zostanie wywołany, co może skutkować nieprawidłową inicjalizacją atrybutów lub niekompletną konfiguracją stanu klasy nadrzędnej.

    ...
    class Son(Father):
        def __init__(self, first_name, last_name, age, hobby):
            self.age = age
            self.hobby = hobby
    ...
    

    Próba wywołania metody `get_info` w takim przypadku poskutkuje błędem `AttributeError`, ponieważ atrybuty `self.first_name` i `self.last_name` nie zostaną zainicjalizowane.

    Wykorzystanie `super()` w metodach klas

    `super()` może być użyte w dowolnej metodzie, nie tylko w konstruktorach. Umożliwia to rozszerzenie lub modyfikację zachowania metody odziedziczonej z klasy bazowej.

    class Father:
        def speak(self):
            return "Hello from Father"
    
    class Son(Father):
        def speak(self):
            
            parent_greeting = super().speak()
            return f"Hello from Son\n{parent_greeting}"
    
    
    son = Son()
    
    son_greeting = son.speak()
    
    print(son_greeting)
    

    Klasa `Son` dziedziczy po klasie `Father` i ma swoją własną implementację metody `speak`. W metodzie `speak` klasy `Son`, wywołanie `super().speak()` powoduje wywołanie metody `speak` klasy `Father`. Dzięki temu wiadomość z klasy nadrzędnej jest dołączona do wiadomości specyficznej dla klasy podrzędnej, rozszerzając funkcjonalność pierwotnej metody.

    Brak zastosowania `super()` w metodzie, która nadpisuje inną, oznacza, że nie zostanie wykonana funkcjonalność obecna w metodzie klasy nadrzędnej. Spowoduje to całkowite zastąpienie zachowania metody, co może być przyczyną nieoczekiwanego działania.

    Zrozumienie kolejności rozpoznawania metod

    Kolejność rozpoznawania metod (MRO) jest sekwencją, w jakiej Python przegląda klasy nadrzędne w poszukiwaniu metody lub atrybutu. MRO pomaga Pythonowi ustalić, którą metodę wywołać, w sytuacjach gdy istnieje wiele poziomów dziedziczenia.

    class Nigeria():
        def culture(self):
            print("Nigeria's culture")
    
    class Africa():
        def culture(self):
            print("Africa's culture")
    

    Przyjrzyjmy się co się stanie, gdy utworzymy obiekt klasy `Lagos` i wywołamy metodę `culture`:

  • Python najpierw szuka metody `culture` w samej klasie `Lagos`. Jeśli ją znajdzie, wywołuje ją. W przeciwnym razie przechodzi do kolejnego kroku.
  • Jeśli w klasie `Lagos` nie ma metody `culture`, Python przeszukuje klasy bazowe, w kolejności w jakiej są zadeklarowane w definicji klasy. W tym przypadku, `Lagos` dziedziczy najpierw po `Africa`, a potem po `Nigeria`. Python zacznie więc szukać metody `culture` w `Africa`.
  • Jeżeli metoda `culture` nie zostanie znaleziona w `Africa`, Python przeszuka klasę `Nigeria`. Ten proces trwa aż do osiągnięcia końca hierarchii klas, gdzie w przypadku nie znalezienia metody w żadnej z klas nadrzędnych zgłaszany jest błąd.
  • Dane wyjściowe prezentują MRO klasy `Lagos`, czytając od lewej do prawej.

    Częste błędy i dobre praktyki

    Podczas pracy z `super()` trzeba unikać kilku typowych błędów.

  • Należy zawsze brać pod uwagę kolejność rozpoznawania metod, szczególnie w sytuacjach, gdzie występuje dziedziczenie wielokrotne. W złożonych hierarchiach warto zapoznać się z algorytmem linearyzacji C3, który Python wykorzystuje do określenia MRO.
  • Należy unikać tworzenia cyklicznych zależności w hierarchii klas, ponieważ mogą prowadzić do nieprzewidywalnego działania.
  • Ważne jest jasne i czytelne dokumentowanie kodu, szczególnie w przypadkach użycia `super()` w skomplikowanych hierarchiach klas, aby kod był zrozumiały dla innych programistów.
  • Prawidłowe użycie `super()`

    Funkcja `super()` jest bardzo potężnym narzędziem w Pythonie, zwłaszcza w kontekście dziedziczenia i modyfikacji metod. Zrozumienie, jak działa `super()` oraz przestrzeganie dobrych praktyk, umożliwia tworzenie kodu łatwiejszego w utrzymaniu i bardziej efektywnego w projektach Pythona.


    newsblog.pl