Skip to content

Method Resolution Order (MRO)

Python programlama dili Object Oriented Programming(OOP – Nesne Yönelimli Programlama) paradigmasını kullanarak uygulamalar geliştirebileceğimiz araç, gereçleri sunmaktadır. OOP kavramının özünde ise nesnelerin davranış ve özelliklerini paketleyerek temiz, tekrar kullanılabilir ve bakım maliyeti düşük uygulamalar geliştirmek yer almaktadır. Bahsettiğimiz bu paketleme işlemini ise classlar üzerinden gerçekleştiririz, pythonda user-defined type olarak tanımlanan class yapısı, tanımlanırken class keywordu kullanılır.

class Person:
    pass

Pythonda classlar türkçede miras alma, kalıtım anlamlarına gelen postance özelliğine sahiptirler. İnheritance sayesinde classlar başka classların özelliklerini devralabilir ve davranışlarını kopyalayabilirler.

class Person:
    year: int = 0
    
    def breath(self):
        print("I'm breathing")
        
        
class Employee(Person):
    pass

employee = Employee()
employee.breath()

-----------------------------------------------
Output:
I'm breathing

İnsan/kişi kavramını bir sınıf olarak düşünelim insanlar sayısal bir veri olan yaş özelliği taşır ve nefes alma davranışı gösterirler, aynı zamanda bir çalışan sınıfı oluşturacak olursak çalışanlarda insan özelliklerini taşıyacaktır. Bu noktada ise çalışan sınıfımızı insan/kişi sınıfından post ederek, aynı davranış ve özellikleri almasını sağladık, bunu yaparken class Employee(Person): tanımıyla classımızı oluşturduk ve gövdesine herhangi bir şey yazmamıza rağmen instance üzerinden breath() metodunu çağırabildik, diğer taraftan bir sınıf birden fazla sınıfın özelliğini taşıyabilir yani birden fazla sınıftan miras alabilir. İlkokul biyoloji derslerinde öğrendiğimiz virüsleri düşünelim, virüsler: hücre içindeyken canlı özellikler gösterirken hücre dışındalarken canlılık özelliği göstermezler bu bilgiden yola çıkarak virüsü modelleyebiliriz.

class Alive:
    def alive(self):
        print("I'm alive")
        
class Dead:
    def dead(self):
        print("I'm not alive")
        
        
class Virus(Alive, Dead):
    pass


virus = Virus()
virus.alive()
virus.dead()

-----------------------------------------------
Output:
I'm alive
I'm not alive

Alive ve Dead classlarımız super class veya parent class olarak adlandırılırken, bu iki classtan kalıtım alarak ürettiğimiz Virus classımız subclass veya child class olarak adlandırılır.

Classlara ve postence kavramına kabaca bir göz attıktan sonra asıl konumuz olan method resolution order kavramını anlamak için yeterli bilgiye sahibiz demektir. (Pythonda OOP kavramıyla ilgili daha fazla bilgi için şuraya göz atabilirsiniz, bu yazıdaki konu MRO olduğundan sadece ilgili kısımları ele aldık.)

Pythonda sınıfların birden fazla sınıftan kalıtım alabileceğinden söz ettik, kalıtım yoluyla super class’tan subclass’a geçen özellikler ve davranışlar belirli bir sıra yoluyla çağırılırlar, bu sıralama ise bizi konumuz olan method resolution order kavramına getiriyor. Türkçeye kabaca yöntem çözümleme sıralaması olarak çevirebileceğimiz kavram sayesinde methodların, kalıtım alan sınıfların metodlarının hangi sıra ile çağırılacağını belirler. Kod üzerinden açıklayacak olursak.

class A():
    def run(self):
        print("Hello from A class")

class B(A):
    def run(self):
        print("Hello from B class}")

class C(A):
    def run(self):
        print("Hello from C class")
        
class X(C, B):
    pass

x = X()
x.run()

-----------------------------------------------
Output:
Hello from C class

X subclassı C ve B classlarından kalıtım alarak oluşturduk, C ve B classları ise run() metoduna sahipler X classının instance/örneği üzerinden run metodunu çalıştırdığımızda C classı içindeki run() metodunu çalıştırıyor. X classını tanımlarken kalıtım sırasını (B, C) şeklinde yaparsak.

