Python'da kalıtım, sınıf tanımında taban sınıfı parantez içinde yazarak kurulur: class Yonetici(Calisan):. Alt sınıf, taban sınıfın bütün öznitelik ve metotlarını devralır, istediğini aynı adla yeniden tanımlayarak geçersiz kılar (override). Geçersiz kılınan metodun taban sınıftaki sürümüne super().metot() ile ulaşılır. Birden fazla taban sınıf da verilebilir; o durumda Python metotları MRO (Method Resolution Order, metot çözümleme sırası) denen ve C3 algoritmasıyla hesaplanan bir sıraya göre arar.

Bu yazıda tekli kalıtımı, super() çağrısının gerçekte ne yaptığını, isinstance() ve issubclass() farkını, çoklu kalıtımda elmas (diamond) yapısını, MRO'nun nasıl hesaplandığını, tutarsız hiyerarşilerde neden TypeError alındığını, soyut taban sınıfları ve __init_subclass__ kancasını Python 3.15.2 ile çalıştırılmış örneklerle ele alıyoruz. Sınıf temelleri için önce class ve nesne yazısını, konu haritası için Python öğrenme rehberini okuyabilirsiniz.

Tekli kalıtım ve metot geçersiz kılma

Tekli kalıtım ve metot geçersiz kılma

Alt sınıf tanımı, sıradan bir sınıf tanımı gibi çalıştırılır; tek fark, sınıf nesnesi oluşturulurken taban sınıfın hatırlanmasıdır. Bir öznitelik alt sınıfta bulunamazsa arama taban sınıfa, o da bulamazsa onun tabanına doğru devam eder. Alt sınıf aynı adlı bir metot tanımlarsa arama orada durur; buna geçersiz kılma denir.

python
class Calisan:
    def __init__(self, ad, maas):
        self.ad = ad
        self.maas = maas

    def yillik(self):
        return self.maas * 12

    def tanit(self):
        return f'{self.ad}: {self.yillik()} TL'

class Yonetici(Calisan):
    def __init__(self, ad, maas, prim):
        super().__init__(ad, maas)      # taban sınıfın kurulumu
        self.prim = prim

    def yillik(self):                   # geçersiz kılma (override)
        return super().yillik() + self.prim

print(Calisan('Ali', 1000).tanit())
print(Yonetici('Elif', 1000, 5000).tanit())   # tanit() taban sınıfta, yillik() alt sınıfta
# çıktı: Ali: 12000 TL
# çıktı: Elif: 17000 TL

Örnekteki ince nokta şudur: tanit() yalnızca taban sınıfta tanımlı ve içinde self.yillik() çağrılıyor. self bir Yonetici olduğu için arama alt sınıftan başlar ve primli sürüm çalışır. Belgeler bunu C++ diliyle anlatır: Python'da bütün metotlar fiilen "virtual"dır. Taban sınıftaki bir metot, aynı nesnenin başka bir metodunu çağırdığında alt sınıfın geçersiz kıldığı sürüm devreye girebilir. Bu, şablon metot gibi kalıpları mümkün kılar ama taban sınıfı değiştirirken alt sınıfları da düşünmenizi gerektirir.

Alt sınıfta __init__ yazdığınızda taban sınıfın __init__'i otomatik çalışmaz. Veri modeli belgesi bu durumda alt sınıfın taban sınıfı açıkça çağırması gerektiğini söyler: super().__init__(...). Bu çağrı unutulursa taban sınıfın kurduğu öznitelikler hiç oluşmaz; aşağıdaki "sık hatalar" bölümünde bunun nasıl göründüğünü göreceksiniz.

isinstance() ve issubclass()

isinstance(nesne, Sinif), nesnenin o sınıftan ya da onun doğrudan veya dolaylı bir alt sınıfından olup olmadığını döndürür. issubclass(A, B) aynı soruyu sınıflar için sorar ve bir sınıfı kendi alt sınıfı sayar. İkinci argüman olarak bir demet ya da Python 3.10'dan beri int | float gibi bir birleşim tipi de verilebilir:

python
class Hayvan: pass
class Kedi(Hayvan): pass

k = Kedi()
print(isinstance(k, Kedi), isinstance(k, Hayvan), isinstance(k, object))
print(issubclass(Kedi, Hayvan), issubclass(Hayvan, Kedi), issubclass(Kedi, Kedi))
print(issubclass(bool, int), issubclass(float, int))
print(isinstance(3.5, (int, float)), isinstance('3', int | float))
print(type(k) is Hayvan)
print(Kedi.__bases__, Kedi.__base__.__name__)
# çıktı: True True True
# çıktı: True False True
# çıktı: True False
# çıktı: True False
# çıktı: False
# çıktı: (<class '__main__.Hayvan'>,) Hayvan

bool'un int'ten türediğine dikkat edin; bu yüzden isinstance(True, int) de True döner. type(k) is Hayvan ise yalnızca tam tip eşleşmesine bakar ve alt sınıfları dışarıda bırakır. Genel kural: tip denetimi gerekiyorsa çoğu durumda isinstance() doğru araçtır, çünkü alt sınıfların taban sınıf yerine kullanılabilmesine izin verir. __bases__ sınıfın doğrudan tabanlarını, __base__ ise birincil tabanı gösterir.

super() gerçekte ne yapar?

Yaygın bir yanlış anlama, super()'ın "üst sınıfı" döndürdüğüdür. Oysa belgelere göre super(), çağrıları bir ebeveyn ya da kardeş sınıfa yönlendiren bir vekil (proxy) nesnedir. Arama, nesnenin MRO listesinde, super()'ın yazıldığı sınıftan sonraki sınıftan başlar. Tekli kalıtımda bu sonraki sınıf her zaman taban sınıftır, o yüzden fark görünmez. Çoklu kalıtımda ise sonraki sınıf, yazdığınız sınıfın hiç tanımadığı bir kardeş sınıf olabilir:

python
class Taban:
    def kaydet(self):
        print('Taban.kaydet')

class Gunluk(Taban):
    def kaydet(self):
        print('Gunluk.kaydet')
        super().kaydet()

class Onbellek(Taban):
    def kaydet(self):
        print('Onbellek.kaydet')
        super().kaydet()

class Servis(Gunluk, Onbellek):
    def kaydet(self):
        print('Servis.kaydet')
        super().kaydet()

Servis().kaydet()
print([c.__name__ for c in Servis.__mro__])
# çıktı: Servis.kaydet
# çıktı: Gunluk.kaydet
# çıktı: Onbellek.kaydet
# çıktı: Taban.kaydet
# çıktı: ['Servis', 'Gunluk', 'Onbellek', 'Taban', 'object']

Gunluk sınıfının tabanı Taban olduğu hâlde, içindeki super().kaydet() çağrısı Onbellek'e gitti. Çünkü nesne bir Servis ve onun MRO'sunda Gunluk'tan sonra Onbellek geliyor. Her sınıf super()'ı çağırdığı için zincir bütün sınıfları birer kez dolaştı ve Taban.kaydet yalnızca bir kez çalıştı. Belgeler bu kullanıma "işbirlikçi çoklu kalıtım" (cooperative multiple inheritance) adını verir ve iyi tasarım için aynı metodun her sınıfta aynı imzayla yazılmasını önerir; çünkü çağrı sırası çalışma anında, hiyerarşiye göre belirlenir.

Argümansız super() yalnızca sınıf gövdesi içindeki metotlarda çalışır; derleyici gerekli sınıf ve örnek bilgisini kendisi doldurur. İç içe fonksiyonlarda, hatta örtük olarak iç fonksiyon oluşturan üreteç ifadelerinde beklendiği gibi çalışmaz. İki argümanlı biçim super(Sinif, nesne) ise her yerde kullanılabilir ve aramayı MRO'da Sinif'tan sonrasından başlatır:

python
class A:
    def selam(self):
        return 'A'

class B(A):
    def selam(self):
        return 'B + ' + A.selam(self)        # taban sınıfı adıyla çağırmak

class C(B):
    def selam(self):
        return 'C + ' + super().selam()

print(C().selam())
print(super(B, C()).selam())                # MRO'da B'den sonrakini ara
# çıktı: C + B + A
# çıktı: A

