Özel metotlar ve özel istisnalar

Python’daki nesneler yalnızca bizim yazdığımız normal metotlarla değil, dilin veri modeliyle de etkileşir. print(obj), repr(obj), obj1 == obj2 veya bir koleksiyon içinde nesnenin görünümü gibi davranışlar özel metotlarla tanımlanabilir. Bu hafta ayrıca problem alanına özgü hataları kendi istisna sınıflarımızla ifade edeceğiz.

str ve len gibi Python işlemlerinin __str__ ve __len__ gibi özel metotlara bağlanmasını gösteren diyagram.

Özel metot protokolü

Bu bölümün kapsamı

NoteSınavda sorulur
  • __str__ ile kullanıcıya dönük temsil
  • __repr__ ile durumu açık gösteren temsil
  • koleksiyonların elemanlar için repr() kullanması
  • __eq__ ile alan kuralına göre eşitlik
  • NotImplemented ile NotImplementedError rol farkı
  • __eq__ tanımlayan sınıfın hash sözleşmesi
  • özel istisna için uygun temel sınıf seçimi
  • __hash__ yazımı
  • __lt__, __add__ gibi diğer özel metotlar ve functools.total_ordering
  • istisna zincirleme (raise ... from ...)

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

Bu iki nesne ne zaman “aynı” sayılır?

Aynı ISBN’li iki Book nesnesi aynı kitap mıdır? Cevap Python’da değil, sizin problem alanınızdadır. Kararı verdikten sonra == dışında nelerin değiştiğini de göreceğiz; in ve remove() aynı tanımı kullanır.

__str__: kullanıcıya dönük temsil

print(product) ve str(product) mümkün olduğunda __str__() sonucunu kullanır.

__repr__: geliştiriciye dönük temsil

Bu derste öncelik, repr çıktısının hata ayıklamada nesnenin durumunu açık göstermesidir.

__str__ / __repr__ geri düşme davranışı

Üç pratik kural önemlidir:

  1. Sınıfta __str__ yok ama __repr__ varsa, str(obj)/print(obj) geliştirici temsilini kullanabilir.
  2. Sadece __str__ tanımlamak repr(obj) davranışını değiştirmez; repr kendi varsayılan temsilinde kalır.
  3. Liste gibi koleksiyonlar elemanlarını gösterirken repr() kullanır. Bu yüzden print([product]), product.__str__() yerine product.__repr__() sonucunu gösterir.
Importantprint(obj) ile print([obj]) aynı protokolü kullanmaz

print(obj) kullanıcıya dönük str temsilini tercih eder. Bir listenin kendi repr çıktısı ise elemanların repr temsilini kullanır. __str__ yazdığınız hâlde print(products) çıktısının değişmemesi çoğu zaman bu yüzdendir.

__eq__: iki nesne ne zaman eşit?

== alan kuralımıza göre eşitliği; is hâlâ kimliği sorar.

NoteEşitlik tasarım kararıdır

Product için kod, Book için ISBN veya Student için öğrenci numarası “aynı kabul edilme” kuralı olabilir. __eq__ yazmadan önce bu kuralı problem alanında belirleyin.

NotImplemented ve NotImplementedError aynı şey değildir

İsimleri benzese de tamamen farklı rolleri vardır:

Ad Tür Ne yapılır? Tipik kullanım
NotImplemented özel singleton değer return edilir __eq__ gibi ikili işlemde bu türle karşılaştırmayı desteklemiyorum
NotImplementedError istisna sınıfı raise edilir bir davranışın burada uygulanmadığını bildirmek

Doğru örnekler:

def __eq__(self, other):
    if not isinstance(other, Product):
        return NotImplemented
class Shape:
    def area(self):
        raise NotImplementedError("Alt sınıf hesaplamalı")
Warningreturn NotImplementedError ve raise NotImplemented yazmayın
  1. haftada NotImplementedError fırlattık; bu hafta __eq__ içinde NotImplemented döndürüyoruz. Bu ayrımı özellikle koruyun.

__eq__ ve hash sözleşmesi

Eşit kabul edilen nesnelerin aynı hash değerine sahip olması gerekir. Bir sınıf __eq__ tanımlayıp uygun __hash__ tanımlamazsa örnekleri çoğu durumda hash edilemez hâle gelir. Bu derste __hash__ yazmıyoruz; mutable alan nesnelerini set elemanı veya dict anahtarı yapmadan önce eşitlik/hash tasarımını düşünmeniz yeterlidir.

Özel istisna hangi sınıftan türemeli?

Genel alan hataları için güvenli başlangıç:

class InsufficientBalanceError(Exception):
    pass