Hello from B class

örnektede görüldüğü üzere sıralama sağdan sola olacak şekilde çalışıyor, run() metodu bu sırayla sınıflar içinde aranıyor ve ilk bulunan sınıftaki run() metodu çalıştırılıyor. Peki C sınıfının instance’ı üzerinden run() metodunu çalıştırmak isteseydik.

b = B()
b.run()
-----------------------------------------------
Output:
Hello from C class

B sınıfı A sınıfından kalıtım almış olmasına rağmen önce kendi içerisindeki run() metodunu aradı, buldu ve çalıştırdı.

X sınıfı için: class X -> class C -> class B -> class A
C sınıfı için: class C -> class A 

Python bu sıralamayı builtin metodla görmemizi de sağlıyor. __mro__ veya mro() metoduyla sınıfların method resolution orderlarını görebiliyoruz.

print(X.__mro__)
print(C.mro())
-----------------------------------------------
Output:
(<class '__main__.X'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
[<class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

Şimdi biraz daha derinlere dalma vakti, yukarıdaki örnekte de gördüğümüz üzere python birden fazla sınıftan kalıtım alabilme yeteneğine sahiptir(multiple postence), bu yeteneğe sahip programlama dillerinde(örneğin Java ve C#’ta dillerinde multiple postence yapılamaz) ise diamond problem olarak tanımlanan problem ortaya çıkmaktadır. Elmas problemini çoklu kalıtım durumlarında hangi metodun özelliğinin veya davranışının önce çağırılacağının belirsiz olması durumu olarak tanımlayabiliriz. X sınıfı B ve C sınıflarından kalıtım almakta, ve X sınıfının instance’i üzerinden run() metodu çağırıldığımızda B’de override edilmiş ve özelleştirilmiş metodmu çağırılmalıdır, yoksa C’de override edilmiş metodmu çağırılmalıdır? Daha açıklayıcı olması için C classından run() metodunu silerek pass keywordu ile geçelim.

class A():
    def run(self):
        print("Hello from A class")

class B(A):
    def run(self):
        print("Hello from B class}")

class C(A):
    pass
        
class X(C, B):
    pass

x = X()
x.run()

-----------------------------------------------
Output:
Hello from A class

X sınıfının instance’ı üzerinden run() metodunu çağırdığımızda önce X sınıfında metodu arıyor ve bulamıyor bu noktada hem fikir olduğumuzu düşünüyorum, sonra C sınıfına gidiyor ve metodu arıyor C classında da bu method yok, Depth-first left to right kuralına göre B sınıfında bir sonraki aramayı gerçekleştirmesi gerekiyordu ancak bunun yerine X classının base classında yani A classında bulunan run() metodunu çalıştırdı. (yukarıdaki diagramıda elmas şekline benziyor gibi?)

# Python 2.3 öncesi 
class A():
    def run(self):
        print("Hello in A class")

# Python 2.3 sonrası ve Python 3+
class A(object):
    def run(self):
        print("Hello in A class")

Python2.3 versiyonu öncesi ise bu problemin çözümü olarak depth-first left to right (DLR) algoritması kullanılır. Yani önce sub classlar daha derindeki sub classlar içerisinde, soldan sağa sonra ise daha az derinliğe sahip classlarda arama yapar. Python2.3 versiyonundan sonra ve Python3+ versiyonlarında class tanımlanırken object keywordu kullanılır(new style classes) ve tüm classlar object sınıfından otomatik olarak kalıtım alır.

Python2.2 sonrası ve Python3+ versiyonlarında mro C3 Linearization Algorithm(C3 Lineerleştirme Algoritması) kullanılarak yapılır. Bu algoritma 3 temel kural üzerine kurulmuştur.

  • Kalıtım grafiği, mro’nun yapısını belirler.
  • Yalnızca sınıfın metodlarına gidildikten sonra super sınıfın metodlarına gidilebilir.
  • Monotonluk

bu kurallarla birlikte:

  • Sub classlar super classlardan önce gelir.
  • Bir class birden fazla classtan post edildiyse, sıralama base classta belirtilen sırada tutulur.

Kısaca kod üzerinden bu kural ve kısıtlamalara değinecek olursak.

class A(object): 
    pass


class B(object): 
    pass


class C(A,B) : 
    pass


class D(B,A): 
    pass


class E(C,D): 
    pass

e = E()
print(e.__mro__)

------------------------------------------------------------------------
Output:
TypeError: Cannot create a consistent method resolution
order (MRO) for bases A, B

Kurala göre E sınıfı hem D hemde C sınıfından kalıtım alamaz çünkü C sınıfında A, B den önce gelirken D sınıfında B, A’dan önce gelmektedir. Bu sebepten dolayı E(C) veya E(A) olarak sınıfımızı oluşturmaya zorlamaktadır.

O  = object

class F(O): 
    pass

class E(O): 
    pass

class D(O): 
    pass

class C(D,F): 
    pass

class B(D,E): 
    pass

class A(B,C): 
    pass

print(A.mro())

------------------------------------------------------------------------
Output:
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <class 'object'>]

Notasyon ve biraz daha formule bir şekilde yukarıdaki kodu ve D3 lineerleştirme algoritmasıyla mro’nun nasıl oluştuğunu görelim.

C1 C2 C3 ... CN

C harfinin sınıflara karşılık geldiğini düşünün C1’den CN’e kadar olan sınıfların listesini şöyle oluşturabiliriz.

[C1, C2, C3, ..., CN]

Sınıf listesinin head/baş kısmında C1 sınıfı var ve bu durumda geri kalan C2 C3 … CN sınıfları ise tail/kuyruk kısmını oluşturuyor.

head: C1
tail: C2 C3 ... CN

Listeyi yeni bir C sınıfı eklemek istediğimizde ise şöyle yapıyoruz.

C + (C1 C2 C3 ... CN) = C C1 C2 C3... CN

C sınıfının lineerleştirilmesini: C sınıfı + üst sınıfların doğrusallaştırmaları + üst sınıfların birleşimi olarak tanımlayabiliriz. C sınıfı BN kadar B sınıfından multiple post olsun bunun notasyonda gösterimi ise şöyle olacaktır.

L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN)

Doğrusallaştırmada kullanacağımız mantık ise şöyle olacak, Listenin head/başını alıyoruz, eğer head bölümü diğer listelerden herhangi birinin kuğruğunda değilse, doğrusallaştırmaya ekliyoruz ve listeden çıkarıyoruz, listenin head kısmı bu şartı sağlamıyorsa(diğer listelerin herhangi birinin kuyruğundaysa) bir sonraki listenin head’ına bakıyoruz, bu şart sağlanana kadar aynı işlemi tekrar ediyoruz bu şartın sağlanmadığı durumlarda doğrusallaştırma işlemi yapılamaz.(yukarıdaki TypeError exceptionu fırlatan örnek) yukarıdaki mro’su hesaplanmış örnek kod için öğrendiğimiz mantık ve notasyonla doğrusallaştırma yapacak olursak.

L[O] = O

L[F] = F O

L[E] = E O

L[D] = D O

L[C] = C + merge(DO, FO, DF) 
= C + D + merge(O, FO, F) 
= C + D + F + merge(O, O) 
= CDFO

L[B] = B + merge(DO, EO, DE) 
= B + D + merge(O, EO, E) 
= B + D + E + merge(O, O) 
= BDEO

L[A] = A + merge(BO, CO, BC)
= A + merge(BDEO, CDFO, BC) 
= A + B + merge(DEO, CDFO, C)
= A + B + C + merge(DEO, DFO)
= A + B + C + D + merge(EO, FO)
= A + B + C + D + E + merge(O, FO)
= A + B + C + D + E + F + merge(O, O)
= ABCDEFO

Örnekte lineerleştirilen yapı aslında D3 lineerleştirme algoritmasının ilk maddesi kalıtım grafiğine göre iyi sıralanmış durumdadır, B sınıfını D ve E sınıflarının yerini değiştirerek kalıtım alsaydı (class B(E, D)) kalıtım grafiği değişiyor olacaktı ve sıralamada değişecekti. Böylelikle bu yazınında sonuna gelmiş bulunuyoruz. MRO konusunda daha fazla ayrıntı için buraya gidebilirsiniz.


Sources:

Published inPython

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *