Polimorfizm ve duck typing

Polimorfizm, farklı nesnelerin ortak bir davranış üzerinden aynı akışta kullanılabilmesidir. Python’da bunun için her zaman ortak bir üst sınıf kurmak gerekmez. Bir nesne gerekli davranışı sunuyorsa onu kullanabiliriz. Bu yaklaşım yaygın olarak duck typing olarak adlandırılır.

Bu bölümün kapsamı

NoteSınavda sorulur
  • aynı çağrının farklı nesnelerde farklı davranış üretmesi
  • ortak üst sınıf olmadan polimorfizm (duck typing)
  • büyüyen tür koşulu zincirinin tasarım kokusu olması
  • isinstance() kullanımının yerinde olduğu durumlar
  • hasattr() sonucunun neyi söyleyip söylemediği
  • EAFP ile LBYL karşılaştırması ve dar istisna yakalama
  • polimorfizm ile kalıtımın ayrımı
  • soyut temel sınıf (ABC) ve @abstractmethod yazımı
  • typing.Protocol ile yapısal alt tipleme
  • çoklu gönderim (multiple dispatch)

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

Çağıran kodun nesnenin türünü bilmesi gerekir mi?

Her yeni tür geldiğinde büyüyen bir if zinciri, davranışın yanlış yerde seçildiğinin işaretidir. Python’da bunu düzeltmek için ortak bir üst sınıf şart değildir; nesnelerin aynı davranışı sunması yeter.

Aynı çağrı, farklı davranış

Döngü nesnenin tam türünü bilmeden send() çağırır. Her nesne davranışı kendi biçiminde gerçekleştirir.

Farklı nesnelerin aynı metot çağrısı üzerinden farklı davranışlar sergilemesini gösteren diyagram.

Polimorfizm ve ortak arayüz

Kalıtım olmadan da polimorfizm

Yukarıdaki iki sınıf ortak bir üst sınıftan türemedi. Yine de ikisi de send(message) davranışını sunduğu için aynı akışta kullanılabildi.

Kalıtım ilişkisi zorunlu olmadan gerekli davranışı sağlayan nesnelerin aynı bağlamda kullanılabilmesini gösteren diyagram.

Duck typing

Nesnenin kim olduğundan önce, gerekli davranışı sunup sunmadığına bak.

Bu yaklaşım duck typing olarak bilinir.

Kötü sinyal: büyüyen tür zinciri

def send_notification(notification, message):
    if type(notification) is EmailNotification:
        return notification.send_email(message)
    elif type(notification) is SmsNotification:
        return notification.send_sms(message)

Yeni tür geldikçe fonksiyon değişir. Ortak davranış sunmak daha genişletilebilir olabilir:

def send_notification(notification, message):
    return notification.send(message)
Importantisinstance() kullanmak başlı başına hata değildir

Tür kontrolü sınır doğrulamasında, serileştirmede veya gerçekten türe bağlı karar gerektiğinde anlamlı olabilir. Tasarım kokusu, nesnenin kendi davranışını çağırmak yerine her yeni tür için büyüyen bir koşul zinciriyle davranış seçmektir.

hasattr() ne söyler, ne söylemez?

Bir nesnenin belirli adlı niteliğe/metoda sahip olup olmadığını hasattr() ile kontrol edebiliriz:

Ancak hasattr(obj, "send") yalnızca bu adın erişilebilir olduğunu söyler. send gerçekten çağrılabilir mi, doğru parametreleri kabul ediyor mu ve beklenen anlamı taşıyor mu sorularını tek başına çözmez. Bu yüzden duck typing’i “önce her şeyi hasattr ile kontrol et” şeklinde anlamayın.

EAFP ve LBYL

Python kodunda iki yaklaşımın adını sık görebilirsiniz:

  • LBYL — Look Before You Leap: İşlemden önce koşulu kontrol et.
  • EAFP — Easier to Ask Forgiveness than Permission: Normal işlemi dene, beklenen başarısızlığı uygun istisnayla yönet.

Örneğin LBYL:

if hasattr(notification, "send"):
    result = notification.send(message)
else:
    result = "Desteklenmiyor"

EAFP yaklaşımı:

try:
    result = notification.send(message)
except AttributeError:
    result = "Desteklenmiyor"
WarningEAFP “her şeyi except Exception ile yakala” değildir

Beklenen başarısızlığı dar biçimde yakalayın. Ayrıca AttributeError bazen send() metodunun içinde de oluşabilir; körlemesine yakalamak gerçek bir programlama hatasını yanlış yorumlayabilir. Davranış sözleşmesi açık olduğunda çoğu zaman doğrudan çağrı yapmak en sade çözümdür.

Bu kavramlar 5. haftadaki try/except bilgisini nesne arayüzleriyle birleştirir. Amaç tek bir tarzı dogma hâline getirmek değil, gereksiz ön kontroller ile hatayı fazla geniş yakalama arasındaki dengeyi görmektir.

Davranış sözleşmesi

Duck typing “her şeyi dene, çalışırsa olur” anlamına gelmez. send(message) gibi ortak davranış için giriş, dönüş değeri ve hata koşulları açık olmalıdır. Resmî bir arayüz sınıfı olmasa bile bu bir davranış sözleşmesi oluşturur.

Fonksiyonlar da polimorfik davranabilir

Farklı türler ortak protokole katıldığı için len() hepsiyle çalışır. 12. haftada kendi sınıflarımızı Python veri modeline bağlayacağız.

Polimorfizm ve kalıtım ayrımı

Kalıtım sınıflar arasındaki yapısal ilişkidir. Polimorfizm farklı nesnelerin ortak davranış üzerinden kullanılabilmesidir. Kalıtım bunu kolaylaştırabilir; ancak Python’da ortak üst sınıf zorunlu değildir.

Soyut temel sınıf (ABC) kavram radarı

from abc import ABC, abstractmethod

