Polimorfizm ve duck typing

Geçen hafta alt sınıfın üst sınıftaki bir metodu override edebildiğini gördünüz. Bu hafta farklı sınıflardan nesneleri aynı kodla kullanacağız. Farklı sınıflardan nesnelerin aynı metot çağrısıyla, aynı döngüde ya da aynı fonksiyonda kullanılabilmesine polimorfizm denir. Python’da bunun için sınıfların ortak bir üst sınıftan türemesi her zaman gerekmez. Nesnenin sınıfına değil, hangi metotları olduğuna bakan bu yaklaşıma duck typing denir. Adı “Ördek gibi yürüyor ve ördek gibi vaklıyorsa ördektir” sözünden gelir. Örneklerin çoğu okulun duyuru programından: bir duyuru e-postayla, SMS’le ve okulun mobil uygulamasından gönderiliyor.

Bu sayfadaki kod hücreleri kendiliğinden çalışmaz. Her hücrede önce çıktıyı tahmin edin, sonra Run Code düğmesine basın. Kodu değiştirip yeniden çalıştırabilir, Start Over ile ilk hâline döndürebilirsiniz.

Bu bölümün kapsamı

NotSınavda sorulur
  • Aynı metot çağrısının farklı nesnelerde farklı iş yapması
  • Ortak üst sınıf olmadan polimorfizm (duck typing)
  • Her yeni türde uzayan tür koşulu zincirinin neden kod kokusu sayıldığı
  • isinstance() kullanımının yerinde olduğu durumlar
  • hasattr() sonucunun neyi söyleyip söylemediği
  • EAFP ile LBYL yaklaşımlarının farkı ve yalnız beklenen istisnayı yakalamak
  • Polimorfizm ile kalıtımın farkı
  • 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. İleride karşınıza çıkar, ama bu derste ezberlemeniz beklenmiyor.

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

Bir duyuruyu bütün kanallardan tek döngüyle göndermek istiyoruz. Aşağıdaki iki sınıfın ortak bir üst sınıfı yok, ama ikisinde de send(message) metodu var. Döngü çalışır mı? Çalışırsa ne yazar?

Döngü, nesnenin hangi sınıftan geldiğini bilmeden send() metodunu çağırır. Her nesne kendi sınıfındaki send() metodunu çalıştırır. Aynı çağrının farklı nesnelerde farklı iş yapması polimorfizmdir.

Aynı metot çağrısına farklı nesnelerin farklı davranışla karşılık verdiğini gösteren diyagram.

Polimorfizm ve ortak arayüz

Kalıtım olmadan da polimorfizm

Sıra mobil uygulama kanalında. Bu kanalın sınıfını başka bir ekip yazdı. Geçen haftaki bilgiyle bütün kanalları ortak bir Notification üst sınıfından türetebilirdiniz. Aşağıda EmailNotification böyle yazıldı. AppNotification sınıfını yazan ekip ise onu Notification sınıfından türetmedi. Döngü ikinci nesnede hata verir mi? isinstance() satırları ne yazar?

AppNotification nesnesi bir Notification sayılmıyor, ama döngü onunla da çalıştı. Çağrı için gereken tek şey nesnede send(message) metodunun bulunması.

Gereken metoda sahip nesnelerin, aralarında kalıtım ilişkisi olmasa da aynı yerde kullanılabildiğini gösteren diyagram.

Duck typing

Nesnenin hangi sınıftan olduğuna değil, gereken metodu olup olmadığına bakın.

Duck typing’in kuralı budur.

Her yeni türde uzayan koşul zinciri

C’de farklı türdeki kanalları ayırmak için çoğu zaman bir tür alanı ve switch yazılır. Python’daki karşılığı, nesnenin türüne bakan bir if/elif zinciridir. type(nesne) nesnenin sınıfını verir.

Aşağıdaki sınıflarda gönderme metotlarının adları farklı. Bu yüzden send_notification() her türü ayrı bir dalda tanıyor. AppNotification sonradan eklendi ve fonksiyona dokunulmadı. Son satır ne yazar?

Her yeni kanal bu fonksiyona bir elif daha ekletir. Eklemeyi unutursanız fonksiyon hata da vermez: hiçbir dal çalışmayınca None döndürür. Kodun çalıştığı ama tasarımda bir sorun olduğunu gösteren böyle işaretlere kod kokusu (code smell) denir. Buradaki sorun, mesajın nasıl gönderileceğini nesnenin değil fonksiyonun bilmesi. Bütün sınıflarda aynı adlı send() metodu olursa fonksiyon tek satıra iner ve yeni bir kanal eklemek için onu değiştirmeniz gerekmez:

