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
Ajest wymieniona przed klasąB, wszystkie metody klasyApowinny być rozważane przed metodami klasyB. - Utrzymanie porządku w klasach rodzicielskich: jeśli klasa
Ajest rodzicem klasyB, wszystkie metody klasyApowinny być rozważane przed metodami klasyB. - Uważanie na porządek dziedziczenia: jeśli klasa
Cjest rodzicem dla dwóch lub więcej klas, porządek metod klasyCpowinien 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 klasachBiC, 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
Di 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
DjestB. - 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
BjestC. - 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
CjestA. - Metoda
A.method()jest wykonywana i drukuje"A". - Następnie wywoływane jest
super().method(), ale ponieważAnie ma rodzicielskich metodmethod(opróczobject), wywołanie zakończy się bez dalszych działań.
GO TO FULL VERSION