class Payment(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

ABC, alt sınıfların belirli davranışı sunması beklentisini daha açık ifade edebilir. Bu geçiş sürümünde ABC yazmak ölçme beklentisi değildir.

Alıştırma — Koşul zincirini kaldır

Tür kontrollerini kaldırıp aynı çıktıyı ortak speak() davranışıyla üretin. Ardından Bird ekleyip ana döngünün değişmediğini gösterin.

Alıştırma — Ortak davranışı tamamla

return payment.pay(amount)

Küçük üretim görevi — Rapor çıktıları

ConsoleReport, TextReport, CompactReport sınıflarının her biri render(data) metodu sunsun. Tek bir show_report(report, data) fonksiyonuyla üçünü de kullanın. Dördüncü bir sınıf ekleyerek ana fonksiyonun değişmediğini gösterin.

Çalışır kod teslimi: Dört rapor nesnesini aynı koleksiyonda dolaştırıp render() sonuçlarını üretin. Ayrıca bir nesnede hasattr(..., "render") sonucunu gözlemleyin; sonra asıl çözümünüzün neden tür/özellik kontrol zincirine ihtiyaç duymadığını iki cümleyle açıklayın.

Sınav provası

Önce kendi cevabını seç; sonra cevap anahtarında her şıkkın neden doğru veya yanlış olduğunu oku. Ç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 EmailNotification:
    def send(self, message):
        return f"E-posta: {message}"

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

for notification in [EmailNotification(), SmsNotification()]:
    print(notification.send("Merhaba"))
  • A. TypeError; farklı türler aynı listede tutulamaz
  • B. E-posta: Merhaba ve SMS: Merhaba
  • C. Hiçbir şey yazmaz
  • D. İki satırda da E-posta: Merhaba
  • E. AttributeError; iki sınıfın ortak üst sınıfı yoktur

Madde 2. EmailNotification ve SmsNotification ortak bir üst sınıftan türemediği hâlde aynı döngüde kullanılabilmesi neyle açıklanır?

  • A. Python’un örtük olarak ortak bir üst sınıf oluşturması
  • B. Metot adlarının aynı olması hâlinde Python’ın sınıfları birleştirmesi
  • C. Duck typing; nesnenin kim olduğundan önce gerekli davranışı sunup sunmadığına bakılır
  • D. isinstance() denetiminin listede kendiliğinden yapılması
  • E. İki sınıfın da object sınıfından türemesi

Madde 3. Duck typing “her şeyi dene, çalışırsa olur” demek midir?

  • A. Evet; Python’da sözleşme kavramı yoktur
  • B. Evet; hasattr kontrolü yapıldığı sürece her şey denenebilir
  • C. Hayır; duck typing yalnızca kalıtım varken çalışır
  • D. Hayır; ortak davranış için giriş, dönüş değeri ve hata koşulları açık olmalıdır — resmî bir arayüz sınıfı olmasa da bu bir davranış sözleşmesidir
  • E. Hayır; duck typing için soyut temel sınıf (ABC) kullanmak zorunludur

Madde 4. Aşağıdaki fonksiyonun tasarım sorunu nedir?

def send_notification(notification, message):
    if type(notification) is EmailNotification:
        return notification.send_email(message)
    elif type(notification) is SmsNotification:
        return notification.send_sms(message)
  • A. Sorun yoktur; tür denetimi her zaman en güvenli yoldur
  • B. type() fonksiyonu Python’da kullanılamaz
  • C. elif yerine else yazılmalıdır
  • D. Fonksiyon iki parametre aldığı için
  • E. Her yeni bildirim türü geldiğinde bu fonksiyon değişmek zorundadır; davranış nesnenin kendisinden değil dışarıdaki koşuldan seçilir

Madde 5. Yukarıdaki koşul zincirini kaldırmak için ne yapılmalıdır?

  • A. Her bildirim sınıfına aynı adlı send(message) davranışını koyup fonksiyonda doğrudan notification.send(message) çağırmak
  • B. Her tür için ayrı fonksiyon yazmak
  • C. type() yerine isinstance() yazmak
  • D. Bütün bildirim türlerini tek bir sınıfta birleştirmek
  • E. Fonksiyonu bir sözlükle tür → fonksiyon eşlemesine çevirmek

Madde 6. isinstance() kullanımı ne zaman yerindedir?

  • A. Yalnızca hasattr() ile birlikte kullanıldığında
  • B. Sınır doğrulaması, serileştirme veya gerçekten türe bağlı bir karar gerektiğinde
  • C. Hiçbir zaman; nesne tabanlı kodda tür kontrolü yasaktır
  • D. Yalnızca kalıtım kullanılmayan tasarımlarda
  • E. Her metot çağrısından önce, güvenlik amacıyla

Madde 7. hasattr(obj, "send") çağrısı True verirse ne kesin olarak bilinir?

  • A. send(message) çağrısının hata vermeyeceği
  • B. Nesnenin belirli bir sınıftan türediği
  • C. Yalnızca send adının nesne üzerinden erişilebilir olduğu; çağrılabilir olup olmadığı, hangi parametreleri kabul ettiği ve beklenen anlamı taşıyıp taşımadığı bilinmez
  • D. send metodunun bir metin döndürdüğü
  • E. send metodunun beklenen davranışı sunduğu

Madde 8. EAFP ve LBYL yaklaşımları nasıl tanımlanır?

  • A. EAFP yalnızca dosya işlemlerinde, LBYL yalnızca nesnelerde kullanılır
  • B. İkisi de aynı şeyin farklı adıdır
  • C. EAFP her hatayı except Exception ile yakalamaktır
  • D. LBYL işlemden önce koşulu kontrol eder; EAFP normal işlemi dener ve beklenen başarısızlığı uygun istisnayla yönetir
  • E. LBYL istisnaları hiç kullanmamaktır

Madde 9. try: result = notification.send(message) / except AttributeError: kalıbının gözden kaçan riski nedir?

  • A. except AttributeError çok dar olduğu için çoğu hatayı kaçırır
  • B. EAFP yaklaşımı Python’da önerilmez
  • C. as error yazılmadığı için hata mesajı kaybolur
  • D. try bloğu içinde atama yapılamaz
  • E. AttributeError send() metodunun içinde de oluşabilir; körlemesine yakalamak gerçek bir programlama hatasını “desteklenmiyor” diye yorumlar

Madde 10. Polimorfizm ile kalıtım arasındaki ilişki nedir?

  • A. Kalıtım sınıflar arasındaki yapısal ilişkidir; polimorfizm farklı nesnelerin ortak davranış üzerinden kullanılabilmesidir — kalıtım bunu kolaylaştırabilir ama Python’da zorunlu değildir
  • B. Polimorfizm kalıtımın başka adıdır
  • C. Kalıtım yalnızca polimorfizm kurmak için kullanılır
  • D. İkisi birbirinin alternatifidir; biri kullanılırsa diğeri kullanılamaz
  • E. Polimorfizm için ortak bir üst sınıf zorunludur

Madde 1 — Doğru: B. Döngü nesnenin tam türünü bilmeden send() çağırır; her nesne davranışı kendi biçiminde gerçekleştirir.

  • A yanlış: Python listeleri karışık türleri tutabilir.
  • C yanlış: Döngü iki tur döner ve her turda yazdırır.
  • D yanlış: Çağrı, listedeki her nesnenin kendi sınıfındaki metoda gider.
  • E yanlış: Ortak üst sınıf gerekmez; her nesne send sunmaktadır.

Madde 2 — Doğru: C. İkisi de send(message) davranışını sunduğu için aynı akışta çalışırlar; tür ilişkisi gerekmez.

  • A yanlış: Böyle bir otomatik sınıf üretimi yoktur.
  • B yanlış: Sınıflar birleştirilmez; ayrı kalırlar.
  • D yanlış: Döngü hiç tür denetimi yapmaz.
  • E yanlış: Bu her sınıf için doğrudur ama send davranışını açıklamaz.

Madde 3 — Doğru: D. Sözleşme yazılı bir sınıf olmasa bile vardır; aksi hâlde çağıran kod neye güveneceğini bilemez.

  • A yanlış: Sözleşme dilin zorlamasıyla değil tasarımla kurulur.
  • B yanlış: hasattr yalnız adın erişilebilirliğini söyler.
  • C yanlış: Duck typing zaten kalıtım gerektirmeyen yaklaşımdır.
  • E yanlış: ABC beklentiyi açık kılabilir ama zorunlu değildir.

Madde 4 — Doğru: E. Ortak bir send(message) davranışı sunulursa fonksiyon return notification.send(message) satırına iner ve yeni türler onu değiştirmeden eklenir.

  • A yanlış: Büyüyen tür zinciri genişletilebilirliği düşürür.
  • B yanlış: Kullanılabilir; sorun kullanım biçimidir.
  • C yanlış: Yazım ayrıntısı asıl sorunu çözmez.
  • D yanlış: Parametre sayısı sorun değildir.

Madde 5 — Doğru: A. Davranış seçimini nesneye bırakmak, yeni tür eklemeyi çağıran kodu değiştirmeden mümkün kılar.

  • B yanlış: Çağıran kod yine hangi fonksiyonu çağıracağına karar vermek zorunda kalır.
  • C yanlış: Zincir yine büyür; sorun tür denetiminin kendisi değil, davranışın dışarıda seçilmesidir.
  • D yanlış: Koşul zinciri bu kez sınıfın içine taşınır.
  • E yanlış: Zinciri gizler ama yeni tür yine merkezî bir tabloyu güncellemeyi gerektirir.

Madde 6 — Doğru: B. Tür kontrolü başlı başına hata değildir. Tasarım kokusu, nesnenin kendi davranışını çağırmak yerine her yeni tür için büyüyen koşul zinciri kurmaktır.

  • A yanlış: İkisi ayrı sorular sorar; birlikte kullanım zorunluluğu yoktur.
  • C yanlış: Böyle bir yasak yoktur; yerinde kullanımları vardır.
  • D yanlış: Kalıtımla ilgisi yoktur.
  • E yanlış: Gereksiz ön kontroller kodu şişirir ve duck typing’i bozar.

Madde 7 — Doğru: C. Bu yüzden duck typing “önce her şeyi hasattr ile kontrol et” biçiminde anlaşılmamalıdır.

  • A yanlış: Çağrı, metodun içinde de hata yükseltebilir.
  • B yanlış: Tür ilişkisi hakkında bilgi vermez.
  • D yanlış: Dönüş türü hakkında bilgi vermez.
  • E yanlış: Ad varlığı davranış sözleşmesini garanti etmez.

Madde 8 — Doğru: D. Amaç bir tarzı dogma hâline getirmek değil, gereksiz ön kontroller ile hatayı fazla geniş yakalama arasındaki dengeyi görmektir.

  • A yanlış: Böyle bir alan ayrımı yoktur.
  • B yanlış: Kontrolün işlemden önce mi sonra mı yapıldığı bakımından ayrılırlar.
  • C yanlış: Beklenen başarısızlık dar biçimde yakalanmalıdır.
  • E yanlış: Kontrol yapılsa da istisnalar yine oluşabilir.

Madde 9 — Doğru: E. Yakalanan hata beklenen başarısızlıkla sınırlı değildir. Sözleşme açıkken doğrudan çağrı çoğu zaman en sade çözümdür.

  • A yanlış: Sorun darlık değil, aynı hata türünün iki farklı nedenden gelebilmesidir.
  • B yanlış: Önerilir; uyarı yalnız körlemesine yakalamaya yöneliktir.
  • C yanlış: Ayrıntı yararlıdır ama asıl risk hatanın yanlış yorumlanmasıdır.
  • D yanlış: Yapılabilir; sözdizimi geçerlidir.

Madde 10 — Doğru: A. İki kavram sık birlikte anılır ama aynı şey değildir; duck typing polimorfizmi kalıtımsız da kurar.

  • B yanlış: Kalıtımsız polimorfizm örnekleri bunu çürütür.
  • C yanlış: Kod yeniden kullanımı ve tür ilişkisi de amaçlar arasındadır.
  • D yanlış: Birlikte kullanılabilirler.
  • E yanlış: Python’da zorunlu değildir.

Tek sayfa özet

  • Polimorfizm, ortak çağrının farklı nesnelerde farklı davranış üretmesidir.
  • Duck typing sayesinde ortak üst sınıf her zaman zorunlu değildir.
  • hasattr() yararlı bir gözlem aracıdır; davranış sözleşmesinin yerini tutmaz.
  • EAFP ve LBYL iki farklı kontrol yaklaşımıdır; beklenen hatayı dar yakalamak önemlidir.
  • Nesnenin türünü sorgulamak yerine davranışı çağırmak çoğu zaman daha esnek tasarımdır.
  • Kalıtım bir ilişki; polimorfizm bir kullanım biçimidir.

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

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

  • Aynı çağrının farklı nesnelerde farklı davranış üretmesini gösterir.
  • Ortak üst sınıf olmadan da polimorfizmin kurulabildiğini açıklar.
  • Büyüyen tür koşulu zincirini tasarım kokusu olarak tanır ve ortak davranışa çevirir.
  • isinstance() kullanımının hangi durumda yerinde olduğunu belirler.
  • hasattr() sonucunun neyi söyleyip neyi söylemediğini açıklar.
  • EAFP ile LBYL yaklaşımlarını karşılaştırır ve dar istisna yakalamayı savunur.
  • Polimorfizm ile kalıtımı birbirinden ayırır.
Back to top