11.1 Method Resolution Order
Porządek rozwiązywania metod (Method Resolution Order, MRO) określa kolejność, w jakiej Python szuka metod i atrybutów w hierarchii klas. Jest to szczególnie ważne przy pracy z wielodziedziczeniem, gdy klasa może dziedziczyć atrybuty i metody z kilku klas macierzystych.
Mówiąc ogólnie, istnieje ścisły, określony porządek (albo raczej algorytm), według którego Python przeszukuje drzewo dziedziczenia klas. Ten algorytm zapewnia poprawny porządek wyszukiwania metod, który można opisać następująco:
Algorytm C3-linearizacji
Algorytm C3-linearizacji określa MRO przez łączenie:
- Samo klasy.
- Listy klas macierzystych w kolejności ich wymienienia.
- MRO klas macierzystych w tej samej kolejności.
Zasady algorytmu C3-linearizacji
- Zachowanie lokalnego porządku metod: jeśli klasa
A
jest wymieniona przed klasąB
, wszystkie metody klasyA
powinny być rozważane przed metodami klasyB
. - Utrzymanie porządku w klasach rodzicielskich: jeśli klasa
A
jest rodzicem klasyB
, wszystkie metody klasyA
powinny być rozważane przed metodami klasyB
. - Uważanie na porządek dziedziczenia: jeśli klasa
C
jest rodzicem dla dwóch lub więcej klas, porządek metod klasyC
powinien być zachowany w MRO wszystkich tych klas.
Kroki algorytmu:
Krok 1. Zaczynamy od samej klasy:
Zawsze zaczynamy od klasy, w której metoda została wywołana.
Krok 2. Dodajemy klasy bazowe w kolejności ich wymienienia:
Po bieżącej klasie sprawdzamy klasy bazowe w takiej kolejności, w jakiej zostały wymienione przy dziedziczeniu.
Krok 3. Przechodzimy przez klasy rodzicielskie:
Szukamy tam pól i metod.
Krok 4. Łączymy MRO klas rodzicielskich:
Jeśli ta sama klasa bazowa jest dziedziczona przez kilka ścieżek, jest sprawdzana tylko jeden raz i w poprawnej kolejności (wszystkie inne razy zostaje pominięta).
Dla tych, którzy już są zaznajomieni z tematem „Algorytmy i struktury danych”, jest to przeszukiwanie w głąb, a nie wszerz.
11.2 Sprawdzanie MRO
W Pythonie można sprawdzić kolejność przeszukiwania metod i pól klasy, używając atrybutu __mro__
lub funkcji mro()
.
Przykład:
class A:
def method(self):
print("A")
class B(A):
def method(self):
print("B")
class C(A):
def method(self):
print("C")
class D(B, C):
def method(self):
print("D")
# Sprawdzanie MRO
print(D.__mro__)
Wynik będzie:
(<class '__main__.D'>,
<class '__main__.B'>,
<class '__main__.C'>,
<class '__main__.A'>,
<class 'object'>)
To pokazuje kolejność, w jakiej Python będzie szukać metod i atrybutów:
D
: Python najpierw sprawdza metodę w klasieD
.-
B
: Następnie Python sprawdza metodę w klasieB
(pierwsza klasa rodzicielska). -
C
: Jeśli metoda nie jest znaleziona w klasieB
, Python sprawdza metodę w klasieC
(druga klasa rodzicielska). -
A
: Jeśli metoda nie jest znaleziona w klasachB
iC
, Python sprawdza metodę w klasieA
. -
object
: Na koniec, Python sprawdza metodę w bazowej klasieobject
.
11.3 Użycie super()
z MRO
Funkcja super()
podąża za MRO, aby wywołać metody klas rodzicielskich w poprawnej kolejności. Zobaczmy przykład użycia
super()
:
class A:
def method(self):
print("A")
super().method()
class B(A):
def method(self):
print("B")
super().method()
class C(A):
def method(self):
print("C")
super().method()
class D(B, C):
def method(self):
print("D")
super().method()
d = D()
d.method()
Wynik będzie następujący:
D
B
C
A
Kolejność przeszukiwania (MRO)
1. Wywołanie metody method
klasy D
:
- Python najpierw sprawdza metodę w klasie
D
i znajduje ją tam. - Metoda
D.method()
jest wykonywana i drukuje"D"
. - Następnie wywoływane jest
super().method()
, które podąża za MRO, aby wywołać następną metodę.
2. Wywołanie metody method
klasy B
:
- Zgodnie z MRO, następną klasą po
D
jestB
. - Metoda
B.method()
jest wykonywana i drukuje"B"
. - Następnie wywoływane jest
super().method()
, które podąża za MRO, aby wywołać następną metodę.
3. Wywołanie metody method
klasy C
:
- Następną klasą w MRO po
B
jestC
. - Metoda
C.method()
jest wykonywana i drukuje"C"
. - Następnie wywoływane jest
super().method()
, które podąża za MRO, aby wywołać następną metodę.
4. Wywołanie metody method
klasy A
:
- Następną klasą w MRO po
C
jestA
. - Metoda
A.method()
jest wykonywana i drukuje"A"
. - Następnie wywoływane jest
super().method()
, ale ponieważA
nie ma rodzicielskich metodmethod
(opróczobject
), wywołanie zakończy się bez dalszych działań.
GO TO FULL VERSION