Taban sınıfı adıyla çağırmak (A.selam(self)) da geçerli bir yöntemdir ve eğitim belgesinde yer alır. Ancak bu biçim MRO'yu atlar; çoklu kalıtımda bir sınıfın iki kez çağrılmasına ya da bir kardeşin hiç çağrılmamasına yol açabilir. Python 3.14 ile super nesneleri kopyalanabilir ve pickle ile serileştirilebilir hâle geldi.

Çoklu kalıtım ve MRO nasıl hesaplanır?

Çoklu kalıtım ve MRO nasıl hesaplanır?

En basit hâliyle çoklu kalıtımdaki aramayı "önce derinlik, soldan sağa, aynı sınıfa iki kez bakmadan" diye düşünebilirsiniz. Gerçek kural biraz daha inceliklidir. Python 2.3'ten beri, Python 3 dahil, kullanılan C3 doğrusallaştırması üç özelliği korur: her sınıfın tanımında yazılan soldan sağa taban sırası (yerel öncelik), her sınıfın yalnızca bir kez yer alması ve monotonluk. Monotonluk şu demektir: bir sınıfın MRO'sunda X, Y'den önce geliyorsa, o sınıftan türetilen her sınıfta da X, Y'den önce gelir. Böylece yeni bir alt sınıf eklemek, ebeveynlerin öncelik sırasını bozmaz.

C3 kuralı kısaca şöyledir: bir sınıfın doğrusallaştırması, sınıfın kendisi artı ebeveynlerinin doğrusallaştırmalarının ve ebeveyn listesinin birleştirilmesidir (merge). Birleştirmede ilk listenin başındaki sınıf, diğer listelerin kuyruğunda (baş dışındaki kısmında) yoksa sonuca eklenir; varsa bir sonraki listenin başına bakılır. MRO belgesindeki örnek hiyerarşiyi çalıştırdığımızda sonuç belgedekiyle aynıdır:

python
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(' '.join(k.__name__ for k in A.mro()))
print(' '.join(k.__name__ for k in B.mro()))
print(' '.join(k.__name__ for k in C.mro()))
# çıktı: A B C D E F object
# çıktı: B D E object
# çıktı: C D F object

Hesabı B için adım adım yapalım. D ve E yalnızca object'ten türediği için doğrusallaştırmaları D object ve E object'tir. B(D, E) için birleştirilecek listeler [D object], [E object] ve ebeveyn listesi [D E]'dir. İlk baş D, hiçbir listenin kuyruğunda yok, sonuca eklenir. Sıradaki baş object, ama [E object] listesinin kuyruğunda olduğu için şimdilik atlanır ve bir sonraki listenin başı E alınır. En sonda object eklenir: B D E object. A için de aynı yöntem, B ve C'nin doğrusallaştırmalarını birleştirerek A B C D E F object sırasını verir.

Sinif.__mro__ özniteliği bu sırayı bir demet olarak, Sinif.mro() metodu ise liste olarak verir. getattr() ve super() aramalarında kullanılan sıra budur. Bir hiyerarşide hangi metodun çalışacağından emin değilseniz tahmin etmek yerine __mro__'yu yazdırmak en hızlı yoldur.

Tutarsız hiyerarşi: MRO oluşturulamıyor hatası

Her hiyerarşi doğrusallaştırılamaz. A sınıfı X'i Y'den önce, B sınıfı ise Y'yi X'ten önce istiyorsa, ikisinden türeyen bir sınıf için iki kuralı birden sağlayan bir sıra yoktur. Python belirsiz bir sıra seçmek yerine sınıf oluşturmayı reddeder:

python
class X: pass
class Y: pass
class A(X, Y): pass
class B(Y, X): pass

try:
    class C(A, B): pass
except TypeError as e:
    print(e)
# çıktı: Cannot create a consistent method resolution order (MRO) for bases X, Y

Bu hatayı gördüğünüzde çözüm genellikle taban sınıfların sırasını tutarlı hâle getirmek ya da hiyerarşiyi sadeleştirmektir. Çoklu kalıtımda pratik bir kural, ortak davranış ekleyen küçük "mixin" sınıflarını solda, asıl taban sınıfı en sağda tutmaktır.

