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.

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ı

NoteSı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.

Duyuru programındaki dört işi C’de nasıl yapardınız?

Okulun duyuru programını yazıyorsunuz. Bir duyuru e-postayla, SMS’le ve okulun mobil uygulamasından gönderiliyor. Her kanal mesajı kendi biçiminde gönderiyor: E-posta: Toplantı 14.00'te, SMS: Toplantı 14.00'te. Program şu dört işi yapacak:

  1. Bir duyuruyu bütün kanallardan tek bir döngüyle göndermek
  2. Mobil uygulama kanalını eklemek: bu kanalın sınıfını başka bir ekip yazdı, sizin sınıflarınızdan türetmedi
  3. Yeni bir kanal eklendiğinde gönderme fonksiyonunu değiştirmemek
  4. Listeye gönderme işi olmayan bir nesne karışırsa programı çökertmeden “Desteklenmiyor” yazmak

Bu işlerin her birini C’de nasıl yapardınız? Geçen haftaki kalıtım bilgisiyle Python’da nasıl yazacağınızı düşündüyseniz onu da yazabilirsiniz. Okumaya devam etmeden önce her iş için bir satır yazın: “Tür alanına bakıp switch ile ayırırım” da bir cevap.

Bölümde bu dört işin Python’da nasıl yazıldığını göreceğiz. Bölümün sonunda listenize döneceğiz.

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

Birinci iş: bir duyuruyu bütün kanallardan tek döngüyle göndermek. 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

İkinci iş: başka bir ekibin yazdığı mobil uygulama kanalını eklemek. 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ı ise başka ekip yazdı ve 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

Üçüncü iş: yeni bir kanal eklendiğinde gönderme fonksiyonunu değiştirmemek. C’de bu iş 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)
Importantisinstance() 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?

Dördüncü iş: gönderme işi olmayan bir nesne listeye karışırsa “Desteklenmiyor” yazmak. Ö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?

WarningEAFP’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.

Soruya dönelim: duyuru programı Python’da

Bölümün başında duyuru programının dört işini nasıl yapacağınızı sormuştuk. Python’daki karşılıkları şöyle:

İş 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
Gönderemeyen 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

Listenizi tabloyla karşılaştırın. C’de switch ya da Python’da if/elif zinciri yazdıysanı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 cevabı 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 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üremedikleri hâlde aynı döngüde kullanılabiliyor. Bunun nedeni nedir?

  • 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; çağrı için yalnız gereken metodun bulunması yeter
  • 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. 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

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() 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 hem nesneyi hem mesajı parametre olarak aldığı için

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

  • 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

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

  • A. Yalnızca hasattr() ile birlikte kullanıldığında
  • B. Hiçbir zaman; nesne tabanlı kodda tür kontrolü yasaktır
  • C. Yalnızca kalıtım kullanılmayan tasarımlarda
  • D. Karar gerçekten türe bağlıyken, örneğin dış veriyi doğrularken
  • E. Her metot çağrısından önce, nesne yanlış türdeyse erken durmak için

Madde 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

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 istisnaları hiç kullanmamaktır
  • E. LBYL önce koşulu denetler; EAFP işi dener, hatayı except ile yakalar

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

  • 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. try bloğu içinde atama yapılamaz

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

  • A. Polimorfizm, kalıtımın metot düzeyindeki başka bir adıdır
  • B. Kalıtım yalnızca polimorfizm için kullanılır
  • C. İkisi birbirinin alternatifidir; biri kullanılırsa diğeri kullanılamaz
  • D. Kalıtım polimorfizmi kolaylaştırabilir ama ön koşulu değildir
  • E. Polimorfizm için ortak bir üst sınıf zorunludur

Madde 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.

Madde 2. Doğru: C. 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.

Madde 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.

Madde 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.

Madde 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.

Madde 6. Doğru: D. Dışarıdan gelen veriyi doğrulamak (sınır doğrulaması) ve serileştirme böyle 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ış: İki fonksiyon da nesneyi sorguladığı için birlikte kullanılıyormuş gibi görünür. isinstance() türü, hasattr() bir adın varlığını sorar; birlikte kullanılmaları gerekmez.
  • B 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.
  • C yanlış: isinstance() alt sınıfları da kabul ettiği için kalıtımla ilgili bir araç sanılabilir. Ama ne zaman kullanılacağı kalıtımın olup olmamasına bağlı değildir.
  • 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.

Madde 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.

Madde 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ış: 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.
  • D yanlış: Ön kontrol yapıldığı için istisnaya yer kalmadığı sanılabilir. Ama kontrol yapılsa da istisnalar yine oluşabilir.

Madde 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ış: Kısa try bloklarında çoğu zaman yalnız bir çağrı görüldüğü için atamanın yasak olduğu sanılabilir. Yapılabilir; söz dizimi geçerlidir.

Madde 10. Doğru: D. Kalıtım sınıflar arasındaki bir ilişkidir, polimorfizm farklı nesnelerin aynı metot çağrısıyla kullanılabilmesidir. İki kavram sık birlikte anılır. Ama bölümdeki bildirim örneğinde polimorfizm kalıtım olmadan, duck typing ile kuruldu.

  • A yanlış: Override örnekleri polimorfizmi kalıtımla birlikte gösterdiği için ikisi aynı sanılabilir. Ama kalıtımsız polimorfizm örnekleri bunu çürütür.
  • B yanlış: Bu bölümde kalıtım polimorfizmle birlikte anıldığı için tek amacı buymuş gibi görünebilir. Ama kalıtım, üst sınıftaki kodu alt sınıfta yeniden kullanmak ve türler arasında ilişki kurmak için de kullanılır.
  • C yanlış: Duck typing kalıtımsız çalıştığı için ikisi arasında seçim yapmak gerekiyormuş gibi görünür. Ama birlikte kullanılabilirler: bir alt sınıf, override ettiği metotla polimorfik çağrıya katılabilir.
  • E yanlış: Polimorfizm çoğu kaynakta ortak üst sınıfla anlatıldığı için bu bir şart sanılabilir. Python’da zorunlu değildir; send metodu olan her nesne aynı döngüde kullanılabilir.

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.
Back to top