Kalıtım ve metot ezme

Kalıtımla (inheritance) bir sınıf, başka bir sınıfın metotlarını yeniden yazmadan devralır ve gerekenleri kendine göre değiştirir. Örneğin Developer sınıfı, Employee sınıfındaki describe() metodunu hazır bulur. Yanlış kullanıldığında ise sınıflar birbirine sıkı bağlanır ve sınıf hiyerarşisi (üst ve alt sınıfların oluşturduğu ağaç) kırılganlaşır: üst sınıftaki küçük bir değişiklik alt sınıfları bozabilir. Bu hafta kalıtımı is-a (“bir … türüdür”) ilişkisinden yola çıkarak öğrenecek ve geçen haftaki kompozisyonla karşılaştıracağız.

Bu bölümün kapsamı

NoteSınavda sorulur
  • Alt sınıfın üst sınıftan davranış devralması ve tür ilişkisi
  • Kalıtım kararını yerine geçme sorusuyla sınama
  • Bir metodu override ederek davranışı özelleştirme
  • super() çağrısının normal metotta da kullanılması
  • is-a ile has-a ayrımı; ortak alanın kalıtım gerekçesi olmaması
  • Derin kalıtım zincirinin kırılganlığı
  • Override edilen ve devralınan metotların çağrıda hangi sınıftan geldiğini izleme
  • Çoklu kalıtım ve MRO terimleri
  • Çoklu kalıtım tasarımı ve mixin kalıpları
  • MRO algoritmasının (C3) ayrıntısı
  • Soyut temel sınıf (ABC) yazımı

Bu başlıklar konunun devamıdır; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.

Bu iki sınıf arasındaki ilişki is-a mı, has-a mı?

“Kare bir dikdörtgendir” cümlesi matematikte doğrudur. Yine de Square sınıfını Rectangle sınıfından türetmek programda sorun çıkarabilir. Bu bölümde “Bir X, bir Y’dir” cümlesinin ne zaman kalıtıma yol gösterdiğini, ne zaman yanılttığını göreceğiz.

İlk kalıtım örneği

Developer sınıfının gövdesinde hiçbir metot yok, ama describe() metodunu Employee sınıfından devralır. Bir Developer nesnesi aynı zamanda bir Employee sayılır; bu nedenle iki isinstance() çağrısı da True döndürür.

is-a yeterli mi? Liskov yerine geçme ilkesi

Kalıtım için ilk sezgi:

Bir Developer, bir Employee’dir.

Ama daha belirleyici soru şu:

Bir Employee beklenen yerde Developer kullanıldığında program, üst sınıfın sözleşmesi bozulmadan çalışıyor mu?

Yazılım tasarımında bu soru Liskov yerine geçme ilkesi (Liskov Substitution Principle, LSP) adıyla bilinir. İlkenin biçimsel tanımını ezberlemeyeceğiz; alt türün, üst türden beklenen davranışı bozup bozmadığına bakacağız. Örneğin çalışanları listeleyen bir fonksiyon her nesnenin describe() metodunu çağırıyorsa, listeye bir Developer koyduğunuzda bu çağrı yine bir metin döndürmelidir.

Override: davranışı özelleştirmek

Alt sınıftaki send, üst sınıftaki aynı adlı metodu ezer (override): bir EmailNotification nesnesinde send çağrılınca alt sınıftaki sürüm çalışır. Override ederken çağıranın beklentisini bozmayın. Burada iki sürüm de aynı parametreyi alıyor ve bir metin döndürüyor.

Alt sınıfın üst sınıftan davranış devralması ve bir metodu override ederek özelleştirmesini gösteren diyagram.

Kalıtım ve override

super() yalnızca __init__ için değildir

Önce kurucudaki kullanımına bakalım:

super().__init__(name) üst sınıfın __init__ metodunu çalıştırır. Böylece self.name = name satırını alt sınıfta yeniden yazmanız gerekmez.

Alt sınıf kurucusundan super().__init__ çağrısıyla üst sınıf kurucusuna geçişi gösteren akış.

