Neden önemli?
Kalıtım ve polimorfizm dersinde tek bir üst sınıftan türetmeyi, super() ile kurucuyu genişletmeyi ve mixin fikrine kısa bir girişi gördün. Gerçek kod tabanlarında ise sınıfların çoğu birden fazla kaynaktan yetenek alır: Anadolu Market'in Siparis sınıfı hem bir Kayit'tır, hem JSON'a çevrilebilir, hem işlemlerini loglar, hem de tutarına göre sıralanabilir. Django'nun sınıf tabanlı görünümleri, scikit-learn'ün tahmin edicileri, socketserver.ThreadingMixIn hep bu kalıpla yazılmıştır.
Çoklu kalıtım (multiple inheritance) güçlüdür ama bir soruyu doğurur: iki üst sınıfta aynı adlı metot varsa hangisi çalışır? Python bu soruyu MRO (Method Resolution Order, metot çözümleme sırası) ile kesin olarak yanıtlar. Bu dersin sonunda bir sınıfın MRO'sunu kâğıt üzerinde hesaplayabilecek, super()'nün aslında "üst sınıf" değil "MRO'da sıradaki sınıf" demek olduğunu bilecek ve temiz mixin'ler tasarlayabileceksin. Bunlar PCAP 4.5 konusunun tamamıdır.
Tekli ve çoklu kalıtım sözdizimi
Parantez içine birden fazla sınıf yazarsın. Arama soldan sağa yapılır: önce sınıfın kendisi, sonra ilk yazılan üst sınıf ve onun ataları (ortak ata hariç), sonra ikinci üst sınıf...
class Kargolanabilir:
def teslimat(self):
return "Kargo ile 2 gün"
def etiket(self):
return "KARGO"
class Indirilebilir:
def teslimat(self):
return "Anında indirme"
def boyut(self):
return "120 MB"
class EKitapPaketi(Indirilebilir, Kargolanabilir): # basılı kitap + PDF
pass
p = EKitapPaketi()
print(p.teslimat()) # soldaki kazanır
print(p.etiket(), p.boyut()) # ikisinin yetenekleri de var
print([k.__name__ for k in EKitapPaketi.__mro__])
__mro__ sınıfın arama sırasını demet olarak tutar; EKitapPaketi.mro() aynı bilgiyi liste olarak verir. Her sınıfın zinciri object ile biter.
Elmas problemi
İki üst sınıf ortak bir atadan türüyorsa kalıtım grafiği elmas biçimini alır. Eski dillerde ortak atanın metodu iki kez mi çalışacak, hangi yoldan mı gelinecek belirsizdi. Python'da ortak ata MRO'da bir kez ve tüm alt sınıflarından sonra yer alır.
class Hesap:
def aciklama(self):
return "Hesap"
class Vadeli(Hesap):
def aciklama(self):
return "Vadeli"
class Dovizli(Hesap):
def aciklama(self):
return "Dövizli"
class DovizliVadeli(Vadeli, Dovizli):
pass
print([k.__name__ for k in DovizliVadeli.__mro__])
print(DovizliVadeli().aciklama())
print(Hesap in DovizliVadeli.__mro__, DovizliVadeli.__mro__.count(Hesap))
C3 doğrusallaştırma: MRO nasıl hesaplanır?
Python 2.3'ten beri MRO, C3 doğrusallaştırma (C3 linearization) algoritmasıyla bulunur. Üç kuralı garanti eder:
- Çocuk önce: bir sınıf her zaman üst sınıflarından önce gelir.
- Yerel sıra korunur:
class D(B, C)yazdıysan B, C'den önce gelir. - Tutarlılık (monotonluk): üst sınıfların kendi MRO'larındaki sıra bozulmaz.
Hesap şöyle yapılır: L[D] = D + birleştir(L[B], L[C], [B, C]). Birleştirme adımında listelerin başlarına bakarsın; başka hiçbir listenin kuyruğunda (ilk eleman dışındaki kısmında) geçmeyen ilk baş adayı seçilir, tüm listelerden çıkarılır, tekrar edilir. Aday kalmazsa sıralama imkânsızdır ve Python sınıfı oluşturmayı reddeder:
class A: pass
class B(A): pass
try:
class C(A, B): # A, B'den önce olsun diyorsun ama B, A'nın çocuğu!
pass
except TypeError as e:
print("TypeError:", e)
class C(B, A): # tutarlı sıra
pass
print([k.__name__ for k in C.mro()])
| Tanım | MRO | Not |
|---|---|---|
class B(A) | B, A, object | Tekli kalıtım: düz zincir |
class D(B, C); B(A), C(A) | D, B, C, A, object | Elmas: A en sonda, bir kez |
class E(C, B); B(A), C(A) | E, C, B, A, object | Yerel sıra değişti, MRO da değişti |
class X(A, B); B(A) | TypeError | Çocuk-önce kuralıyla çelişir |
super() üst sınıfı değil, MRO'da sıradakini çağırır
Bu cümle çoklu kalıtımın anahtarıdır. super().metot(), nesnenin MRO'sunda, içinde bulunduğun sınıftan sonra gelen sınıfın metodunu çağırır. Aynı Vadeli sınıfındaki super(), tek başına kullanıldığında Hesap'a, DovizliVadeli nesnesinde ise Dovizli'ye gider.
class Hesap:
def kaydet(self):
print("Hesap: veritabanına yazıldı")
class Vadeli(Hesap):
def kaydet(self):
print("Vadeli: vade tablosu güncellendi")
super().kaydet()
class Dovizli(Hesap):
def kaydet(self):
print("Dövizli: kur kaydı eklendi")
super().kaydet()
class DovizliVadeli(Vadeli, Dovizli):
def kaydet(self):
print("DövizliVadeli: başlıyor")
super().kaydet()
DovizliVadeli().kaydet()
print("---")
Vadeli().kaydet()
Hesap'ın kaydet'i bir kez çalıştı ve her sınıf yalnız kendi işini yaptı. Bu düzene işbirlikçi çoklu kalıtım (cooperative multiple inheritance) denir. Şartı basittir: zincirdeki her sınıf super() çağırır ve kimse üst sınıfı adıyla (Hesap.kaydet(self)) çağırmaz. Adıyla çağırırsan zincir kopar ya da ortak ata iki kez çalışır.
Kurucularda **kwargs ile işbirliği
Farklı sınıfların kurucuları farklı parametreler ister. Çözüm: her sınıf kendi parametresini adıyla alır, kalanını **kwargs ile super().__init__'e iletir. Zincirin sonunda object'e boş kwargs ulaşmalıdır.
class Kayit:
def __init__(self, *, kimlik, **kwargs):
super().__init__(**kwargs)
self.kimlik = kimlik
class ZamanDamgasi:
def __init__(self, *, tarih="2026-01-15", **kwargs):
super().__init__(**kwargs)
self.tarih = tarih
class Sahipli:
def __init__(self, *, sahip, **kwargs):
super().__init__(**kwargs)
self.sahip = sahip
class Fatura(ZamanDamgasi, Sahipli, Kayit):
def __init__(self, *, tutar, **kwargs):
super().__init__(**kwargs)
self.tutar = tutar
f = Fatura(kimlik="F-1001", sahip="Anadolu Market", tutar=1250.0)
print(vars(f))
print([k.__name__ for k in Fatura.__mro__])
*, sonrasındaki argümanların adla verilmesini zorunlu kılar. İşbirlikçi kurucularda konuma bağlı argüman sırası MRO'ya göre değişeceği için adla geçmek tek güvenli yoldur.Mixin tasarımı
Mixin, tek başına örneklenmesi amaçlanmayan, tek bir yeteneği (JSON'a çevirme, loglama, karşılaştırma) başka sınıflara "karıştırmak" için yazılmış küçük bir sınıftır. İyi bir mixin:
- Tek bir iş yapar ve adı
...Mixinile biter. - Kendi
__init__'ini mümkünse tanımlamaz; tanımlarsa işbirlikçi yazar. - Ev sahibi sınıftan beklediğini (ör.
_anahtar()metodu) belgelendirir. - Sınıf tanımında solda, asıl taban sınıf en sağda yazılır:
class Siparis(JsonMixin, LogMixin, Kayit).
import json
from functools import total_ordering
class JsonMixin:
def to_json(self):
return json.dumps(vars(self), ensure_ascii=False, sort_keys=True)
class LogMixin:
def log(self, mesaj):
self.__dict__.setdefault("_kayitlar", [])
self._kayitlar.append(f"[{type(self).__name__}] {mesaj}")
@total_ordering
class KarsilastirmaMixin:
# Ev sahibi _anahtar() tanımlamalı
def __eq__(self, other):
if not isinstance(other, KarsilastirmaMixin):
return NotImplemented
return self._anahtar() == other._anahtar()
def __lt__(self, other):
if not isinstance(other, KarsilastirmaMixin):
return NotImplemented
return self._anahtar() < other._anahtar()
class Urun(JsonMixin, KarsilastirmaMixin):
def __init__(self, ad, fiyat):
self.ad, self.fiyat = ad, fiyat
def _anahtar(self):
return self.fiyat
def __repr__(self):
return f"Urun({self.ad!r}, {self.fiyat})"
urunler = [Urun("Termos", 349.9), Urun("Kalem", 25.0), Urun("Çanta", 899.0)]
print(sorted(urunler))
print(urunler[0] > urunler[1], max(urunler).ad)
print(urunler[1].to_json())
functools.total_ordering, __eq__ ve __lt__'den diğer dört karşılaştırmayı türetir. to_json ise vars(self) ile nesnenin sözlüğünü kullanır; kapsülleme dersindeki iç gözlem araçlarının pratik bir uygulaması.
Metot ezme, __str__ ve __repr__
Alt sınıf bir metodu yeniden tanımlayınca (ezme, overriding) MRO'da önce o bulunur. __repr__'ı taban sınıfta bir kez, type(self).__name__ kullanarak yazarsan tüm alt sınıflar doğru adı kendiliğinden gösterir. __str__ son kullanıcıya, __repr__ geliştiriciye yöneliktir; __str__ yoksa print __repr__'a düşer.
Polimorfizm ve duck typing
Polimorfizmin Python'daki en yaygın biçimi duck typing'dir: "Ördek gibi yürüyor ve ötüyorsa ördektir." Bir fonksiyon nesnenin türünü değil, yapabildiklerini önemser. Ortak üst sınıf zorunlu değildir.
class KargoUcreti:
def __init__(self, desi): self.desi = desi
def ucret(self): return 30 + self.desi * 4.5
class Sigorta:
def __init__(self, deger): self.deger = deger
def ucret(self): return round(self.deger * 0.01, 2)
class HediyePaketi:
def ucret(self): return 25
class Not: # ucret() yok
metin = "Kapıya bırakın"
kalemler = [KargoUcreti(3), Sigorta(2000), HediyePaketi(), Not()]
toplam = 0
for k in kalemler:
try:
toplam += k.ucret()
except AttributeError:
print("Ücretsiz kalem atlandı:", type(k).__name__)
print("Toplam ek ücret:", toplam)
try/except AttributeError yaklaşımına EAFP ("izin isteme, af dile") denir ve Python'da hasattr ile önceden sormaktan (LBYL) genellikle daha deyimseldir.
Ne zaman kompozisyon?
Kalıtım "bir ... dır" (is-a), kompozisyon "bir ... sahiptir" (has-a) ilişkisidir. Çoklu kalıtım cazip gelse de şu durumlarda kompozisyonu seç:
| Durum | Tercih | Neden |
|---|---|---|
| Küçük, durumsuz yetenek (JSON, log, karşılaştırma) | Mixin | Kod tekrarı yok, MRO basit |
| Davranış çalışma anında değişecek (SMS mi e-posta mı) | Kompozisyon | Nesneyi değiştirmek sınıf değiştirmekten kolay |
| Parça kendi durumunu taşıyor (adres, sepet) | Kompozisyon | Yaşam döngüleri ayrı |
| Aynı yetenekten iki tane gerekiyor (iki adres) | Kompozisyon | Bir sınıftan iki kez kalıtım alınamaz |
| Çerçeve (Django, unittest) sınıf bekliyor | Kalıtım + mixin | Çerçevenin sözleşmesi |
__init__'inde super() çağırmıyor?" İşbirliğini bozan tek bir sınıf, zincirin geri kalanını sessizce devre dışı bırakır ve hata ancak belirli bir sınıf bileşiminde ortaya çıkar. Ekipler bu yüzden mixin'leri durumsuz tutar, __mro__'yu testte doğrular ve kalıtım derinliğini sınırlar.super() perde arkasında
Argümansız super(), derleyicinin eklediği __class__ hücresi ve metodun ilk argümanı (self) ile super(SuAnkiSinif, self)'e dönüşür. Bu nesne type(self).__mro__'yu alır, SuAnkiSinif'ı bulur ve aramaya ondan sonraki sınıftan başlar. Bu yüzden aynı satır, farklı nesnelerde farklı sınıflara gidebilir. super(Vadeli, self).kaydet() biçimi hâlâ geçerlidir ama yalnız bir sınıfı bilerek atlamak istediğinde kullanılır.
Mixin ile ABC birlikte
Mixin'in ev sahibinden beklediği metodu belgelemek yetmezse abc.abstractmethod ile zorunlu kılabilirsin; ya da tip denetleyicileri için typing.Protocol tanımlarsın (İleri OOP dersinde). isinstance(x, JsonMixin) sorgusu çalışır ama duck typing ruhuna aykırıdır; yeteneği kullanmayı dene, yoksa hatayı yakala.
PCAP için hızlı kontrol listesi
issubclass(D, A)dolaylı atalar için de True'dur;D.__bases__yalnız doğrudan üstleri gösterir.- Aynı adlı nitelik hem sınıf değişkeni hem metot olarak farklı üstlerde varsa MRO'da önce gelen kazanır; türü önemli değildir.
- Tutarsız MRO, sınıf tanımlanırken TypeError verir; nesne oluşturulurken değil.
- Tüm sınıflar örtük olarak
object'ten türer;class A:ileclass A(object):Python 3'te aynıdır.