def send_notification(notification, message):
    return notification.send(message)
Önemliisinstance() nerede yerinde?

Tür kontrolü bazı yerlerde yerindedir: programa dışarıdan gelen verinin beklenen türde olup olmadığını denetlerken (sınır doğrulaması), bir nesneyi dosyaya ya da ağa yazmak için metne çevirirken (serileştirme) ya da karar gerçekten türe bağlıysa. Kod kokusu, nesnenin kendi metodunu çağırmak yerine her yeni türde uzayan bir koşul zinciriyle ne yapılacağını seçmektir. Tür kontrolü gerekiyorsa çoğu zaman isinstance() seçilir: type(x) is Sınıf yalnız tam o sınıfı kabul eder, isinstance(x, Sınıf) alt sınıfların nesnelerini de kabul eder.

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

Listeye send() metodu olmayan bir nesne karışırsa program çökmesin, “Desteklenmiyor” yazsın. Önce şunu sorabilirsiniz: nesnede send var mı? Bir nesnede belirli bir adda nitelik ya da metot bulunup bulunmadığını hasattr(nesne, "ad") ile sorarsınız. İki satır ne yazar?

Ancak hasattr(obj, "send") yalnızca nesnede send adında bir şey bulunduğunu söyler. Bunun çağrılabilir bir metot mu yoksa sıradan bir nitelik mi olduğunu, message parametresini alıp almadığını ya da gerçekten mesaj gönderip göndermediğini söylemez. Örneğin obj.send = "evet" atanmış bir nesnede de hasattr(obj, "send") True verir. Bu yüzden duck typing’i “önce her şeyi hasattr ile kontrol et” diye anlamayın.

EAFP ve LBYL

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

  • LBYL (Look Before You Leap, “atlamadan önce bak”): İşi yapmadan önce koşulu kontrol edin.
  • EAFP (Easier to Ask Forgiveness than Permission, “özür dilemek izin istemekten kolaydır”): İşi doğrudan yapmayı deneyin. Beklediğiniz hata çıkarsa onu uygun except ile yakalayın.

hasattr() ile önce sormak LBYL yaklaşımıdır:

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

EAFP yaklaşımında send() doğrudan çağrılır. Nesnede send yoksa Python AttributeError yükseltir ve except bu hatayı yakalar. Aşağıdaki Printer sınıfında send yok. SmsNotification sınıfının send() metodunda ise upper yanlış yazılmış. Üç satır ne yazar?

UyarıEAFP’de yalnız beklediğiniz hatayı yakalayın

except Exception gibi geniş bir yakalama her hatayı yutar. Yalnız beklediğiniz hata türünü yakalayın. Hücre bir tuzak daha gösteriyor: AttributeError, send() metodunun içinde de oluşabilir. SmsNotification mesaj gönderebilen bir sınıf, ama metodun içindeki yazım hatası yüzünden “Desteklenmiyor” sonucunu aldı. Kendi programlama hatanız böylece gözden kaçar. Her bildirim nesnesinde send() bulunacağı baştan belliyse çoğu zaman en sade çözüm metodu doğrudan çağırmaktır.

Bu iki yaklaşımda 5. haftada gördüğünüz try/except bilgisini nesnelerin metotlarına uyguluyorsunuz. Hangisinin her zaman doğru olduğunu söyleyen bir kural yok. Bir yanda gereksiz ön kontrollerle uzayan kod, öbür yanda gerçek hataları da yutan geniş bir except var. İkisinin arasındaki dengeyi gözetin.

Davranış sözleşmesi

Duck typing’de de kurallar vardır. send(message) gibi ortak bir metodun ne aldığı, ne döndürdüğü ve hangi durumda hangi hatayı yükselttiği belli olmalıdır. Örneğin bu bölümdeki bütün bildirim sınıflarında send() bir metin alır ve bir metin döndürür. Bu kuralları yazan ayrı bir sınıf olmasa bile kuralların kendisi bir davranış sözleşmesidir: çağıran kod bu kurallara güvenerek yazılır.

Fonksiyonlar da polimorfik davranabilir