super() çağrı akışı

super() normal metotlarda da çalışır. Üst sınıfın sonucunu alıp üstüne bir şey eklemeye davranışı genişletmek denir:

Alt sınıf, üst sınıfın send metodunu silip baştan yazmaz. Onu çağırır ve dönen metnin sonuna [kanal=e-posta] ekler.

is-a ve has-a

İlişki Örnek Tipik araç
is-a Developer bir Employee’dir kalıtım
has-a Car bir Engine’e sahiptir kompozisyon

İki sınıfta aynı adlı bir nitelik ya da metot bulunması, birini ötekinden türetmek için gerekçe olmaz. Driver ve Car sınıflarının ikisinde de name bulunabilir; ama “sürücü bir arabadır” (Driver is a Car) cümlesi anlamsızdır.

Tanımanız yeterli: kırılgan hiyerarşi ve MRO

Kalıtım zinciri derinleştikçe, yani B sınıfı A’dan, C sınıfı B’den, D sınıfı C’den türetildikçe, A sınıfındaki küçük bir değişiklik bütün alt sınıflara yayılır. Bu derste iki-üç seviyeden derin hiyerarşi tasarlamayacağız.

Python bir sınıfın birden fazla üst sınıftan türetilmesine de izin verir. Buna çoklu kalıtım denir:

class C(A, B):
    pass

A ve B sınıflarında aynı adlı bir metot varsa Python önce hangisine bakar? Bu arama sırasına method resolution order (MRO) denir. Sırayı C.mro() ile görebilirsiniz. Çoklu kalıtım ve MRO alıştırmalarda ve sınavda sorulmaz. İnternette bu terimlerle karşılaştığınızda ne olduklarını bilmeniz yeter.

WarningKalıtım öğrendik diye her yerde kullanmayın

Bir tasarımda üst ve alt sınıf sayısı arttıkça tasarım iyileşmez. Kompozisyon çoğu durumda daha gevşek ve değiştirilebilir bir bağ kurar: Car bir Engine nesnesi tutuyorsa, motoru aynı metotlara sahip başka bir motor nesnesiyle değiştirmek için Car sınıfına dokunmanız gerekmez.

Alıştırma: Uygun ilişkiyi seç

Dog–Animal, Order–Customer, Laptop–Battery, Teacher–Course, AdminUser–User çiftlerinin her biri için is-a, has-a ya da “ikisi de değil” cevaplarından birini seçin. is-a seçtiğiniz her durumda ayrıca “üst tür beklenen yerde alt tür güvenle kullanılabilir mi?” sorusunu yanıtlayın.

Alıştırma: Override tamamla

return self.width * self.height
Note

Bir alt sınıf area() metodunu yazmazsa, o sınıfın nesnesinde area() çağrıldığında NotImplementedError çıkar. Böylece “bu metodu alt sınıf yazmalı” beklentisi koda açıkça yazılmış olur. Bu, öğretim için seçilmiş bir örnektir ve sınıfı teknik olarak soyut (nesnesi oluşturulamayan) yapmaz: Shape() nesnesi yine oluşturulabilir. NotImplemented ile NotImplementedError arasındaki farkı 12. haftada göreceğiz.

Alıştırma: Normal metotta super() kullan

Bir Logger.log(message) metodu "LOG: ..." döndürsün. TimestampLogger, log() metodunu override etsin; önce super().log(message) çağırıp sonra sonuca " [zaman damgalı]" eklesin. Üst metodu kopyalamadan davranışı genişletin.

Sıra sizde: Ödeme türleri

Bir Payment üst sınıfı ile CardPayment ve CashPayment alt sınıflarını tasarlayın. İkisinde de pay(amount) metodu olsun. En az bir alt sınıfta super() ile normal bir üst metodu genişletin.

Çalışır kod görevi: İki alt sınıftan birer nesneyi aynı listeye koyun ve bir döngüde her birinin pay() metodunu çağırın. isinstance(child, Parent) sonucunu assert ile doğrulayın. Son olarak yanlış kurulmuş bir kalıtım örneği yazın ve yerine geçme ilkesini (LSP) neden bozduğunu iki cümleyle açıklayın.

