super ve çoklu kalıtım
Çoklu kalıtım ve super konusuna girmeden önce... Uyarı, işler epey tuhaf ve karmaşık olabilir.
Öncelikle, çoklu kalıtım nedir? Şimdiye kadar örnek kod, tek bir alt sınıfın tek bir ebeveyn sınıftan kalıtım almasını kapsıyordu. Çoklu kalıtımda birden fazla ebeveyn sınıf vardır. Bir alt sınıf 2, 3, 10 vb. ebeveyn sınıftan kalıtım alabilir.
İşte super’ın faydalarının daha netleştiği yer burası. Farklı ebeveyn sınıf adlarına referans vermenin tuş vuruşlarını azaltmanın yanı sıra, çoklu kalıtım düzenleriyle super kullanmanın incelikli faydaları vardır. Kısacası, çoklu kalıtım kullanacaksanız, super kullanın.
super olmadan çoklu kalıtım
Herhangi bir ebeveyn metodunu değiştirmekten kaçınan ve dolayısıyla super’dan kaçınan bir çoklu kalıtım örneğine bakalım.
Girdi
class B:
def b(self):
print('b')
class C:
def c(self):
print('c')
class D(B, C):
def d(self):
print('d')
d = D()
d.b()
d.c()
d.d()
Çıktı
b
c
d
Çoklu çözümleme sırası
Çoklu kalıtım kavramı düşünüldüğünde bu çıktı pek şaşırtıcı değil. D, ebeveyn sınıflarından x ve z metotlarını miras aldı ve şimdilik her şey yolunda...
Peki ya B ve C’nin her ikisinde de aynı isimde bir metot olsaydı? İşte burada MRO (method resolution order – metot çözümleme sırası) denilen bir kavram devreye girer. Bir alt sınıfın MRO’su, Python’un belirli bir metodu nerede arayacağını ve bir çakışma olduğunda hangi metodun çağrılacağını belirler.
Bir örneğe bakalım.
Girdi
class B:
def x(self):
print('x: B')
class C:
def x(self):
print('x: C')
class D(B, C):
pass
d = D()
d.x()
print(D.mro())
Çıktı
x: B
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]
Kalitılan x metodunu çağırdığımızda, yalnızca B’den miras alınan çıktıyı görüyoruz. D sınıfımızın MRO’sunu mro sınıf metodunu çağırarak görebiliriz. D.mro() çıktısından şunları öğreniyoruz: Programımız varsayılan olarak D içindeki metotları deneyecek, sonra B’ye, ardından C’ye ve en sonunda object’e başvuracak. Eğer aranan metot bu yerlerin hiçbirinde bulunamazsa, D’de talep edilen metodun olmadığına dair hata alırız.
Varsayılan olarak her sınıfın object’ten kalıtım aldığını ve bunun her MRO’nun sonunda yer aldığını not etmek gerekir.
Çoklu kalıtım, super ve elmas problemi
Aşağıda, init’in MRO’sunu faydalı bir şekilde ele almak için super kullanımına bir örnek bulunuyor. Örnekte bir dizi metin işleme sınıfı oluşturup, bunların işlevselliğini çoklu kalıtım ile başka bir sınıfta birleştiriyoruz. 4 sınıf oluşturacağız ve kalıtım yapısı aşağıdaki diyagramdaki yapıyı takip edecek.
Not: Bu yapı sadece örnekleme amaçlıdır ve kısıtlar olmadıkça bu mantığı uygulamanın daha iyi yolları vardır.

