Kolejność Rozwiązywania Metod (MRO) — to mechanizm w Pythonie, który określa, w jakiej kolejności klasy będą przeszukiwane w celu znalezienia metod i atrybutów w przypadku dziedziczenia wielokrotnego.
C3-linarnizacja (C3 linearization) — algorytm, który jest stosowany do tworzenia kolejności rozwiązywania metod. Zrozumienie MRO jest krytycznie ważne podczas projektowania złożonej hierarchii klas, aby uniknąć niejasności i nieoczekiwanego zachowania metod.
Można poznać MRO dla dowolnej klasy za pomocą atrybutu __mro__ lub funkcji mro():
class A: def hello(self): print('A') class B(A): def hello(self): print('B') class C(A): def hello(self): print('C') class D(B, C): pass print(D.__mro__) # (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) obj = D() obj.hello() # 'B', ponieważ B jest przed C w MRO
Nie można jawnie zmieniać MRO, ale można kontrolować kolejność wymieniania klas bazowych.
W jakiej kolejności będą wywoływane metody, jeśli klasa dziedziczy od kilku rodziców? Na przykład:
class Base: def foo(self): print('Base') class Left(Base): def foo(self): print('Left') super().foo() class Right(Base): def foo(self): print('Right') super().foo() class Child(Left, Right): pass Child().foo()
Wielu ludzi oczekuje, że "foo" zostanie wywołane najpierw z Left, potem z Right, a następnie z Base — co się zgadza. Jednak jeśli zmienimy kolejność klas bazowych (class Child(Right, Left)), kolejność się zmieni!
Prawidłowa odpowiedź:
Kolejność wywołań będzie taka, jak określono w MRO. W pierwszym przypadku — Left, Right, Base. MRO jest budowane według określonego algorytmu (C3-linarnizacja):
[Child, Left, Right, Base, object]
Historia
Wielokrotne dziedziczenie logiki, nadpisywanie metody
Zespół deweloperów używał wielokrotnego dziedziczenia dla mixinów. Okazało się, że z powodu niewłaściwej kolejności klas bazowych, wspólna logika mixina została "przykryta" przez klasę potomną, a część logiki biznesowej nie była wykonywana, co prowadziło do utraty danych.
Historia
Wywoływana nie ta metoda rodzica
W metodzie finalizującej (__del__) oczekiwano, że wywołana zostanie metoda klasy bazowej. Jednak z powodu błędnego zrozumienia MRO, wywołano inną, odziedziczoną metodę mixina, w której nie było czyszczenia zasobów. W rezultacie — wyciek pamięci.
Historia
Użycie super() bez zrozumienia łańcucha wywołań
W projekcie Python 3 zamawiający przepisał część kodu z wielokrotnym dziedziczeniem, zapominając zaktualizować wywołania przez super(). Doprowadziło to do sytuacji niekończącej się rekurencji, gdy metoda ciągle wywoływała samą siebie z powodu błędnej kolejności rozwiązywania metod.