Sınav provası

Önce kendi cevabınızı seçin; sonra cevap anahtarında her şıkkın neden doğru veya yanlış olduğunu okuyun. Çeldiriciler uydurma değil, bu konuda gerçekten yapılan hatalardır. Sınav maddeleri de bu mantıkla yazılır.

Madde 1. Aşağıdaki program ne yazdırır?

class Employee:
    def __init__(self, name):
        self.name = name

    def describe(self):
        return f"Çalışan: {self.name}"

class Developer(Employee):
    pass

dev = Developer("Ada")
print(dev.describe())
print(isinstance(dev, Employee))
  • A. Geliştirici: Ada ve True
  • B. TypeError; pass gövdeli bir sınıf örneklenemez
  • C. AttributeError ve True
  • D. Çalışan: Ada ve True
  • E. Çalışan: Ada ve False

Madde 2. Kalıtım kararı verirken is-a sezgisinden daha güçlü olan soru nedir?

  • A. Alt sınıf, üst sınıfın __init__ metodunu kullanabiliyor mu
  • B. Alt sınıf üst sınıfla aynı dosyada mı
  • C. Alt sınıf, üst sınıfa yeni metotlar ekleyerek onu genişletiyor mu
  • D. İki sınıfta aynı adlı ortak nitelikler bulunuyor mu
  • E. Alt sınıf, üst sınıfın beklendiği her yerde sorunsuz çalışıyor mu

Madde 3. Bir Square sınıfını Rectangle sınıfından türetmek neden sorun çıkarabilir?

  • A. Sorun yoktur; matematiksel olarak kare bir dikdörtgendir
  • B. Square sınıfı Rectangle sınıfından daha az nitelik taşıdığı için AttributeError oluşur
  • C. Square, kenarları ayrı ayrı değişebilen dikdörtgen sözünü tutamaz
  • D. Python’da geometrik sınıflar türetilemez
  • E. Kalıtım yalnızca aynı sayıda parametre alan kurucularda geçerlidir

Madde 4. Aşağıdaki program ne yazdırır?

class Notification:
    def send(self, message):
        return f"Bildirim: {message}"

class EmailNotification(Notification):
    def send(self, message):
        return f"E-posta: {message}"

print(EmailNotification().send("Merhaba"))
  • A. Bildirim: Merhaba
  • B. E-posta: Merhaba
  • C. TypeError; aynı adlı iki metot tanımlanamaz
  • D. Önce Bildirim: Merhaba, sonra E-posta: Merhaba
  • E. None

Madde 5. Aşağıdaki program ne yazdırır?

class Notification:
    def send(self, message):
        return f"Gönderildi: {message}"

class EmailNotification(Notification):
    def send(self, message):
        base_result = super().send(message)
        return f"{base_result} [kanal=e-posta]"

print(EmailNotification().send("Toplantı 14.00"))
  • A. Gönderildi: Toplantı 14.00
  • B. RecursionError; super().send kendisini çağırır
  • C. TypeError; super() yalnızca __init__ içinde kullanılabilir
  • D. Toplantı 14.00 [kanal=e-posta]
  • E. Gönderildi: Toplantı 14.00 [kanal=e-posta]

Madde 6. Alt sınıf kurucusunda super().__init__(name) yazmanın gerekçesi nedir?

  • A. Üst sınıfın başlangıç kurulumunu tekrar yazmadan çalıştırmak
  • B. Alt nesnenin yanında üst sınıftan ayrı bir örnek oluşturmak
  • C. Python’un zorunlu kıldığı bir kuralı yerine getirmek
  • D. Alt sınıfın üst sınıf metotlarını override etmesini sağlamak
  • E. Alt sınıfın kendi niteliklerini otomatik oluşturmak