Bu aslında çoklu kalıtımın "elmas problemi"ne bir örnektir. Adı elbette tasarımın şekline ve epey kafa karıştırıcı bir problem olmasına dayanır.
Aşağıda tasarım, super kullanılarak yazılmıştır.
Girdi
class Tokenizer:
"""Tokenize text"""
def __init__(self, text):
print('Start Tokenizer.__init__()')
self.tokens = text.split()
print('End Tokenizer.__init__()')
class WordCounter(Tokenizer):
"""Count words in text"""
def __init__(self, text):
print('Start WordCounter.__init__()')
super().__init__(text)
self.word_count = len(self.tokens)
print('End WordCounter.__init__()')
class Vocabulary(Tokenizer):
"""Find unique words in text"""
def __init__(self, text):
print('Start init Vocabulary.__init__()')
super().__init__(text)
self.vocab = set(self.tokens)
print('End init Vocabulary.__init__()')
class TextDescriber(WordCounter, Vocabulary):
"""Describe text with multiple metrics"""
def __init__(self, text):
print('Start init TextDescriber.__init__()')
super().__init__(text)
print('End init TextDescriber.__init__()')
td = TextDescriber('row row row your boat')
print('--------')
print(td.tokens)
print(td.vocab)
print(td.word_count)
Çıktı
Start init TextDescriber.__init__()
Start WordCounter.__init__()
Start init Vocabulary.__init__()
Start Tokenizer.__init__()
End Tokenizer.__init__()
End init Vocabulary.__init__()
End WordCounter.__init__()
End init TextDescriber.__init__()
--------
['row', 'row', 'row', 'your', 'boat']
{'boat', 'your', 'row'}
5
Öncelikle, TextDescriber sınıfının sınıf aile ağacındaki tüm öznitelikleri miras aldığını görüyoruz. Çoklu kalıtım sayesinde birden fazla sınıfın işlevselliğini "birleştirebiliyoruz".
Şimdi sınıfların init metotlarından gelen yazdırmaları tartışalım:
Her __init__ metodu tam olarak bir kez çağrıldı.
TextDescriber sınıfı, Tokenizer’dan kalıtım alan 2 sınıftan kalıtım aldı. Peki Tokenizer.__init__ neden iki kez çağrılmadı?
Tüm super çağrılarımızı eski usul çağrılarla değiştirirsek, Tokenizer.__init__ iki kez çağrılırdı. super çağrıları kalıbımızı biraz daha "düşünür" ve A’ya yapılacak fazladan ziyareti atlar.
Her __init__ metodu, diğerlerinden herhangi biri tamamlanmadan önce başlatıldı.
Her bir __init__’in başlama ve bitiş sırası, başka bir ebeveyn sınıfla isim çakışması olan bir öznitelik ayarlamaya çalışıyorsanız dikkate değerdir. Öznitelik üzerine yazılacaktır ve işler çok kafa karıştırıcı hale gelebilir.
Bizim durumda, miras alınan özniteliklerde isim çakışmalarından kaçındık, bu nedenle her şey beklendiği gibi çalışıyor.
Tekrarlamak gerekirse, elmas problemi hızla karmaşıklaşabilir ve beklenmedik sonuçlara yol açabilir. Programlamada çoğu durumda olduğu gibi, karmaşık tasarımlardan kaçınmak en iyisidir.
Neler öğrendik
superfonksiyonunu ve tekli kalıtımdaParentName.method’un yerine nasıl kullanılabileceğini öğrendik. Bu, daha sürdürülebilir bir uygulama olabilir.- Çoklu kalıtımı ve bir alt sınıfa birden fazla ebeveyn sınıfın işlevselliğini nasıl aktarabileceğimizi öğrendik.
- Çoklu çözümleme sırasını ve ebeveyn metotları arasında isim çakışması olduğunda çoklu kalıtımda ne olacağına nasıl karar verdiğini öğrendik.
- Elmas problemini öğrendik ve
superkullanımının elmasın içinde nasıl yol aldığını gösteren bir örnek gördük.

Kalıtıma hızlı bir bakış
Python projelerinizi ve paketlerinizi büyüttükçe, sınıflardan yararlanmak ve bunu yaparken DRY (kendini tekrarlama) ilkesini uygulamak isteyeceksiniz. Sınıf kalıtımı, DRY kalmak için bir sınıfı başka bir sınıfa dayanarak oluşturmanın harika bir yoludur. Bu yazı kalıtımın daha ileri kavramlarını ele alacak; temel kalıtım ayrıntılı olarak işlenmeyecek. Kısa bir giriş yapacağız, ancak dışarıda çok daha iyi ve detaylı girişler var. Başlamak için bazı kaynaklar: Object-Oriented Programming in Python kursu ve Python Object-Oriented Programming (OOP): Tutorial.
Peki sınıf kalıtımı nedir? Genetikte olduğu gibi, bir alt sınıf ebeveynden öznitelikleri ve metotları "miras" alabilir. Hemen bir örnek için koda dalalım. Aşağıdaki kod bloğunda,
ChildsınıfınınParentsınıfından kalıtım aldığı bir örnek göstereceğiz.Girdi
Çıktı
ChildsınıfınınParentsınıfından öznitelikleri ve metotları "miras" aldığını görüyoruz. Biz ek bir şey yapmadan,Parent.parent_methodzatenChildsınıfının bir parçası.Parent.__init__()metodunun faydalarını elde etmek için metodu açıkça çağırıpselfgeçmemiz gerekti. Bunun nedeni,Childiçine bir__init__metodu eklediğimizde, miras alınan__init__'i üzerine yazmış olmamızdır.Kısacık ve kapsamlı olmayan bu özetten sonra yazının esas kısmına geçelim.
super’a girişEn basit durumda,
superfonksiyonuParent.__init__(self)şeklindeki açık çağrının yerini alabilir. İlk bölümdeki giriş örneğimiz, aşağıda görüldüğü gibisuperkullanılarak yeniden yazılabilir. Not: Aşağıdaki kod bloğu Python 3 ile yazılmıştır; önceki sürümlerde sözdizimi biraz farklıdır. Ayrıca, çıktı ilk kod bloğuyla aynı olduğu için burada yer verilmemiştir.Dürüst olmak gerekirse, bu durumda
superbize çok az, hatta hiç avantaj sağlamaz. Ebeveyn sınıfın adına bağlı olarak birkaç tuş vuruşu tasarrufu sağlayabiliriz ve__init__çağrısınaselfgeçmek zorunda kalmayız. Aşağıda tekli kalıtım durumlarındasuperkullanımının artı ve eksileri yer alıyor.Eksiler
Burada
superkullanmanın kodu daha az açık hale getirdiği öne sürülebilir. Kodu daha az açık hale getirmek, "Açık olan örtük olandan iyidir." diyen Python Zen’ine aykırıdır.Artılar
Tekli kalıtımda bile
superiçin sürdürülebilirlik açısından bir argüman sunulabilir. Her ne sebeple olursa olsun alt sınıfınızın kalıtım düzeni değişirse (ör. ebeveyn sınıf değişir ya da çoklu kalıtıma geçiş olur)ParentClass.method_name()şeklindeki kalmış tüm referansları bulup değiştirmeye gerek kalmaz;superkullanımı, değişikliklerinclassbildirimindeki değişiklikle birlikte akmasını sağlar.