len() tek bir fonksiyon, ama aşağıda üç farklı türle çağrılıyor. Üç satır ne yazar?

len() metinle, listeyle ve sözlükle aynı biçimde çalışır, çünkü üç türde de uzunluğu veren __len__ adlı özel bir metot vardır. len(x) yazdığınızda Python nesnenin __len__() metodunu çağırır. Bir işlemin çalışması için nesnede bulunması gereken metotlara protokol denir. 12. haftada kendi sınıflarınıza bu tür özel metotlar yazıp onları print() ve == gibi Python işlemleriyle çalıştıracaksınız.

Polimorfizm ve kalıtım ayrımı

Kalıtım iki sınıf arasındaki bir ilişkidir: class SmsNotification(Notification) yazdığınızda SmsNotification üst sınıfın metotlarını devralır. Polimorfizm ise farklı nesnelerin aynı metot çağrısıyla kullanılabilmesidir. Kalıtım polimorfizmi kolaylaştırabilir. Ama Python’da ortak üst sınıf zorunlu değildir: bu bölümün ilk örneğinde kalıtım yoktu.

Tanımanız yeterli: soyut temel sınıf (ABC)

from abc import ABC, abstractmethod

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

ABC ile alt sınıfların hangi metotları yazması gerektiğini kodda açıkça belirtirsiniz: buradaki Payment sınıfından türeyen her sınıfta bir pay() metodu olmalıdır. ABC yazmak sınavda sorulmaz.

C ile Python yan yana

Duyuru programındaki işlerin C’deki ve Python’daki karşılıkları:

İş C’de ya da önceki haftalarda Bu haftaki Python yolu Dikkat
Bir duyuruyu bütün kanallardan tek döngüyle göndermek C’de tür alanı ve döngüde switch, ya da 10. haftadaki gibi ortak üst sınıf ve override Her sınıfta send(message), döngüde notification.send(message) Çağrı her nesnenin kendi sınıfındaki metoda gider
Başka ekibin yazdığı kanalı eklemek C’de onların struct’ını kendi tür alanınıza uydurursunuz, Python’da sınıflarını Notification sınıfından türetmelerini istersiniz Sınıfta send(message) bulunması yeter (duck typing) isinstance(x, Notification) False döndürse de çağrı çalışır
Yeni kanalda gönderme fonksiyonunu değiştirmemek switch’e yeni bir case ya da if/elif zincirine yeni bir dal eklersiniz return notification.send(message) Dal eklenmeyen türde zincir hata vermez, None döndürür
send() metodu olmayan nesnede “Desteklenmiyor” yazmak switch’in default dalında yazdırırsınız hasattr() ile önce sormak (LBYL) ya da try/except AttributeError (EAFP) except AttributeError, send() içindeki yazım hatasını da yakalar

C alışkanlığıyla switch ya da Python’da if/elif zinciri yazarsanız üçüncü satırdaki sorunla karşılaşırsınız: her yeni kanal gönderme fonksiyonunu değiştirmeyi gerektirir. Ortak üst sınıf ve override da çalışır, ama Python’da şart değil: ikinci satırdaki kanal üst sınıf olmadan da döngüde çalıştı. Gönderme işini fonksiyon yerine nesnenin kendisi bildiğinde dördüncü satırdaki kontrole de çoğu zaman gerek kalmaz. Her nesnede send() olacağı belliyse metodu doğrudan çağırırsınız.

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

Tür kontrollerini kaldırın: döngü yalnızca animal.speak() çağırsın ve çıktı aynı kalsın. Ardından bir Bird sınıfı ekleyin ve döngüye dokunmadan onun da çalıştığını gösterin.

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

return payment.pay(amount)

Sıra sizde: Rapor çıktıları

ConsoleReport, TextReport ve CompactReport sınıflarının her birine bir render(data) metodu yazın. Tek bir show_report(report, data) fonksiyonuyla üçünü de kullanın. Dördüncü bir rapor sınıfı ekleyin ve show_report() fonksiyonunu değiştirmeden onunla da çalıştığını gösterin.

Çalışır kod görevi: Dört rapor nesnesini bir listeye koyun, döngüyle dolaşıp her birinin render() sonucunu yazdırın. Ayrıca bir nesnede hasattr(..., "render") sonucuna bakın. Sonra çözümünüzün neden tür ya da nitelik kontrol eden bir if zincirine ihtiyaç duymadığını 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 soruları da bu mantıkla yazılır.

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