Madde 7. Driver ve Car sınıflarının ikisinde de name niteliği bulunması ne anlatır?

  • A. İki sınıfın tek sınıfta birleştirilmesi gerektiğini
  • B. Kalıtım için tek başına bir gerekçe olmadığını
  • C. Driver sınıfının Car sınıfından türetilebileceğini
  • D. İkisinin ortak bir üst sınıftan türetilmesi gerektiğini
  • E. Car sınıfının bir Driver niteliği taşıması gerektiğini

Madde 8. Derin kalıtım zincirleri neden kırılgandır?

  • A. Her seviye nesneye yeni bir kopya eklediği için bellek kullanımı artar
  • B. super() üçten fazla seviyede çalışmaz
  • C. Alt sınıflar üst sınıf metotlarını çağıramaz hâle gelir
  • D. Üst sınıftaki küçük bir değişiklik bütün alt sınıflara yayılır
  • E. Python üç seviyeden derin kalıtıma izin vermez

Madde 9. “Kalıtım öğrendik diye her yerde kullanmayın” uyarısının gerekçesi nedir?

  • A. Kompozisyon çoğu durumda daha gevşek ve değiştirilebilir bir bağ kurar
  • B. Kalıtım Python’da yavaş çalıştığı için
  • C. Kalıtım kullanılan sınıflar test edilemez
  • D. Python’da çoklu kalıtım bulunmadığı için
  • E. Kalıtım yalnızca standart kütüphane sınıflarında kullanılmalıdır

Madde 10. Aşağıdaki program ne yazdırır?

class Notification:
    def send(self, message):
        return f"Bildirim: {message}"

    def channel(self):
        return "genel"

class SmsNotification(Notification):
    def send(self, message):
        return f"SMS: {message}"

n = SmsNotification()
print(n.send("Ders 10.00"))
print(n.channel())
  • A. Önce Bildirim: Ders 10.00, sonra SMS: Ders 10.00, sonra genel
  • B. Önce Bildirim: Ders 10.00, sonra genel
  • C. Önce SMS: Ders 10.00, sonra genel
  • D. Önce SMS: Ders 10.00, sonra AttributeError
  • E. Önce SMS: Ders 10.00, sonra None

Madde 1. Doğru: D. Developer gövdesi boş olsa da __init__ ve describe üst sınıftan devralınır. Bir Developer nesnesi aynı zamanda bir Employee sayılır.

  • A yanlış: Developer metodu override etmemiştir; üst sınıfın metni kullanılır.
  • B yanlış: Gövdesi yalnızca pass olan sınıf geçerlidir; nesnesi oluşturulabilir.
  • C yanlış: describe üst sınıfta tanımlıdır ve bulunur.
  • E yanlış: isinstance() üst sınıfları da dikkate alır.

Madde 2. Doğru: E. Yerine geçme sorusu (Liskov ilkesi), üst sınıfın beklendiği yerde alt sınıf kullanıldığında sözleşmenin bozulup bozulmadığını sınar.

  • A yanlış: super().__init__() kalıtımla birlikte öğrenildiği için ölçüt gibi görünebilir. Ama kurucuyu kullanabilmek yerine geçme sorusunu cevaplamaz.
  • B yanlış: Yan yana yazılan sınıflar arasında bir bağ varmış gibi görünür. Ama sınıfların hangi dosyada durduğu tür ilişkisini değiştirmez.
  • C yanlış: Kalıtım “genişletme” diye anlatıldığı için yeni metot eklemek ölçüt gibi görünür. Ama metot eklemek, alt sınıfın üst türün yerine geçip geçemeyeceğini söylemez.
  • D yanlış: Ortak nitelikler iki sınıfın akraba olduğunu düşündürür. Ama iki sınıfta aynı nitelik bulunması, birini ötekinden türetmek için gerekçe olmaz.