Uygulama düzeyindeki özel istisnaları doğrudan BaseException sınıfından türetmeyin; BaseException, KeyboardInterrupt ve SystemExit gibi program kontrolüyle ilgili istisnaların da köküdür.

Bazen özel hatanız aynı zamanda yerleşik bir hata kategorisinin özel biçimiyse daha anlamlı bir temel seçebilirsiniz:

class InvalidScoreError(ValueError):
    pass

Bu durumda çağıran kod ister InvalidScoreError, ister daha genel ValueError olarak yakalayabilir. Hangi tabanın doğru olduğu hata semantiğine bağlıdır; sırf özel sınıf yazmak için gereksiz hiyerarşi kurmayın.

Alan hatalarını açık ifade etmek

Nesne metodu çoğu zaman hatayı üretir, uygulama/UI katmanı hatayı yakalar. Alan nesnesi hatayı kendi içinde print() edip yutarsa çağıran kod başarısızlığı güvenilir biçimde ayırt edemez.

Genel bir hata yerine alanı ifade eden özel istisna türünün yükseltilmesi ve yakalanmasını gösteren diyagram.

Özel istisna

Alıştırma — __str__ ekle

return f"{self.student_id}{self.name}"

Alıştırma — repr farkını gözlemle

Bir sınıfa hem __str__ hem __repr__ ekleyin. print(obj), repr(obj) ve print([obj]) sonuçlarını yan yana yazdırın; hangi çağrının hangi özel metoda gittiğini açıklayın.

Alıştırma — Eşitlik kuralını seç

Book(isbn, title, author) sınıfında eşitliği ISBN üzerinden tanımlayın. Başka türden nesne geldiğinde NotImplemented döndürün. Bu alıştırmada __hash__ yazmanız beklenmez.

Küçük üretim görevi — Üyelik modeli

Member(member_id, name, active) sınıfı oluşturun. __str__, __repr__, __eq__ tanımlayın; eşitlik member_id üzerinden olsun. InactiveMemberError(Exception) özel istisnasını ve ensure_active() davranışını ekleyin.

Çalışır kod teslimi: İki farklı ama aynı member_id değerine sahip nesne için == ve is sonuçlarını doğrulayın; print(member) ve print([member]) farkını gösterin; pasif üyede özel istisnanın üretildiğini test edin. Ayrıca NotImplemented ile NotImplementedError için birer doğru kullanım satırı ekleyin.

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 Product:
    def __init__(self, code, name):
        self.code = code
        self.name = name

    def __str__(self):
        return f"{self.code}{self.name}"

print(Product("P1", "Klavye"))
  • A. Product(code='P1', name='Klavye')
  • B. TypeError; print() bir nesne alamaz
  • C. P1 — Klavye
  • D. <__main__.Product object at 0x...>
  • E. None

Madde 2. Bir sınıfta yalnız __str__ tanımlanırsa repr(obj) ne verir?

  • A. __str__ sonucunu verir
  • B. AttributeError yükseltir
  • C. Boş metin verir
  • D. Varsayılan temsili verir; yalnız __str__ tanımlamak repr davranışını değiştirmez
  • E. None verir

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

class Product:
    def __init__(self, code):
        self.code = code

    def __str__(self):
        return f"Ürün {self.code}"

    def __repr__(self):
        return f"Product({self.code!r})"

product = Product("P1")
print(product)
print([product])
  • A. Product('P1') ve [Product('P1')]
  • B. Ürün P1 ve [Ürün P1]
  • C. İki satırda da Ürün P1
  • D. Ürün P1 ve [<__main__.Product object at 0x...>]
  • E. Ürün P1 ve [Product('P1')]

Madde 4. __str__ yazdığınız hâlde print(products) çıktısının değişmemesi neyle açıklanır?

  • A. Liste kendi repr çıktısını üretirken elemanların repr temsilini kullanır; __str__ bu yolda hiç çağrılmaz
  • B. print() listeleri hiçbir zaman biçimlendirmez
  • C. __str__ metodunun adı __string__ olmalıdır
  • D. Listelerde __str__ tanımlamak için list sınıfı türetilmelidir
  • E. __str__ yalnız tek elemanlı listelerde çalışır

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

class Product:
    def __init__(self, code):
        self.code = code

    def __eq__(self, other):
        if not isinstance(other, Product):
            return NotImplemented
        return self.code == other.code

first = Product("P1")
second = Product("P1")
print(first == second)
print(first is second)
  • A. True ve True
  • B. True ve False
  • C. False ve False
  • D. False ve True
  • E. NotImplemented ve False