2. EmailNotification ve SmsNotification ortak bir üst sınıftan türemedikleri hâlde aynı döngüde kullanılabiliyor. Bunun nedeni nedir?

  • A. Python’ın ö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. İki sınıfta da send(message) metodunun bulunması
  • D. isinstance() denetiminin listede kendiliğinden yapılması
  • E. İki sınıfın da object sınıfından türemesi

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

  • A. Hayır; ortak metodun ne alıp ne döndürdüğü yine belli olmalıdır
  • B. Evet; Python’da sözleşme kavramı yoktur
  • C. Evet; hasattr kontrolü yapıldığı sürece her şey denenebilir
  • D. Hayır; duck typing yalnızca kalıtım varken çalışır
  • E. Hayır; duck typing için soyut temel sınıf (ABC) kullanmak zorunludur

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() sonucu is ile karşılaştırılamadığı için dallar çalışmaz
  • C. Her yeni bildirim türü bu fonksiyona bir elif daha ekletir
  • D. elif yerine else yazılırsa zincir kısalır ve sorun çözülür
  • E. Fonksiyon nesnenin yanında mesajı da gereksiz yere parametre alır

5. Aşağıdaki koşul zincirini kaldırmak için ne yapılmalıdır?

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. Her tür için ayrı fonksiyon yazmak
  • B. Her sınıfa send(message) metodu yazıp onu doğrudan çağırmak
  • C. type() yerine isinstance() yazmak
  • D. Bütün bildirim türlerini tek bir sınıfta birleştirmek
  • E. Koşul zinciri yerine her türü bir fonksiyonla eşleyen bir sözlük kullanmak

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

  • A. Hiçbir zaman; tür kontrolü her durumda kod kokusudur
  • B. Bildirimin türüne göre send_email() ya da send_sms() seçerken
  • C. Nesnede bir metodun bulunup bulunmadığını öğrenirken
  • D. Programa dışarıdan gelen verinin türünü doğrularken
  • E. Her metot çağrısından önce nesnenin türünü sınarken

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

  • A. send(message) çağrısının hiçbir durumda hata vermeyeceği
  • B. Nesnenin send metodunu tanımlayan sınıftan türediği
  • C. send metodunun çağrıldığında bir metin döndüreceği
  • D. send metodunun çağrılınca beklenen işi yapacağı
  • E. Nesnede send adında bir nitelik ya da metot bulunduğu

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

  • A. EAFP dosya işlemlerinde, LBYL nesne metotlarında kullanılan yöntemdir
  • B. İkisi de işi yapmadan önce koşulu denetleyen aynı yaklaşımdır
  • C. EAFP önce koşulu denetler; LBYL işi dener, hatayı except ile yakalar
  • D. LBYL hatayı hiç yakalamaz; EAFP her hatayı except Exception ile yakalar
  • E. LBYL önce koşulu denetler; EAFP işi dener, hatayı except ile yakalar

9. Aşağıdaki kalıbın gözden kaçan riski nedir?

try:
    result = notification.send(message)
except AttributeError:
    result = "Desteklenmiyor"
  • A. send() içindeki bir AttributeError da yakalanıp yanlış yorumlanır
  • B. except AttributeError çok dar olduğu için çoğu hatayı kaçırır
  • C. EAFP yaklaşımı Python’da önerilmez
  • D. as error yazılmadığı için hata mesajı kaybolur
  • E. send() başarılı olsa da except bloğu çalışır

10. Aşağıdaki iki kod parçası için hangisi doğrudur?

# A
class Invoice:
    def total(self):
        return 100

class DiscountedInvoice(Invoice):
    pass

# B
class PdfExport:
    def export(self, rows):
        return "PDF"

class CsvExport:
    def export(self, rows):
        return "CSV"

for exporter in [PdfExport(), CsvExport()]:
    print(exporter.export([1, 2]))
  • A. A’da polimorfizm var; B’deki iki sınıf arasında kalıtım var
  • B. A’da kalıtım yok, çünkü DiscountedInvoice hiçbir metot yazmıyor
  • C. A’da hem kalıtım hem polimorfizm var; B’de ikisi de yok
  • D. A’da kalıtım var; B’de kalıtım olmadan polimorfizm var
  • E. İkisinde de kalıtım var; aynı adlı metot ortak üst sınıf demektir