Madde 3. Doğru: C. Rectangle bekleyen kod, genişliği ve yüksekliği bağımsız ayarlayabileceğini varsayar. Kareyle çalışırken bir kenarı değiştirmek ötekini de değiştirir ve kod şaşırtıcı sonuç alır. is-a cümlesi kurulabilse de yerine geçme bozulur.

  • A yanlış: Kare matematikte bir dikdörtgendir, ama programdaki Rectangle genişliğin ve yüksekliğin ayrı ayrı değişebileceğine söz verir. Sorun bu sözdedir.
  • B yanlış: Kare tek kenar uzunluğuyla tanımlandığı için bazı niteliklerin eksik kalacağı sanılabilir. Ama devralınan nitelikler kaybolmaz; sorun hata değil, beklenmedik davranıştır.
  • D yanlış: Sorun bir dil kısıtı gibi görünebilir. Böyle bir kısıt yoktur; sorun tasarımdadır.
  • E yanlış: Kare tek, dikdörtgen iki ölçüyle kurulduğu için kurucular uyuşmuyor gibi görünür. Ama alt sınıfın kurucusu üst sınıfınkinden farklı sayıda parametre alabilir.

Madde 4. Doğru: B. Alt sınıftaki send üst sınıftaki aynı adlı metodu ezer; Python metodu önce nesnenin kendi sınıfında arar.

  • A yanlış: Üst sınıf metodu ancak alt sınıfta aynı ad tanımlı değilse çalışır.
  • C yanlış: Farklı sınıflarda aynı ad kullanılabilir; override budur.
  • D yanlış: Override ile üst metot kendiliğinden çağrılmaz.
  • E yanlış: Metot bir metin döndürür.

Madde 5. Doğru: E. super().send(message) üst sınıfın send metodunu çalıştırır; alt sınıf dönen metne kendi ekini ekler.

  • A yanlış: Alt sınıf sonuca kendi ekini de katar.
  • B yanlış: super() bir üstteki sınıfın metodunu çağırır; özyineleme oluşmaz.
  • C yanlış: super() her metotta kullanılabilir; bu kod da bunun örneğidir.
  • D yanlış: Üst sınıfın metni de sonuca girer.

Madde 6. Doğru: A. Üst sınıfın __init__ içindeki doğrulama ve atamalar kopyalanmadan kullanılır; kural değişince tek yer güncellenir.

  • B yanlış: super() bir üst sınıf nesnesi veriyormuş gibi okunabilir. Ama kalıtımda ikinci bir nesne oluşmaz; aynı örnek başlatılır.
  • C yanlış: Çoğu örnekte yazıldığı için Python’un bunu zorunlu tuttuğu sanılabilir. Zorunlu değildir; yazılmazsa üst kurulum hiç çalışmaz.
  • D yanlış: super() üst sınıfla bağ kurduğu için override’ı da o sağlıyormuş gibi görünür. Override için aynı adla metot yazmak yeter; super() gerekmez.
  • E yanlış: super() çağrısından sonra nesne hazır göründüğü için alt sınıfın nitelikleri de kurulmuş sanılabilir. Ama alt sınıfın kendi nitelikleri ayrıca yazılır.

Madde 7. Doğru: B. Ortak bir alan bulunması is-a ilişkisi kurmaz. Driver is a Car cümlesi anlamsızdır; ortak nitelik yalnız bir rastlantıdır.

  • A yanlış: Aynı alanı iki kez yazmamak için tek sınıf kısa yol gibi görünür. Ama sürücü ile arabanın sorumlulukları ayrıdır.
  • C yanlış: Ortak nitelik, bir sınıfın ötekinden türeyebileceğini düşündürür. Ama sürücü bir araba değildir.
  • D yanlış: Ortak alanı tek yerde toplamak tekrarı azaltıyor gibi görünür. Ama yalnızca aynı adlı bir nitelik taşımaları, ortak bir üst tür gerektirmez.
  • E yanlış: Arabanın bir sürücüsü olabileceği için bu seçenek doğru bir ilişkiyi anlatıyor gibi görünür. Gerekebilir ama bunu name alanının ortaklığı söylemez.