Yerleşik tiplerden türetmek

Resmî eğitimin vurguladığı bir fark: C++ ve Modula-3'ün aksine Python'da yerleşik tipler de taban sınıf olarak kullanılabilir. dict, list, str ya da int'ten türeyip davranışlarını genişletebilirsiniz. İki kancayı bilmek işinizi kolaylaştırır. Birincisi __missing__: veri modeli belgesine göre dict alt sınıflarında aranan anahtar bulunamadığında dict.__getitem__ bu metodu çağırır. İkincisi __new__: değiştirilemez tiplerde değer nesne oluşturulurken belirlenir, __init__ çalıştığında artık çok geçtir. Belgeler __new__'in esas olarak int, str ve tuple gibi değiştirilemez tiplerin alt sınıflarında örnek oluşturmayı özelleştirmek için var olduğunu söyler:

python
class Sayaclar(dict):
    def __missing__(self, anahtar):     # dict alt sınıflarında eksik anahtar kancası
        return 0

s = Sayaclar(elma=2)
print(s['elma'], s['armut'], s)

class Etiket(str):
    def __new__(cls, metin):            # değiştirilemez tip: __init__ değil __new__
        return super().__new__(cls, metin.strip().upper())

e = Etiket('  python  ')
print(e, isinstance(e, str), e.lower(), type(e.lower()).__name__)
# çıktı: 2 0 {'elma': 2}
# çıktı: PYTHON True python str

İki ayrıntıya dikkat edin. s['armut'] sıfır döndürdü ama sözlüğe yeni anahtar eklenmedi; __missing__ yalnızca bir değer üretir. Ayrıca e.lower() sonucu artık Etiket değil düz str: yerleşik tiplerin metotları genellikle taban tipten yeni nesne döndürür. Sözlük için hazır ve daha zengin bir çözüm isterseniz collections.defaultdict ve Counter zaten vardır (collections yazısı).

Soyut taban sınıflar: abc modülü

Bazen taban sınıf yalnızca bir sözleşme tanımlar: "alt sınıflar şu metodu mutlaka yazmalı". abc modülündeki ABC sınıfından türeyip metotları @abstractmethod ile işaretlediğinizde, soyut metotlarının hepsini geçersiz kılmayan bir sınıftan örnek oluşturulamaz. Hata, metot çağrıldığında değil, nesne oluşturulmaya çalışıldığında ortaya çıkar:

python
from abc import ABC, abstractmethod

class Sekil(ABC):
    @abstractmethod
    def alan(self):
        ...

    def tanit(self):
        return f'{type(self).__name__} alanı: {self.alan()}'

class Kare(Sekil):
    def __init__(self, kenar):
        self.kenar = kenar

    def alan(self):
        return self.kenar ** 2

class Eksik(Sekil):
    pass

print(Kare(3).tanit())
for sinif in (Sekil, Eksik):
    try:
        sinif()
    except TypeError as e:
        print(e)
# çıktı: Kare alanı: 9
# çıktı: Can't instantiate abstract class Sekil without an implementation for abstract method 'alan'
# çıktı: Can't instantiate abstract class Eksik without an implementation for abstract method 'alan'

@abstractmethod başka betimleyicilerle (örneğin @property) birlikte kullanılacaksa belgeler onu en içteki dekoratör olarak yazmanızı ister. Soyut metotların gövdesi de super() ile çağrılabilir, yani taban sınıfa ortak bir varsayılan davranış koyabilirsiniz.

__init_subclass__ ile alt sınıfları yakalamak

Bir sınıftan her türetme yapıldığında Python taban sınıfın __init_subclass__ metodunu çağırır; cls parametresi yeni oluşan alt sınıftır. Bu metot otomatik olarak sınıf metoduna çevrilir. Sınıf satırında verilen anahtar kelime argümanları da buraya iletilir. Eklenti kayıt sistemi gibi işler için metasınıf yazmaktan çok daha basit bir yoldur:

python
class Eklenti:
    kayit = {}

    def __init_subclass__(cls, /, anahtar, **kwargs):
        super().__init_subclass__(**kwargs)
        Eklenti.kayit[anahtar] = cls