1. Doğru: B. Döngü, nesnenin hangi sınıftan geldiğini bilmeden send() metodunu çağırır. EmailNotification e-posta metnini, SmsNotification SMS metnini döndürür.

  • 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; iki nesnede de send metodu vardır.

2. Doğru: C. Buna duck typing denir: nesnenin hangi sınıftan olduğuna değil, gereken metoda sahip olup olmadığına bakılır. İkisinde de send(message) metodu olduğu için aynı döngüde kullanılabilirler; aralarında kalıtım ilişkisi gerekmez.

  • A yanlış: Kalıtımdan sonra ortak davranışın hep bir üst sınıftan geldiği sanılabilir. Ama Python kendiliğinden ortak bir üst sınıf oluşturmaz.
  • B yanlış: Aynı adlı metotlar iki sınıfı birbirine bağlıyormuş gibi görünür. Ama sınıflar birleştirilmez; ayrı kalırlar.
  • D yanlış: Farklı türler aynı listede sorunsuz çalıştığı için arka planda bir tür denetimi yapıldığı sanılabilir. Döngü hiç tür denetimi yapmaz; yalnız metodu çağırır.
  • E yanlış: Her sınıfın object sınıfından türediğini bilen öğrenci ortak üst sınıfı burada arayabilir. Bu doğrudur ama object sınıfında send metodu yoktur.

3. Doğru: A. Ortak metodun ne aldığı, ne döndürdüğü ve hangi hatayı yükselttiği belli olmalıdır. Sözleşmeyi yazan ayrı bir sınıf olmasa da bu bir davranış sözleşmesidir; yoksa çağıran kod neye güveneceğini bilemez.

  • B yanlış: Python türleri önceden denetlemediği için sözleşme de yok sanılabilir. Ama Python sözleşmeyi zorla uygulatmaz; sözleşmeyi kodu tasarlayan kişi belirler.
  • C yanlış: Ön kontrol güvenlik sağlıyormuş gibi görünür. Ama hasattr yalnız adın erişilebilir olduğunu söyler; metodun ne aldığını ve ne döndürdüğünü söylemez.
  • D yanlış: Polimorfizm çoğu zaman kalıtımla anlatıldığı için duck typing’in de onu gerektirdiği sanılabilir. Duck typing kalıtım gerektirmez; ortak üst sınıfı olmayan sınıflarla da çalışır.
  • E yanlış: ABC beklentiyi açıkça yazdığı için duck typing’in ön koşulu gibi görünebilir. ABC beklentiyi kodda açıkça yazmanıza yarar ama zorunlu değildir.

4. Doğru: C. Ne yapılacağına nesne değil, fonksiyondaki koşul zinciri karar verir. Bütün sınıflarda ortak bir send(message) metodu olursa fonksiyon return notification.send(message) satırına iner; yeni türler fonksiyonu değiştirmeden eklenir.

  • A yanlış: Tür denetimi hangi dalın çalışacağını açıkça gösterdiği için güvenli görünür. Ama uzayan tür zinciri yüzünden her yeni tür bu fonksiyonu değiştirmeyi gerektirir.
  • B yanlış: is işlecinin sayı ve metinlerde yanıltıcı olduğunu bilen öğrenci burada da hata bekleyebilir. Ama her sınıf nesnesi tektir; type(x) is EmailNotification doğru çalışır. Sorun type() değil, onunla uzayan koşul zinciridir.
  • D yanlış: Son dalı else yapmak bir koşulu sildiği için çözüm gibi görünür. Ama else zinciri kısaltmaz; her yeni tür yine yeni bir dal ister.
  • E yanlış: Mesajı ayrıca geçirmek fazlalık gibi görünebilir. Ama parametre sayısı sorun değildir; send(message) de mesajı parametre olarak alır.

5. Doğru: B. Her bildirim sınıfı aynı adlı send(message) metodunu taşıyınca fonksiyon yalnız notification.send(message) çağırır. Ne yapılacağına nesne karar verince yeni bir tür eklemek için çağıran kodu değiştirmek gerekmez.

  • A yanlış: Her türün kodu ayrı yerde durduğu için düzenli görünür. Ama çağıran kod yine hangi fonksiyonu çağıracağına karar vermek zorunda kalır.
  • C yanlış: isinstance() alt sınıfları da kabul ettiği için daha doğru bir denetim gibi görünür. Ama zincir yine uzar. Sorun tür denetiminin kendisi değil, ne yapılacağının nesnenin dışında seçilmesidir.
  • D yanlış: Sınıf sayısı azalınca tür denetimine gerek kalmayacakmış gibi görünür. Ama koşul zinciri bu kez sınıfın içine taşınır.
  • E yanlış: elif satırları kaybolduğu için zincir çözülmüş gibi görünür. Zincir görünmez olur ama her yeni tür için sözlüğe yine bir satır eklemek gerekir.