Madde 8. Doğru: D. Bağ sıkıdır; değişikliğin nereleri etkilediği izlenemez hâle gelir. Bu yüzden bu derste iki-üç seviyeden derin hiyerarşi tasarlanmaz ve kompozisyon tercih edilir.

  • A yanlış: Her seviye ayrı bir nesne kuruyormuş gibi düşünülürse bellek sorunu akla gelir. Ama kalıtımda tek bir nesne vardır; sorun bellekte değil bakımdadır: bir değişikliğin nereleri etkilediğini izlemek zorlaşır.
  • B yanlış: Zincir uzadıkça super() çağrılarının kopacağı sanılabilir. Ama her sınıf super() çağırdıkça çağrı zincir boyunca ilerler.
  • C yanlış: Uzun zincirde en üstteki sınıf uzakta kalmış gibi görünür. Ama alt sınıflar üst sınıf metotlarını çağırabilir; sorun, alt sınıfların üst sınıfa bu kadar sıkı bağlı olmasıdır.
  • E yanlış: Derste iki-üç seviye sınırı konduğu için bunun bir dil kuralı olduğu sanılabilir. Teknik bir sınır yoktur; sınır tasarım kararıdır.

Madde 9. Doğru: A. İyi tasarımda amaç çok sayıda üst ve alt sınıf kurmak değil, parçaları kolayca değiştirilebilir tutmaktır.

  • B yanlış: Gerekçe başarım değildir.
  • C yanlış: Test edilebilir; sorun bağın sıkılığıdır.
  • D yanlış: Python çoklu kalıtımı destekler; yalnızca bu dersin kapsamı dışında bırakıldı.
  • E yanlış: Kendi sınıflarımızda da yerinde kullanılabilir.

Madde 10. Doğru: C. send alt sınıfta override edildiği için alt sınıftaki çalışır. channel alt sınıfta yok, üst sınıftan devralınır.

  • A yanlış: Override üst sınıftaki metodu kendiliğinden çağırmaz. Bunun için super().send(...) yazmak gerekir.
  • B yanlış: Metot önce nesnenin kendi sınıfında aranır. SmsNotification kendi send metoduna sahip.
  • D yanlış: Alt sınıfta tanımlanmayan metot hata vermez, üst sınıftan gelir. Kalıtımın amacı bu.
  • E yanlış: Devralınan metot üst sınıftaki gövdesiyle çalışır ve "genel" döndürür.

Tek sayfa özet

  • Kalıtım bir is-a ilişkisidir; alt tür üst türün beklenen sözleşmesini korumalıdır.
  • Bu beklentinin adı Liskov yerine geçme ilkesidir: üst türün beklendiği yerde alt tür de sorunsuz çalışmalıdır.
  • Alt sınıf üst sınıfın metodunu olduğu gibi devralabilir, yeni metot ekleyebilir ya da metodu override edebilir.
  • super() yalnızca kurucuda değil, normal bir metodun sonucunu genişletirken de kullanılır.
  • isinstance, alt sınıfın örneğini üst sınıfın örneği olarak da tanır.
  • Bir metot önce nesnenin kendi sınıfında aranır, orada yoksa üst sınıftan gelir.
  • Kalıtıma karar vermeden önce kompozisyonu da düşünün: “sahiptir” cümlesi daha doğru duruyorsa kompozisyon seçin.

Bu bölümün kazanımları

Bu bölümü bitiren öğrenci:

  • Alt sınıfın üst sınıftan davranış devraldığını ve üst sınıfın bir alt türü sayıldığını gösterir.
  • Kalıtım kararını is-a sezgisiyle değil, yerine geçme sorusuyla sınar.
  • Bir metodu override ederek davranışı özelleştirir.
  • super() çağrısını yalnız kurucuda değil normal metotta da davranışı genişletmek için kullanır.
  • is-a ile has-a ilişkisini ayırır ve ortak bir niteliği kalıtım gerekçesi saymaz.
  • Derin kalıtım zincirinin kırılganlığını açıklar.
  • Override edilen ve devralınan metotların çağrıda hangi sınıftan geldiğini kod izleyerek belirler.
Back to top