Madde 6. __eq__ yazmadan önce ne belirlenmelidir?

  • A. Nesnelerin bellekte yan yana olup olmadığı
  • B. Sınıfın hangi üst sınıftan türediği
  • C. Problem alanında iki nesnenin hangi kurala göre “aynı” sayılacağı
  • D. __hash__ metodunun kaç bit döndüreceği
  • E. Sınıfın kaç niteliği olduğu

Madde 7. NotImplemented ile NotImplementedError arasındaki fark nedir?

  • A. NotImplemented yükseltilir, NotImplementedError döndürülür
  • B. İkisi de istisna sınıfıdır; yalnız adları farklıdır
  • C. NotImplemented yalnız soyut temel sınıflarda kullanılır
  • D. NotImplemented özel bir değerdir ve __eq__ gibi ikili işlemlerden döndürülür; NotImplementedError bir istisna sınıfıdır ve yükseltilir
  • E. İkisi de değerdir ve return edilir

Madde 8. Alt sınıfın uygulaması gereken bir davranışı üst sınıfta işaretlemek için hangi yazım doğrudur?

  • A. raise NotImplemented
  • B. pass yazıp gövdeyi boş bırakmak
  • C. return NotImplemented
  • D. return NotImplementedError("Alt sınıf hesaplamalı")
  • E. raise NotImplementedError("Alt sınıf hesaplamalı")

Madde 9. __eq__ tanımlayıp uygun __hash__ tanımlamayan bir sınıfın örnekleri için ne olur?

  • A. Çoğu durumda hash edilemez hâle gelir; set elemanı veya dict anahtarı olarak kullanılamaz
  • B. Python kendiliğinden uygun bir __hash__ üretir
  • C. Sınıf örneklenemez hâle gelir
  • D. Hiçbir şey olmaz; hash davranışı __eq__ tanımından etkilenmez
  • E. Örnekler karşılaştırılamaz hâle gelir

Madde 10. Uygulama düzeyinde bir alan hatası için özel istisna tanımlarken hangi taban uygundur?

  • A. object; her sınıfın tabanıdır
  • B. Exception; genel alan hataları için güvenli başlangıçtır
  • C. NotImplementedError
  • D. SystemExit
  • E. BaseException; bütün istisnaların köküdür

Madde 11. class InvalidScoreError(ValueError): pass yazmanın Exception tabanına göre kazancı nedir?

  • A. Hata mesajı otomatik olarak üretilir
  • B. ValueError tabanı istisnanın günlüğe yazılmasını sağlar
  • C. Çağıran kod ister InvalidScoreError ister daha genel ValueError olarak yakalayabilir
  • D. Python hatayı kendiliğinden yakalar
  • E. İstisna daha hızlı yükseltilir

Madde 1 — Doğru: C. print(obj) ve str(obj) mümkün olduğunda __str__() sonucunu kullanır.

  • A yanlış: Bu bir __repr__ çıktısı biçimidir; burada __repr__ tanımlı değildir.
  • B yanlış: print() her nesneyi kabul eder ve metin temsilini ister.
  • D yanlış: Varsayılan temsil ancak __str__ ve __repr__ tanımlı değilse görünür.
  • E yanlış: __str__ bir metin döndürür ve print onu yazar.

Madde 2 — Doğru: D. Düşme yalnız tek yönlüdür: __repr__ varsa str ondan yararlanabilir, tersi geçerli değildir.

  • A yanlış: repr kendi varsayılanında kalır.
  • B yanlış: Varsayılan __repr__ her sınıfta bulunur.
  • C yanlış: Varsayılan temsil sınıf adı ve kimlik bilgisini içerir.
  • E yanlış: repr() her zaman bir metin döndürür.

Madde 3 — Doğru: E. print(obj) kullanıcıya dönük str temsilini tercih eder. Listenin kendi repr çıktısı ise elemanların repr temsilini kullanır.

  • A yanlış: İlk satırda __str__ tanımlı olduğu için o kullanılır.
  • B yanlış: Koleksiyonlar elemanları için str değil repr kullanır.
  • C yanlış: Liste gösterimi farklı protokole başvurur.
  • D yanlış: Sınıfta __repr__ tanımlıdır; varsayılan temsil kullanılmaz.

Madde 4 — Doğru: A. print(obj) ile print([obj]) aynı protokolü kullanmaz. Çözüm __repr__ da tanımlamaktır.

  • B yanlış: Biçimlendirir; ama eleman temsili için repr kullanır.
  • C yanlış: Doğru ad __str__tir.
  • D yanlış: Gerekli değildir; elemanın __repr__ metodu yeterlidir.
  • E yanlış: Eleman sayısıyla ilgisi yoktur.