6. Doğru: D. Dışarıdan gelen veriyi doğrulamak (sınır doğrulaması) ve serileştirme, kararın gerçekten türe bağlı olduğu durumlardır. Tür kontrolü tek başına hata değildir. Kod kokusu, nesnenin kendi metodunu çağırmak yerine her yeni türde uzayan bir koşul zinciri yazmaktır.

  • A yanlış: Uzayan tür zinciri kod kokusu diye anlatıldığı için her tür kontrolü yasakmış gibi algılanabilir. Böyle bir yasak yoktur; yerinde kullanımları vardır.
  • B yanlış: Hangi metodun çağrılacağı türe bağlı göründüğü için isinstance() burada yerinde sanılabilir. Ama bu, her yeni türde uzayan koşul zinciridir; her sınıfa ortak bir send() yazılırsa tür sormak gerekmez.
  • C yanlış: İki fonksiyon da nesneyi sorguladığı için birbirinin yerine kullanılabilirmiş gibi görünür. Ama isinstance() türü sorar; bir adın varlığını hasattr() söyler.
  • E yanlış: Erken denetim hatayı yakalamanın güvenli yolu gibi görünür. Ama gereksiz ön kontroller kodu uzatır; duck typing’de nesnenin türü sorulmaz.

7. Doğru: E. hasattr() yalnız adın varlığını söyler. Adın çağrılabilir olup olmadığı, hangi parametreleri aldığı ve beklenen işi yapıp yapmadığı bilinmez. Bu yüzden duck typing “önce her şeyi hasattr ile kontrol et” biçiminde anlaşılmamalıdır.

  • A yanlış: Ad bulunduğuna göre çağrının da sorunsuz olacağı sanılabilir. Ama çağrı yanlış argümanla ya da metodun içindeki bir hatayla yine istisna yükseltebilir.
  • B yanlış: Metot bir sınıfta tanımlandığı için adın varlığı bir tür ilişkisini düşündürür. Ama hasattr() tür ilişkisi hakkında bilgi vermez.
  • C yanlış: Örneklerdeki send metotları hep metin döndürdüğü için bu beklenti oluşabilir. Ama hasattr() dönüş türü hakkında bilgi vermez.
  • D yanlış: Adı send olan bir metot gönderme işini yapıyormuş gibi görünür. Ama adın bulunması, metodun sözleşmeye uygun çalışacağını garanti etmez.

8. Doğru: E. LBYL işi yapmadan önce koşulu kontrol eder; EAFP işi doğrudan dener, beklenen hata çıkarsa onu yakalar. İkisinden biri her zaman doğru değildir. Bir yanda gereksiz ön kontroller, öbür yanda gerçek hataları da yutan geniş bir except vardır; ikisi arasındaki denge gözetilir.

  • A yanlış: EAFP dosya örnekleriyle sık anlatıldığı için böyle bir ayrım varmış gibi görünür. Böyle bir alan ayrımı yoktur.
  • B yanlış: İki yaklaşım da hataya karşı önlem aldığı için aynı sanılabilir. Ama biri işten önce kontrol eder, öbürü işi dener ve hatayı sonra yakalar.
  • C yanlış: İki kısaltma birbirine benzediği için roller kolayca karışır. Doğrusu tersidir: LBYL “atlamadan önce bak” der ve önce denetler, EAFP işi doğrudan dener.
  • D yanlış: EAFP “dene, hata çıkarsa yakala” diye özetlendiği için her hatayı yakalamak da buna uyuyor gibi görünür. Ama yalnız beklenen hata türü yakalanmalıdır; geniş except gerçek hataları da yutar. LBYL’de de ön kontrole rağmen istisna oluşabilir.

9. Doğru: A. except yalnız “nesnede send yok” durumunu değil, metodun içindeki yazım hatalarını da yakalar; gerçek bir programlama hatası “desteklenmiyor” diye yorumlanır. Her nesnede send() bulunacağı belliyse metodu doğrudan çağırmak çoğu zaman en sade çözümdür.

  • B yanlış: Tek bir hata türü yakalandığı için öteki hataların kaçacağı düşünülebilir. Ama sorun darlık değil, aynı hata türünün iki farklı nedenden gelebilmesidir.
  • C yanlış: Uyarı EAFP kalıbı üzerinden yapıldığı için yaklaşımın kendisi kötüymüş gibi görünür. Python’da EAFP önerilir; uyarı yalnız istisnanın körlemesine yakalanmasıyla ilgilidir.
  • D yanlış: Hata mesajı hiçbir yerde görünmediği için sorunun bu olduğu sanılabilir. as error hata mesajına erişmeye yarar ama asıl risk, hatanın yanlış yorumlanmasıdır.
  • E yanlış: İki blok alt alta yazıldığı için ikisinin de sırayla çalışacağı sanılabilir. Ama except bloğu yalnız try içinde AttributeError yükselirse çalışır.

10. Doğru: D. Kalıtım iki sınıf arasındaki ilişkidir: A’da DiscountedInvoice, Invoice sınıfının total() metodunu devralır. Polimorfizm ise farklı nesnelerin aynı metot çağrısıyla kullanılmasıdır: B’deki döngü export() çağrısını iki farklı nesnede yapar ve her nesne kendi işini yapar.

  • A yanlış: A’daki iki sınıfın aynı total() metoduna sahip olması polimorfizm gibi görünebilir. Ama A’da aynı çağrının farklı nesnelerde farklı iş yaptığı bir kullanım yok. B’deki sınıflar ise birbirinden türemez.
  • B yanlış: Gövdesi pass olan sınıf hiçbir şey yapmıyormuş gibi görünür. Ama class DiscountedInvoice(Invoice): yazımı kalıtımı kurar; DiscountedInvoice().total() çağrısı Invoice sınıfındaki metotla çalışır ve 100 döndürür.
  • C yanlış: A’da kalıtım olduğu doğru. Ama B’de polimorfizm vardır: döngü iki farklı nesnede aynı export() çağrısını yapar ve her nesne kendi sonucunu döndürür. Ortak üst sınıfın olmaması polimorfizmi engellemez.
  • E yanlış: İki sınıfta aynı adlı metot bulunması ortak bir üst sınıfı düşündürebilir. Ama B’deki sınıflar birbirinden ya da ortak bir sınıftan türetilmemiştir; aynı ad yalnız aynı çağrıyla kullanılabilmelerini sağlar.

Tek sayfa özet

  • Polimorfizmde aynı metot çağrısı her nesnede o nesnenin kendi işini yapar.
  • Duck typing nesnenin sınıfına değil metotlarına bakar. Bu yüzden ortak bir üst sınıf her zaman gerekmez.
  • Nesnenin türüne bakan if/elif zinciri her yeni türde uzar ve dal eklenmeyen türde hata vermeden None döndürebilir. Nesnenin metodunu çağırırsanız yeni bir tür eklemek için çağıran kodu değiştirmeniz gerekmez.
  • isinstance(), dışarıdan gelen veriyi doğrulamak gibi kararın gerçekten türe bağlı olduğu yerlerde kullanılır.
  • hasattr() yalnız bir adın bulunup bulunmadığını söyler. Metodun sözleşmeye uygun çalışacağını garanti etmez.
  • LBYL işten önce koşulu kontrol eder, EAFP işi dener ve hatayı yakalar. EAFP’de yalnız beklediğiniz hata türünü yakalayın ve metodun içinde oluşan aynı türden hatanın da yakalanacağını unutmayın.
  • Kalıtım sınıflar arasındaki bir ilişkidir. Polimorfizm nesneleri kullanma biçimidir ve kalıtım olmadan da mümkündür.

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

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

  • Aynı metot çağrısının farklı nesnelerde farklı iş yaptığını örnekle gösterir.
  • Ortak üst sınıf olmadan da polimorfizmin mümkün olduğunu açıklar.
  • Her yeni türde uzayan tür koşulu zincirini kod kokusu olarak tanır ve yerine ortak bir metot çağrısı koyar.
  • 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 yalnız beklenen istisnayı yakalamanın gerekçesini açıklar.
  • Polimorfizm ile kalıtımı birbirinden ayırır.
Başa dön