class CsvOkuyucu(Eklenti, anahtar='csv'):
    pass

class JsonOkuyucu(Eklenti, anahtar='json'):
    pass

print({k: v.__name__ for k, v in Eklenti.kayit.items()})
# çıktı: {'csv': 'CsvOkuyucu', 'json': 'JsonOkuyucu'}

Belgelerdeki öneriye uyarak ihtiyaç duyulan argüman (anahtar) alındı, geri kalanlar super().__init_subclass__(**kwargs) ile zincirin devamına aktarıldı. Bu, başka sınıfların da __init_subclass__ kullandığı çoklu kalıtım durumlarında uyumu korur.

Sık yapılan hatalar

Sık yapılan hatalar: super().__init__ çağrısını unutmak, super() ile taban sınıf adını karıştırmak, İmzaları uyumsuz bırakmak, Kalıtımı her şey için kullanmak
python
class Arac:
    def __init__(self, plaka):
        self.plaka = plaka

class Taksi(Arac):
    def __init__(self, plaka, durak):
        self.durak = durak              # super().__init__ unutuldu

t = Taksi('34 ABC 34', 'Kadıköy')
try:
    print(t.plaka)
except AttributeError as e:
    print(e)
# çıktı: 'Taksi' object has no attribute 'plaka'
  • super().__init__ çağrısını unutmak: Yukarıdaki örnekte plaka hiç atanmadığı için AttributeError oluştu. Alt sınıfta __init__ yazıyorsanız taban sınıfı da başlatın.
  • super() ile taban sınıf adını karıştırmak: Aynı hiyerarşide bazı sınıflar super(), bazıları Taban.metot(self) kullanırsa çoklu kalıtımda bazı metotlar iki kez, bazıları hiç çalışmayabilir. Bir yöntem seçin ve tutarlı kullanın.
  • İmzaları uyumsuz bırakmak: İşbirlikçi zincirde bir sınıf fazladan argüman isterse sıradaki sınıf onu tanımaz. Fazla argümanları **kwargs ile toplayıp zincire aktarmak yaygın çözümdür (*args ve **kwargs yazısı).
  • Kalıtımı her şey için kullanmak: "bir ... türüdür" ilişkisi yoksa (örneğin bir Siparis, bir list türü değildir) kalıtım yerine nesneyi bir öznitelikte tutmak, yani bileşim (composition), çoğu zaman daha esnektir.
  • type() ile tip denetimi: type(x) == Taban alt sınıfları reddeder; isinstance(x, Taban) kullanın.

Sık Sorulan Sorular

Python çoklu kalıtımı destekliyor mu?

Evet. class C(A, B): şeklinde istediğiniz kadar taban sınıf verebilirsiniz. Metot arama sırası C3 ile hesaplanan MRO'dur ve C.__mro__ ile görülebilir. Bazı durumlarda ise çoklu kalıtıma izin verilmez; örneğin veri modeli belgesine göre birden fazla taban sınıf __slots__ ile öznitelik düzeni tanımlıyorsa TypeError oluşur.

super() hangi sınıfı çağırır?

super() belirli bir sınıfı değil, nesnenin MRO'sunda bulunduğunuz sınıftan sonraki sınıfı hedefler. Tekli kalıtımda bu taban sınıftır; çoklu kalıtımda bir kardeş sınıf olabilir.

Bütün sınıflar object'ten mi türer?

Evet. Taban sınıf yazmasanız da her sınıfın MRO'sunun sonunda object vardır. Bu nedenle çoklu kalıtımda her zaman en az bir elmas yapısı oluşur ve MRO'nun her sınıfı bir kez ziyaret etmesi önem kazanır.

Enum ya da dataclass'tan kalıtım alabilir miyim?

Dataclass'lardan türetmek mümkündür ve alanlar birleştirilir; ayrıntı dataclasses yazısında. Üyesi olan bir Enum'dan ise türetilemez; Enum yazısında bunun gerekçesini ve üyesiz Enum ile davranış paylaşımını anlatıyoruz.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Python öğrenme rehberi · Class ve nesne · Özel metotlar · dataclasses · Enum kullanımı