Madde 5 — Doğru: B. == artık alan kuralımıza (ürün kodu) göre karar verir; is hâlâ kimliği sorar ve iki ayrı nesne vardır.

  • A yanlış: __eq__ yazmak nesneleri aynı nesne yapmaz.
  • C yanlış: __eq__ tanımlandığı için kodlar eşit olduğunda True döner.
  • D yanlış: Sonuçlar ters yazılmış.
  • E yanlış: other bir Product olduğu için o dal hiç çalışmaz.

Madde 6 — Doğru: C. Product için kod, Book için ISBN, Student için öğrenci numarası olabilir. Eşitlik bir tasarım kararıdır.

  • A yanlış: Bellek yerleşimiyle ilgisi yoktur.
  • B yanlış: Kalıtım eşitlik kuralını belirlemez.
  • D yanlış: Bu bir uygulama ayrıntısıdır; önce alan kuralı gelir.
  • E yanlış: Nitelik sayısı eşitlik kuralını belirlemez.

Madde 7 — Doğru: D. Biri “bu türle karşılaştırmayı desteklemiyorum” der; diğeri “bu davranış burada uygulanmadı” der.

  • A yanlış: Kullanımlar ters yazılmış; bu en sık yapılan hatadır.
  • B yanlış: NotImplemented bir istisna sınıfı değil, tekil bir değerdir.
  • C yanlış: İkili işlem metotlarında kullanılır; ABC ile ilgisi yoktur.
  • E yanlış: NotImplementedError bir istisna sınıfıdır.

Madde 8 — Doğru: E. Davranışın burada uygulanmadığını bildiren istisna yükseltilir.

  • A yanlış: NotImplemented bir istisna değildir; yükseltilemez.
  • B yanlış: Sessizce None döndürür ve eksik uygulama fark edilmez.
  • C yanlış: Bu, __eq__ gibi ikili işlemlerin kullanımıdır.
  • D yanlış: İstisna sınıfı döndürülmez; yükseltilir.

Madde 9 — Doğru: A. Eşit kabul edilen nesnelerin aynı hash değerine sahip olması gerekir. Python bu sözleşmeyi koruyabilmek için varsayılan hash’i kaldırır.

  • B yanlış: Otomatik üretim yapılmaz.
  • C yanlış: Örnek üretimi etkilenmez.
  • D yanlış: Etkilenir; varsayılan __hash__ kaldırılır.
  • E yanlış: == çalışır; kaybolan hash davranışıdır.

Madde 10 — Doğru: B. class InsufficientBalanceError(Exception): pass yaygın ve güvenli kalıptır.

  • A yanlış: İstisna olarak yükseltilemez.
  • C yanlış: Eksik uygulamayı bildirir; alan hatası kategorisi değildir.
  • D yanlış: Programı sonlandırma isteğini temsil eder.
  • E yanlış: BaseException, KeyboardInterrupt ve SystemExit gibi program kontrolü istisnalarının da köküdür; uygulama hataları buradan türetilmez.

Madde 11 — Doğru: C. Hata zaten bir değer geçersizliği ise bu taban semantiği doğru yansıtır. Yine de sırf özel sınıf yazmak için gereksiz hiyerarşi kurulmamalıdır.

  • A yanlış: Mesaj yükseltilirken verilir.
  • B yanlış: Günlükleme ayrı bir konudur.
  • D yanlış: Yakalama yine çağıranın işidir.
  • E yanlış: Başarım farkı ölçüt değildir.

Tek sayfa özet

  • Özel metotlar sınıfımızı Python’ın veri modeliyle uyumlu hâle getirir.
  • __str__ kullanıcı, __repr__ geliştirici odaklı temsildir; koleksiyonlar elemanlarda repr kullanır.
  • NotImplemented döndürülen özel bir değerdir; NotImplementedError fırlatılan bir istisnadır.
  • __eq__ eşitliğin alan anlamını kodlar; hash edilebilirlik ayrı ama ilişkili bir sözleşmedir.
  • Özel istisnalar çoğunlukla Exception veya semantik olarak daha uygun yerleşik bir istisna sınıfından türetilir.

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

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

  • __str__ ile kullanıcıya dönük temsil yazar.
  • __repr__ ile hata ayıklamada nesnenin durumunu açık gösteren temsil yazar.
  • Koleksiyonların elemanları için repr() kullandığını belirler.
  • __eq__ ile alan kuralına göre eşitlik tanımlar ve kimlikten ayırır.
  • NotImplemented ile NotImplementedError arasındaki rol farkını gösterir.
  • __eq__ tanımlayan sınıfın hash sözleşmesini gözetmesi gerektiğini bilir.
  • Özel istisna sınıfı için uygun temel sınıfı seçer.
Back to top