W poprzedniej lekcji, zapoznaliśmy się z podstawami programowania obiektowego w Python. W tej, dowiemy się co to jest dziedziczenie i jak je zastosować w praktyce. Zaczynajmy!
Powiedzmy, chcemy zaimplementować obiekty odpowiadające ptakom: sowa, orzeł, pingwin. Każdy z tych ptaków jest inny, ale wszystkie mają wiele cech i metod wspólnych:
Wspólne cechy: skrzydła, max prędkość, kolor itd
Wspólne operacje: latanie, wydawanie odgłosów itd
Mogli byśmy zaimplementować 3 klasy, odpowiadające ptakom – sowa, orzeł, pingwin, i od początku do końca w nich zdefiniować nasze konstruktory oraz metody. Programowanie jednak nie lubi powtarzalności. Skoro, wszystkie te 3 obiekty, posiadają cechy i metody wspólne, czy można to zapisać, tak aby aby to co wspólne, napisać raz, a potem wykorzystać?
I tutaj do gry wchodzi dziedziczenie.
Co to jest dziedziczenie?
Dziedziczenie, tak jak nazwa wskazuje, jest mechanizmem, który pozwala przekazać do klas sowa, orzeł, pingwin, informację, że cześć cech oraz metod jest zdefiniowana w innej klasie, a one same, są potomkami tej klasy:
klasa ptak, ma kolor, szybkość, wielkość, lata, wydaje odgłosy
klasa sowa, potomek klasy ptak, dodatkowo czuwa w nocy
klasa orzeł, potomek klasy ptak, dodatkowo poluje
kasa pingwin, potomek klasy ptak, jednak nie lata, ale umie się ślizgać
co jak widzimy znacząco może nam uprościć definicje klas sowa, orzeł, pingwin i pozwolić na skupieniu się, tylko na tych aspektach, które faktycznie te klasy charakteryzują.
Dziedziczenie w Python
jak mogło by to wyglądać w języku Python:
class ptak: def __init__ (self, gatunek, szybkość): self.gatunek = gatunek self.szybkość = szybkość def leć(self): print ("Tu", self.gatunek, ". Startuje, i zaraz osiągne prędkość", self.szybkość) def wydajOdgłos(self): pass class orzeł (ptak): def poluj(self): print ("Tu", self.gatunek, ". Rozpoczołem polowanie") class pingwin (ptak): def ślizgaj(self): print ("Tu", self.gatunek, ". Rozpoczołem ślizg") def leć(self): print ("Tu", self.gatunek, ". Przykro mi, ale nie latam")
Co się dzieje?
- Zdefiniowaliśmy klasę 'ptak’, tak jak to robiliśmy w przypadku każdej innej klasy
- Zdefiniowaliśmy klasę orzeł oraz pingwin, jednak po nazwie, w nawiasach, umieściliśmy informację, że klasy te dziedziczą od klasy 'ptak’. Tym samym konstruktor, jak i metoda 'leć()’, są teraz obecne w obydwu klasach potomnych. Również, obydwie klasy odwołuja się swobodnie do parametrów, takich jak 'gatunek’, za pomocą 'self’.
- Pingwin, z faktu że nie lata, musiał nadpisać metodę 'leć’, klasy ptak. To nastąpiło poprzez ponową jej definicję w klasie potomnej pingwin.
A tak może wyglądać, użycie tych klas w praktyce:

Wszystko, zadziałało z naszymi oczekiwaniami.
Dwie rzeczy, które wydają się bez sensu:
- W momencie tworzenia klas 'orzeł’ oraz 'pingwin’, jako pierwszy parametr, podajemy nazwę ptaka. Jest to bez sensu. Skoro tworzymy klasę orzeł, to powinna widzieć, że jest orłem, prawda? Dzieje się tak, ponieważ korzystamy z konstruktora klasy, którą dziedziczymy, a ta nie wie kto ją będzie dziedziczyć. Za chwilę to naprawimy.
- Klasa ptak, zawiera pustą metodę 'wydajOdgłos()’, która została zlekceważona przez nasze klasy potomne. Tym samym, jeżeli wywołamy tą metodę, nie stanie się nic. Dobrze by było wprowadzić obowiązek jej nadpisywania, przez wszystkie klasy potomne, tak aby zawsze uzyskać jakiś odgłos. I to też zaraz naprawimy.
Dodatkowe parametry konstruktora potomka
W naszym przykładnie, potomek 'orzeł’ oraz 'pingwin’, korzystali, tylko i wyłącznie z konstruktora klasy 'ptak’. A tym samym musieliśmy im podawać ich nazwę.
Możemy, wyeliminować ten problem, poprzez utworzenie dodatkowego konstruktora, już w samym potomku, który dla orła będzie przypisywać 'gatunek = orzeł’, tak aby nie trzeba było tego robić przy tworzeniu obiektu, i analogicznie zrobić to, dla klasy pingwin.
Najpierw spójrzmy na kod, a potem go wytłumaczymy:
class ptak: def __init__ (self, gatunek, szybkość): self.gatunek = gatunek self.szybkość = szybkość def lec(self): print ("Tu", self.gatunek, ". Startuje, i zaraz osiągne prędkość", self.szybkość) def wydajOdgłos(self): pass class orzeł (ptak): def __init__ (self, szybkość): super().__init__("orzeł", szybkość) def poluj(self): print ("Tu", self.gatunek, ". Rozpoczołem polowanie") class pingwin (ptak): def __init__(self, szybkość): super().__init__("pingwin", szybkość) def ślizgaj(self): print ("Tu", self.gatunek, ". Rozpoczołem ślizg") def lec(self): print ("Tu", self.gatunek, ". Przykro mi, ale nie latam")
Zwróćmy uwagę na następujące kwestie:
- Dodaliśmy konstruktory w klasach 'orzeł’ oraz 'pingwin’. W momencie tworzenia obiektu tych klas, Python, w pierwszej kolejności wywołuje ich konstruktory.
- Konstruktor klas potomnych, w tym przypadku, zawiera, mniej parametrów, niż konstruktor klasy 'ptak’
- Wywołane konstruktory klas potomnych, inicjują konstruktor klasy od której dziedziczą, za pomocą funkcji super ()
def __init__ (self, szybkość): super().__init__("orzeł", szybkość)
W te sposób, osiągamy dokładnie ten sam efekt, ale eliminujemy konieczność podawania nazwy ptaka, która powinna być oczywista:

Kolejną kwestią jest fakt, że chcieli byśmy wymusić na klasach potomnych 'orzeł’ oraz 'pingwin’, nadpisanie metody klasy 'ptak’, 'wydajOdgłos’.
Klasy i metody abstrakcyjne
Jeżeli klasa, posiada metody abstrakcyjne, oznacza to tyle że, potomstwo jest zmuszone ją nadpisać. Równocześnie klasa taka, nigdy nie powinna być sama w sobie wykorzystana do tworzenia obiektów.
W naszym przykładzie, chcieli byśmy zamienić klasę 'ptak’, na klasę abstrakcyjną, i to samo uczyć z jej metoda 'wydajOdgłos’. Tym samym, nie było by możliwości utworzenia obiektu klasy 'ptak’, natomiast klasy potomne 'orzeł’ oraz 'pingwin’, zmuszone były by do nadpisania tej metody.
Zobaczmy jak by to mogło wyglądać:
from abc import ABC, abstractmethod class ptak(ABC): def __init__ (self, gatunek, szybkość): self.gatunek = gatunek self.szybkość = szybkość def lec(self): print ("Tu", self.gatunek, ". Startuje, i zaraz osiągne prędkość", self.szybkość) @abstractmethod def wydajOdgłos(self): pass class orzeł (ptak): def __init__ (self, szybkość): super().__init__("orzeł", szybkość) def poluj(self): print ("Tu", self.gatunek, ". Rozpoczołem polowanie") def wydajOdgłos(self): print ("wrrrr") class pingwin (ptak): def __init__(self, szybkość): super().__init__("pingwin", szybkość) def ślizgaj(self): print ("Tu", self.gatunek, ". Rozpoczołem ślizg") def lec(self): print ("Tu", self.gatunek, ". Przykro mi, ale nie latam") def wydajOdgłos(self): print ("kwiiiii")
Co się dzieje?
- Na początku pliku importujemy 'ABC’ oraz 'abstractmethod’, z pakietu abc. Jest to pakiet, który umożliwia nam proces tworzenia abstrakcyjnych klas i metod
- Klasa ptak, dziedziczy ABC
- Metody, które są poprzedzone '@abstractmethod’, stają się metodami abstrakcyjnymi, które muszą zostać nadpisane przez potomstwo. Samo wyrażenie '@abstractmethod’, jest czymś co nazywamy dekoratorem, i zajmiemy się nimi w innej lekcji. Do samego tematu programowania obiektowego, nie mamy potrzeby wchodzenia w szczegóły
- Klasy 'orzeł’ oraz 'pingwin’, są zmuszone nadpisać metody klasy 'ptak’, oznaczone jako '@abstractmetod’.
W praktyce, wszystkie metody, klasy która jest dziedziczona mogą być oznaczone dekoratorem '@abstractmethod’. W ten sposób możemy wymusić, aby gatunki ptaków miały te same metody, ale ich implementację zostawić już dla nich samych. Co w praktyce, okazuje się być bardzo praktyczne.
Podsumowując. Dowiedzieliśmy się co to jest dziedziczenie. Jak nadpisywać metody, rozszerzać konstruktor, a nawet powiedzieliśmy o klasach oraz metodach abstrakcyjnych. Bardzo spora dawka informacji. Na ten moment zrobimy przerwę, aby samodzielnie rozwiązać kilka ćwiczeń, które utrwalą i pogłębią nasze zrozumienie programowania obiektowego.
W następnej lekcji, będziemy rozmawiać o metodach specjalnych, enkapsulacji, a potem przejdziemy do całościowego projektu, który, połączy wszystkie kropki w całość. Zapraszamy!
Ćwiczenia
Ćwiczenie1: Tematem tego ćwiczenia, będą jednoślady – skutery oraz motocykle.
Jesteśmy ich dystrybutorem i potrzebujemy napisać program który będzie nimi zarządzać. Na razie jesteśmy na etapie definiowania obiektów. Skutery i motocykle, mają wiele wspólnych cech, ale również swoje specyficzne kwestie.
Wspólne są chociażby: pojemność silnika, kolor, marka, skrzynia biegów, jedne i drugie mogą zostać uruchomione oraz mogą podać swój przebieg.
Należy zdefiniować klasę jednoślad, zawierającą cechy i metody wspólne, następnie zdefiniować klasę skuter oraz motocykl, które będę tę klasę będą dziedziczyć.
Rozwiązanie 1: Potencjalne rozwiązanie, co nie oznacza optymalne, mogło by wyglądać następująco:
Definiujemy klasę jednoślad, tworzymy konstruktor, oraz między innymi abstrakcyjną metodę 'podajDane()’
from abc import ABC, abstractmethod class jednoślad(ABC): wlaściciel = "MotoFun sp zoo" przebieg = 0 def __init__(self, typ, marka, kolor): self.typ = typ self.marka = marka self.kolor = kolor def jedz(self): self.przebieg += 1 def podajPrzebieg(self): return self.przebieg @abstractmethod def podajDane(self): pass
Następnie definiujemy klasy motocykl oraz skuter. Nadpisujemy konstruktor, ale nie zapominając o konstruktorze nadrzędnych, którego też wywołujemy. Następnie nadpisujemy metodę podajDane. Gdzie każda kasa definiuje ją po swojemu.
class motocykl(jednoślad): def __init__(self, marka, kolor, rocznik, prędkośćMax): super().__init__("motocykl", marka, kolor) self.rocznik = rocznik self.prędkośćMax= prędkośćMax def podajDane(self): dane = "Super szybki motocykl " + str(self.prędkośćMax) + " max km/h" + "\n" dane += "Marki " + self.marka + "\n" dane += "Przebieg " + str(self.podajPrzebieg()) + "\n" return dane class skuter(jednoślad): def __init__(self, marka, kolor, rocznik, styl): super().__init__("skuter", marka, kolor) self.styl = styl self.skrzynia = "automatyczna" def podajDane(self): dane = "Stylowy skuter " + self.styl + "\n" dane += "Marki " + self.marka + "\n" dane += "Przebieg " + str(self.podajPrzebieg()) + "\n" return dane
A następnie, tworzymy zmienną typu lista, która będzie nasza bazą danych oraz tworzymy różne obiekty i je dodajemy.
Z faktu, że wszystkie dziedziczą od jednośladu, możemy być pewni, że każdy z obiektów na liście zawiera metodę 'podajDane()’.
bazaDanych = [] bazaDanych.append(motocykl("Honda", "czerwony", 1996, 232)) bazaDanych.append(motocykl("BMW", "czarny", 2015, 190)) bazaDanych.append(skuter("Vespa", "biały", 2001, "Włoski")) bazaDanych.append(skuter("Junak", "odcień kości słoniowej", 2005, "Skandynawski")) for i in bazaDanych: print(i.podajDane())
Python, NIE tylko dla początkujących
- Wstęp do kursu
- Moduły i pakiety
- Programowanie obiektowe – wstęp
- Programowanie obiektowe – dziedziczenie <– bieżąca lekcja
- Programowanie obiektowe – hermetyzacja
- Mini projekt – Organizer
- Funkcje rekurencyjne w Python
- Pisanie skryptów w Python
- Mini projekt – tajny agent, generator haseł
- *args oraz **kwargs
- Dekoratory w Python
- Mini projekt – tajny agent – kontakty
- Odwzorowanie list
- Podsumowanie oraz dalsze kroki