Özel metotlar ve özel istisnalar

print(product) yazdığınızda ekrana ne çıkacağını, first == second karşılaştırmasının ne zaman True vereceğini ya da nesnenin bir listenin içinde nasıl görüneceğini kendi sınıfınızda siz belirleyebilirsiniz. Bunun için sınıfa, adı iki alt çizgiyle başlayıp biten özel metotlar yazarsınız: __str__, __repr__, __eq__ gibi. Bu metotları siz çağırmazsınız. print(), repr() ya da == kullanıldığında Python kendisi çağırır. 11. haftada len(x) yazınca Python’ın nesnenin __len__() metodunu çağırdığını görmüştünüz. Bu hafta kendi sınıflarınıza bu tür metotlar yazacaksınız. Ayrıca programın konusuna özgü hataları, örneğin stokta olandan fazla ürün satılmak istenmesini, kendi istisna sınıflarınızla bildireceksiniz.

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
  • __str__ ile kullanıcıya dönük temsil
  • __repr__ ile durumu açık gösteren temsil
  • Koleksiyonların elemanlarını gösterirken repr() kullanması
  • __eq__ ile iki nesnenin ne zaman eşit sayılacağını belirleme
  • Alt sınıfın uygulaması gereken metodu raise NotImplementedError ile işaretleme
  • __eq__ tanımlayan sınıfın hash sözleşmesi
  • Özel istisna için uygun temel sınıfı seçme
  • __eq__ içinde return NotImplemented kalıbının ayrıntısı
  • __hash__ yazımı
  • __lt__, __add__ gibi diğer özel metotlar ve functools.total_ordering
  • İstisna zincirleme (raise ... from ...)

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

Mağaza programındaki beş işi C’de nasıl yapardınız?

Bir mağazanın satış programını yazıyorsunuz. Her ürünün bir kodu, bir adı ve stokta kalan adedi var. Program şu beş işi yapacak:

  1. Bir ürünü ekrana P1 - Klavye biçiminde yazdırmak
  2. Hata ayıklarken sepetteki ürünleri bütün alanlarıyla ekrana yazdırmak
  3. Kodu aynı olan iki ürün kaydını aynı ürün saymak
  4. Gün içinde kaç farklı ürün satıldığını saymak
  5. Stokta olandan fazla ürün satılmak istendiğinde bunu satış fonksiyonunu çağıran koda bildirmek

Programlama Temelleri’nde ürünü bir struct product ile tuttuğunuzu düşünün. Bu işlerin her birini C’de nasıl yapardınız? 3. ve 5. haftalarda Python’da gördüklerinizi de kullanabilirsiniz. Okumaya devam etmeden önce her iş için bir satır yazın. “Alanları printf ile yazan bir fonksiyon yazarım” da bir cevap.

Bölümde bu beş işin Python sınıflarında nasıl yapıldığını göreceğiz. Bölümün sonunda listenize döneceğiz.

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

C’de bir struct değişkenini printf’e doğrudan veremezdiniz, alanları tek tek yazan bir fonksiyon yazardınız. Python’da her nesne print() ile yazdırılabilir. Sınıfta bunun için hiçbir şey yazmazsanız ekrana ne çıkar? Hücre ne yazar?

Python her sınıfa bir varsayılan temsil verir: sınıfın adı ve nesnenin kimliğinden gelen, 0x ile başlayan bir sayı. Satış ekranında kullanıcıya bunu göstermek istemeyiz.

Sınıfa __str__ metodu yazarsanız print(product) ve str(product) bu metodun döndürdüğü metni kullanır. Son satır ne yazar?

C’deki yazdırma fonksiyonunuz alanları printf ile kendisi yazıyordu. __str__ ise ekrana bir şey yazmaz, metni return ile döndürür. Yazdırma işini print() yapar. Bu metin nesnenin kullanıcıya gösterilecek hâlidir. Buna kullanıcıya dönük temsil denir.

__repr__: geliştiriciye dönük temsil

Hata ayıklarken ürünün yalnız adını değil, bütün alanlarını görmek istersiniz. Bunun için __repr__ yazılır. Aşağıdaki f-string’de geçen !r, değeri repr() ile yazar. Metinler bu yüzden tırnak içinde görünür. Bu sınıfta __str__ yok. İki satır ne yazar?

repr çıktısı geliştirici içindir. Bu derste onu en çok hata ayıklarken nesnenin durumunu bir bakışta görmek için yazıyorsunuz: Product(code='P1', name='Klavye') çıktısından hangi sınıfın hangi değerlerle oluşturulduğu okunur. Sınıfta __str__ yoksa print() da bu metni kullanır.

Hangi işlem hangi metodu çağırır?

Sepet, ürünlerden oluşan bir listedir. Bu kez sınıfta iki metot da var. İki satır ne yazar?

Aklınızda şu üç kural kalsın:

  1. print(obj) ve str(obj) önce __str__ metoduna bakar. Sınıfta __str__ yoksa __repr__ sonucunu kullanır.
  2. repr(obj) yalnız __repr__ metoduna bakar. Yalnız __str__ tanımlarsanız repr(obj) varsayılan temsilde kalır: <__main__.Product object at 0x...>.
  3. Liste gibi koleksiyonlar elemanlarını gösterirken repr() kullanır. Bu yüzden print([product]), __str__ yerine __repr__ sonucunu gösterir.

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

Özel metot protokolü
ImportantListe elemanlarını repr ile yazar

print(obj) nesnenin kullanıcıya dönük str temsilini yazar. print([obj]) ise listeyi yazdırır. Liste de kendini metne çevirirken her elemanın repr temsilini kullanır. __str__ yazdığınız hâlde print(cart) çıktısı değişmiyorsa nedeni çoğu zaman budur. Sepetteki ürünleri okunur görmek için sınıfa __repr__ da yazın.

__eq__: iki nesne ne zaman eşit?

C’de iki struct değişkenini == ile karşılaştıramazdınız, derleyici hata verirdi. Kodları karşılaştıran same_product(a, b) gibi bir fonksiyon yazardınız. Python’da == her nesnede çalışır. Ama sınıfta bir kural yazmadıysanız neye bakar? Aşağıda kodu aynı olan iki ayrı ürün var. İki satır ne yazar?

Sınıfta __eq__ yoksa == da is gibi kimliğe bakar: iki ad aynı nesneyi göstermiyorsa sonuç False olur. Kodu aynı ürünleri eşit saymak için kuralı __eq__ metoduna yazarız. isinstance(other, Product) (11. hafta) karşıdaki nesnenin bir Product olup olmadığını sorar. Bu kez iki satır ne yazar?

== artık bizim koyduğumuz kurala bakar. is ise hâlâ iki adın aynı nesneyi gösterip göstermediğini, yani kimliği sorar. other bir Product değilse metot NotImplemented döndürür. Bu özel değer Python’a “bu karşılaştırmayı ben yapamıyorum” der. Python da başka bir yol bulamazsa sonucu False sayar: Product("P1") == "P1" satırı False verir.

NoteEşitlik tasarım kararıdır

Kodu aynı iki ürün aynı ürün müdür? Bunu Python bilemez, kararı programın neyi modellediğine bakarak siz verirsiniz. Bir kitapçının satış programında aynı ISBN aynı kitap demektir, ama bir kütüphanede aynı kitabın iki ayrı kopyası da olabilir. Product için ürün kodu, Book için ISBN, Student için öğrenci numarası iki nesneyi “aynı” saymanın ölçütü olabilir. Bu karar yalnız == işlecini etkilemez: listede in ile arama ve remove() ile silme de aynı eşitlik kuralını kullanır.

NotImplemented döndürülür, NotImplementedError yükseltilir

__eq__ içindeki NotImplemented ile 10. haftada gördüğünüz NotImplementedError istisnasının (alt sınıfın yazması gereken metodu işaretleyen istisna) adları benzer, ama işleri farklıdır:

Ad Türü Nasıl kullanılır? Nerede kullanılır?
NotImplemented Python’da tek bir tane bulunan özel değer return ile döndürülür __eq__ gibi iki nesneyi karşılaştıran metotta “bu türle karşılaştıramam” demek için
NotImplementedError istisna sınıfı raise ile yükseltilir Alt sınıfın yazması gereken metodu üst sınıfta işaretlemek için

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

raise NotImplemented satırı TypeError verir, çünkü NotImplemented bir istisna değildir. return NotImplementedError(...) ise hata vermez: metot bir istisna nesnesini değer olarak döndürür ve program yanlış değerle çalışmayı sürdürür. Alt sınıf metodu yazmayı unuttuğunda bunu görmek için istisnayı raise ile yükseltin.

__eq__ ve hash sözleşmesi

Gün içinde kaç farklı ürün satıldığını saymak istiyoruz. 3. haftada tekrarları atmak için küme kullanmıştık. Aynı haftanın “Sözlük anahtarı neden her şey olamaz?” başlığında küme elemanının ve sözlük anahtarının hashlenebilir olması gerektiğini görmüştünüz (hash: küme ve sözlüğün bir değeri hızlı bulmak için o değerden hesapladığı sayı). Aşağıdaki Product sınıfında __eq__ var. Satılan ürünlerin listesini kümeye çevirince ne olur?

Eşit sayılan iki nesnenin hash değeri de aynı olmalıdır. Bu kurala hash sözleşmesi denir. Python bu kuralı koruyabilmek için __eq__ yazıp __hash__ yazmayan sınıfın hash’ini kaldırır. O sınıfın nesneleri küme elemanı ya da sözlük anahtarı olamaz, denerseniz TypeError alırsınız. Hata mesajında unhashable type geçer.

Bu derste __hash__ yazmıyorsunuz. Farklı ürünleri saymak için kümeye nesnenin kendisini değil, hashlenebilir olan kodunu koyabilirsiniz. Hücre ne yazar?

Değiştirilebilir (mutable) bir nesneyi küme elemanı ya da sözlük anahtarı yapmak istediğinizde önce eşitliğin ve hash’in nasıl tanımlanacağını düşünün. Çoğu zaman nesnenin kodunu ya da numarasını kullanmak yeter.

Hatayı kendi adıyla bildirmek

C’de satış fonksiyonu stok yetmeyince -1 gibi bir hata kodu döndürürdü. Çağıran kod bu değeri denetlemeyi unutursa program hiçbir şey olmamış gibi devam ederdi. 5. haftada Python’da hatayı raise ValueError(...) ile yükseltmeyi gördünüz. Ama stok yetmediğinde de ValueError yükseltilirse çağıran kod bu hatayı geçersiz bir miktardan ayıramaz. Kendi istisna sınıfınızı yazarsanız hata kendi adıyla gelir. İstisna sınıfı da bir sınıftır: Exception sınıfından türetilir (10. haftadaki kalıtım) ve gövdesine çoğu zaman yalnız pass yazılır.

Aşağıdaki hücrede 5 klavye satılmak isteniyor, stokta 2 tane var. Hücre ne yazar? Satış tamam satırı yazılır mı?

raise çalıştığı anda sell() yarıda kalır ve akış uygun except bloğuna geçer. try içindeki sonraki satırlar çalışmaz. except OutOfStockError yalnız bu hatayı yakalar, geçersiz miktar için yükseltilen ValueError bu bloğa girmez.

sell() gibi bir metot hatayı çoğu zaman yükseltir. Onu kullanıcıyla konuşan kod, örneğin menüyü gösterip mesajı ekrana yazan kısım yakalar. Product hatayı kendi içinde print() ile yazıp geçerse çağıran kod satışın yapılmadığını anlayamaz.

keyboard.sell(5) çağrısında istenen miktar stoktan fazla olduğu için satışın reddedilip OutOfStockError yükseltildiğini gösteren diyagram.

Özel istisna

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

Programın konusuna özgü hatalar (stokta olmayan ürün, yetersiz bakiye gibi) için güvenli başlangıç Exception sınıfıdır:

class InsufficientBalanceError(Exception):
    pass

Kendi istisnalarınızı doğrudan BaseException sınıfından türetmeyin. BaseException, KeyboardInterrupt (kullanıcının Ctrl+C’ye basması) ve SystemExit (programın sys.exit() ile kapanması) gibi programın akışını durduran istisnaların da köküdür.

Hatanız yerleşik bir hata türünün özel bir biçimiyse o türden türetebilirsiniz. Örneğin geçersiz bir sınav notu, geçersiz bir değerdir. Yani bir tür ValueError hatasıdır. Aşağıdaki except satırında InvalidScoreError değil, ValueError yazıyor. Hata yakalanır mı, hücre ne yazar?

InvalidScoreError bir ValueError’dır (10. haftadaki is-a ilişkisi). Bu yüzden çağıran kod hatayı except InvalidScoreError ile de, daha genel except ValueError ile de yakalayabilir. Hangi temel sınıfın doğru olduğunu hatanın anlamı belirler. Yalnızca özel sınıf yazmış olmak için katman katman istisna sınıfları kurmayın.

Soruya dönelim: beş iş Python’da

Bölümün başında mağaza programının beş işini C’de nasıl yapacağınızı sormuştuk. Python sınıflarındaki karşılıkları şöyle:

İş C’de ya da önceki haftalarda Bu haftaki Python yolu Dikkat
Ürünü P1 - Klavye diye yazdırmak Alanları printf ile yazan bir print_product() fonksiyonu Sınıfa __str__ yazılır, print(product) onu çağırır __str__ metni döndürür, kendisi yazdırmaz
Sepetteki ürünleri hata ayıklarken yazdırmak Diziyi döngüyle dolaşıp her alanı printf ile yazmak Sınıfa __repr__ yazılır, print(cart) her elemanda onu çağırır Liste elemanlarını repr ile yazar, yalnız __str__ yetmez
Kodu aynı iki ürünü aynı saymak Kodları strcmp ile karşılaştıran same_product(a, b) fonksiyonu. C’de a == b iki struct için derlenmez Sınıfa __eq__ yazılır, first == second onu çağırır __eq__ yoksa == kimliğe bakar. in ve remove() da __eq__ kuralını kullanır
Kaç farklı ürün satıldığını saymak C’de eklemeden önce diziyi taramak, 3. haftada kümeye eklemek Kümeye ürünün kodu eklenir: codes.add(product.code) __eq__ yazılmış sınıfın nesnesi kümeye konursa TypeError alınır
Stok yetmediğini çağırana bildirmek C’de -1 gibi bir hata kodu döndürmek, 5. haftada raise ValueError(...) raise OutOfStockError(...), çağıran except OutOfStockError ile yakalar İstisna sınıfı Exception’dan türetilir, BaseException’dan değil

Listenizi tabloyla karşılaştırın. C’deki yazdırma ve karşılaştırma fonksiyonlarını siz adlarıyla çağırıyordunuz. Python’da özel metodu siz çağırmazsınız: print(product) ya da first == second yazarsınız, metodu Python bulur. Dördüncü işte “kümeye eklerim” yazdıysanız 3. haftanın bilgisi doğruydu. Ama küme __eq__ yazılmış nesneleri kabul etmiyor, bu yüzden nesnenin yerine kodunu ekliyoruz. Son işte C’nin hata kodu ile Python’ın istisnası arasındaki fark şu: çağıran kod hata kodunu denetlemeyi unutursa program yanlış veriyle sürer. Yakalanmayan bir istisna ise programı durdurur ve traceback’te hatanın adını gösterir.

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 metodu kullandığını açıklayın.

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

Book(isbn, title, author) sınıfında eşitliği ISBN’e göre tanımlayın: ISBN’leri aynı olan iki kitap eşit sayılsın. Başka türden nesne geldiğinde NotImplemented döndürün. Bu alıştırmada __hash__ yazmanız beklenmez.

Sıra sizde: Üyelik modeli

Member(member_id, name, active) sınıfını yazın. Sınıfa __str__, __repr__ ve __eq__ ekleyin. member_id değerleri aynı olan iki üye eşit sayılsın. InactiveMemberError(Exception) adlı özel istisnayı tanımlayın ve üye aktif değilse bu istisnayı yükselten bir ensure_active() metodu ekleyin.

Çalışır kod görevi: member_id değeri aynı olan iki ayrı nesne oluşturup == ve is sonuçlarını doğrulayın. print(member) ile print([member]) arasındaki farkı gösterin. Pasif bir üyede ensure_active() çağrısının InactiveMemberError yükselttiğini test edin. Ayrıca NotImplemented ve NotImplementedError için birer doğru kullanım satırı yazı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 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; tanımlı tek temsil odur
  • B. Varsayılan temsili verir; repr() __str__ metoduna bakmaz
  • C. AttributeError yükseltir; __repr__ tanımlı değildir
  • D. Boş metin verir
  • 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 [Product('P1')]
  • E. Ürün P1 ve [<__main__.Product object at 0x...>]

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

  • A. print() listeleri hiçbir zaman biçimlendirmez
  • B. __str__ metodunun adı __string__ olmalıdır
  • C. Liste, elemanlarını gösterirken repr temsilini kullanı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 False
  • B. True ve True
  • 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üreyip neyi devraldığı
  • C. __hash__ metodunun kaç bit döndüreceği
  • D. İki nesnenin hangi kurala göre “aynı” sayılacağı
  • E. Sınıfta karşılaştırılacak kaç nitelik bulunduğu

Madde 7. Aşağıdaki program çalıştırıldığında ne olur?

class Shape:
    def area(self):
        raise NotImplementedError("Alt sınıf hesaplamalı")

class Square(Shape):
    def __init__(self, side):
        self.side = side

s = Square(3)
print("Oluşturuldu")
print(s.area())
  • A. Hiçbir şey yazdırılmaz, Square(3) satırında hata yükselir
  • B. Oluşturuldu, ardından area() sonucu olarak None yazdırılır
  • C. Oluşturuldu, ardından NotImplemented yazdırılır
  • D. Oluşturuldu, ardından 9 yazdırılır
  • E. Oluşturuldu yazdırılır, ardından NotImplementedError yükselir

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. Python kendiliğinden uygun bir __hash__ üretir
  • B. Hashlenemez olur; set elemanı ya da dict anahtarı olamaz
  • C. Sınıftan nesne oluşturulamaz
  • D. Hiçbir şey olmaz; hash davranışı __eq__ tanımından etkilenmez
  • E. Örnekler karşılaştırılamaz hâle gelir

Madde 10. Programın konusuna özgü bir hata (örneğin yetersiz bakiye) için özel istisna tanımlarken hangi temel sınıf uygundur?

  • A. Exception; kendi hatalarınız için güvenli başlangıçtır
  • B. object; Python’daki her sınıfın atasıdır
  • C. NotImplementedError; işlemin yapılamadığını bildirir
  • D. SystemExit; hata olunca programı durdurur
  • E. BaseException; bütün istisnaların köküdür

Madde 11. class InvalidScoreError(ValueError): pass yazmanın, Exception sınıfından türetmeye göre kazancı nedir?

  • A. Hata mesajını yazmak gerekmez, uygun mesajı Python kendisi oluşturur
  • B. Çağıran kod bu hatayı except ValueError ile de yakalayabilir
  • C. ValueError sınıfından türeyen istisna bir günlük dosyasına kendiliğinden yazılır
  • D. Python bu hatayı try bloğu yazılmasa da kendiliğinden yakalar
  • E. İstisna, Exception sınıfından türetilene göre daha hızlı yükseltilir

Madde 1. Doğru: C. Sınıfta __str__ tanımlı olduğu için print() onun döndürdüğü metni yazar.

  • A yanlış: Nesnenin alanlarını gösterdiği için akla yatkın görünür. Ama bu bir __repr__ çıktısı biçimidir ve sınıfta __repr__ tanımlı değildir.
  • B yanlış: C’de bir struct doğrudan printf’e verilemediği için Python’da da hata beklenebilir. Ama print() her nesneyi alır ve onu metne çevirerek yazar.
  • D yanlış: Nesneyi yazdırınca bu çıktıyı daha önce gördüyseniz yine bunu beklersiniz. Ama varsayılan temsil yalnız sınıfta __str__ de __repr__ de yoksa görünür. Burada __str__ var.
  • E yanlış: __str__ içinde print çağrılmadığı için ekrana bir şey çıkmayacağı sanılabilir. Ama __str__ metni döndürür, print de o metni yazar.

Madde 2. Doğru: B. Yerine geçme tek yönlüdür: __str__ yoksa str() __repr__ sonucunu kullanabilir; ama repr() hiçbir zaman __str__ metoduna bakmaz. Yalnız __str__ tanımlamak repr davranışını değiştirmez.

  • A yanlış: Sınıfta tek temsil metodu olduğu için Python’ın onu her yerde kullanacağı sanılabilir. Ama repr kendi varsayılanında kalır.
  • C yanlış: Sınıfta __repr__ yazılmadığı için metodun eksik olduğu düşünülebilir. Ama varsayılan __repr__ her sınıfta bulunur.
  • D yanlış: Temsil yazılmadığı için boş sonuç beklenebilir. Ama varsayılan temsil sınıfın adını ve nesnenin kimlik bilgisini (0x...) içerir.
  • E yanlış: Bir şey tanımlanmadığında sonuç None olacakmış gibi görünür. Ama repr() her zaman bir metin döndürür.

Madde 3. Doğru: D. print(product) kullanıcıya dönük str temsilini yazar. Liste ise kendini metne çevirirken her elemanın repr temsilini kullanır.

  • A yanlış: Sınıfta __repr__ olduğu için her yerde onun kullanılacağı düşünülebilir. Ama ilk satırda __str__ tanımlı olduğu için print() onu kullanır.
  • B yanlış: Listeyi de print() yazdırdığı için elemanların __str__ ile yazılacağı sanılabilir. Ama liste elemanlarını gösterirken str değil repr kullanır.
  • C yanlış: Listede tek eleman olduğu için listenin o elemanı olduğu gibi yazacağı sanılabilir. Ama liste köşeli parantezleri de yazar ve elemanın __str__ metodunu hiç çağırmaz.
  • E yanlış: Listelerin nesneleri hep varsayılan biçimde gösterdiğini gören öğrenci bunu bekler. Ama burada sınıfta __repr__ tanımlıdır, varsayılan temsil kullanılmaz.

Madde 4. Doğru: C. Liste kendini metne çevirirken her elemanın repr temsilini kullanır; __str__ bu sırada hiç çağrılmaz. print(obj) ile print([obj]) aynı metodu çağırmaz. Liste çıktısını değiştirmek için sınıfa __repr__ da yazmak gerekir.

  • A yanlış: Liste çıktısı köşeli parantezlerle ham göründüğü için print() onu hiç işlemiyormuş gibi görünür. Biçimlendirir, ama elemanları gösterirken repr kullanır.
  • B yanlış: Metot çağrılmıyormuş gibi göründüğü için adın yanlış yazıldığı sanılabilir. Doğru ad __str__tir; tek nesne yazdırılınca çalıştığı da görülür.
  • D yanlış: Çıktıyı veren liste olduğu için değişikliğin list sınıfında yapılması gerektiği düşünülebilir. Gerekli değildir; elemanın __repr__ metodu yeterlidir.
  • E yanlış: Tek nesne yazdırılınca çıktı değiştiği için sorun eleman sayısında sanılabilir. Ama eleman sayısıyla ilgisi yoktur.

Madde 5. Doğru: A. == artık bizim koyduğumuz kurala (ürün kodları aynı mı?) göre karar verir; is ise hâlâ kimliği sorar ve ortada iki ayrı nesne vardır.

  • B yanlış: Nesneler artık eşit sayıldığı için aynı nesne oldukları düşünülebilir. Ama __eq__ yazmak iki nesneyi tek nesne yapmaz, is hâlâ kimliği sorar.
  • C yanlış: İki ayrı nesne oluşturulduğu için == sonucunun da False olacağı sanılabilir. Ama __eq__ tanımlandığı için == kodlara bakar ve kodlar eşit olduğunda True döner.
  • D yanlış: == ile is karıştırılırsa bu sonuç beklenir. Oysa kuralı == kullanır, iki ayrı nesne için is False verir.
  • E yanlış: Metotta return NotImplemented satırı göründüğü için bu değerin yazdırılacağı sanılabilir. Ama other bir Product olduğu için o dal hiç çalışmaz.

Madde 6. Doğru: D. Eşitlik bir tasarım kararıdır ve programın konusuna göre verilir: Product için kod, Book için ISBN, Student için öğrenci numarası olabilir.

  • A yanlış: is kimliği sorduğu için eşitliğin de bellekle ilgili olduğu sanılabilir. Ama bellek yerleşimiyle ilgisi yoktur.
  • B yanlış: Metotların üst sınıftan devralındığını bilen öğrenci eşitliğin de oradan geleceğini düşünebilir. Ama kalıtım eşitlik kuralını belirlemez.
  • C yanlış: __eq__ ile __hash__ birlikte anıldığı için hash ayrıntısı öncelikli görünebilir. Bu bir uygulama ayrıntısıdır; önce eşitlik kuralı belirlenir.
  • E yanlış: Eşitlik “bütün nitelikler aynı mı?” diye düşünülünce nitelik sayısı önemli görünür. Ama nitelik sayısı kuralı belirlemez; kural, nesneyi hangi niteliğin tanıttığına bağlıdır.

Madde 7. Doğru: E. Square, area metodunu yeniden yazmayı (override) unutmuş. Python bunu nesne oluşturulurken denetlemediği için Square(3) sorunsuz çalışır. s.area() çağrılınca üst sınıftaki gövde çalışır ve NotImplementedError açıkça ortaya çıkar.

  • A yanlış: Eksik metot nesnenin kendisini eksik bırakıyormuş gibi görünebilir. Ama NotImplementedError yalnız metot çağrılınca yükselir; nesne oluşturmak onu tetiklemez.
  • B yanlış: area() alt sınıfta yazılmadığı için bir şey döndürmeyeceği sanılabilir. Ama metot None döndürmez, istisna yükseltir. None ancak gövde pass ile boş bırakılsaydı görülürdü.
  • C yanlış: NotImplemented ile NotImplementedError adları birbirine çok benzer. Ama raise bir değer döndürmez, istisna yükseltir; program print çağrısına ulaşamaz.
  • D yanlış: Kare ve kenar uzunluğu verildiği için alanın hesaplanacağı düşünülebilir. Ama alan hesabı hiçbir yerde yazılmadı; Python side niteliğinden alanı kendisi çıkarmaz.

Madde 8. Doğru: E. NotImplementedError, metodun bu sınıfta yazılmadığını bildiren istisnadır ve raise ile yükseltilir.

  • A yanlış: raise doğru anahtar sözcük olduğu için ad da doğru sanılabilir. Ama NotImplemented bir istisna değil, özel bir değerdir. Bu satır TypeError verir.
  • B yanlış: Gövdesi boş metot “burada henüz bir şey yok” der gibi görünür. Ama metot hata vermeden None döndürür ve alt sınıfın metodu yazmadığı fark edilmez.
  • C yanlış: __eq__ içinde bu satırı gören öğrenci onu her “yazılmadı” durumu için kullanabilir. Ama return NotImplemented yalnız __eq__ gibi iki nesneyi karşılaştıran metotlarda kullanılır.
  • D yanlış: Doğru istisna adı yazıldığı için kabul edilebilir görünür. Ama istisna return ile döndürülürse hata vermez, metot bir nesne döndürür ve program yanlış değerle devam eder. İstisna raise ile yükseltilir.

Madde 9. Doğru: B. Eşit kabul edilen nesnelerin aynı hash değerine sahip olması gerekir. Python bu sözleşmeyi koruyabilmek için çoğu durumda varsayılan hash’i kaldırır; örnekler set elemanı ya da dict anahtarı olarak kullanılamaz.

  • A yanlış: Python varsayılan __repr__ verdiği için uygun bir __hash__ da yazacağı sanılabilir. Ama Python __hash__ metodunu __eq__ kuralına göre kendiliğinden yazmaz.
  • C yanlış: Eksik bir özel metot sınıfı kullanılamaz hâle getiriyormuş gibi görünebilir. Ama nesne oluşturma etkilenmez.
  • D yanlış: __eq__ yalnız == işlecini ilgilendiriyormuş gibi görünür. Ama hash davranışı da etkilenir; varsayılan __hash__ kaldırılır.
  • E yanlış: Sorun eşitlik metoduyla başladığı için == işleminin bozulduğu sanılabilir. Ama == çalışır; kaybolan hash davranışıdır.

Madde 10. Doğru: A. Programın konusuna özgü hatalar Exception sınıfından türetilir. class InsufficientBalanceError(Exception): pass yaygın ve güvenli kalıptır.

  • B yanlış: Her sınıf object sınıfından türediği için en temel seçim gibi görünür. Ama yalnız object sınıfından türeyen bir sınıf istisna olarak yükseltilemez.
  • C yanlış: Adı işin yapılamadığını çağrıştırdığı için yetersiz bakiyeye uyuyor gibi görünebilir. Ama bu istisna metodun yazılmadığını bildirir; bakiye ya da not hatası gibi konuya özgü hatalar için değildir.
  • D yanlış: Hata olunca programın durması gerektiği düşünülürse bu sınıf akla gelebilir. Ama SystemExit programı kapatma isteğini bildirir; çağıran kod yetersiz bakiyeyi yakalayıp çalışmayı sürdürebilmelidir.
  • E yanlış: Bütün istisnaların kökü olduğu için en kapsamlı seçim gibi görünür. Ama BaseException, KeyboardInterrupt ve SystemExit gibi programın akışını durduran istisnaların da köküdür; kendi hatalarınız doğrudan buradan türetilmez.

Madde 11. Doğru: B. InvalidScoreError bir ValueError’dır. Çağıran kod hatayı ister except InvalidScoreError ile, ister daha genel except ValueError ile yakalar. Geçersiz sınav notu zaten geçersiz bir değer olduğu için bu temel sınıf hatanın anlamına uyar. Yine de yalnızca özel sınıf yazmış olmak için katman katman istisna sınıfı kurulmamalıdır.

  • A yanlış: Yerleşik hataların hazır mesajları varmış gibi göründüğü için bu beklenebilir. Ama mesajı istisnayı yükseltirken siz verirsiniz: raise InvalidScoreError("Geçersiz not: 120").
  • C yanlış: Yerleşik bir türden türetmek ek bir özellik kazandırıyormuş gibi görünebilir. Ama Python istisnaları kendiliğinden hiçbir dosyaya yazmaz. Günlük tutmak ayrı bir iştir.
  • D yanlış: ValueError Python’ın tanıdığı yerleşik bir hata olduğu için Python’ın onu kendisi çözeceği sanılabilir. Ama yakalamak yine çağıran kodun except bloğunun işidir. Yakalanmayan istisna programı durdurur.
  • E yanlış: Daha özel bir temel sınıf seçmek bir tür iyileştirme gibi görünebilir. Ama temel sınıf hızı değiştirmez. Seçim hatanın anlamına göre yapılır.

Tek sayfa özet

  • Özel metotları siz çağırmazsınız. print(), repr() ya da == kullanıldığında Python çağırır.
  • Sınıfta temsil metodu yoksa print() varsayılan temsili yazar: <__main__.Product object at 0x...>.
  • __str__ kullanıcıya, __repr__ geliştiriciye gösterilecek metni döndürür. __str__ yoksa print() __repr__ sonucunu kullanır, tersi olmaz.
  • Listeler elemanlarını gösterirken repr kullanır. Liste çıktısının okunur olması için sınıfa __repr__ yazın.
  • Sınıfta __eq__ yoksa == kimliğe bakar. __eq__, iki nesnenin ne zaman “aynı” sayılacağını programın konusuna göre sizin belirlediğiniz kurala bağlar. is yine kimliği sorar.
  • __eq__ başka türden bir nesneyle karşılaşınca NotImplemented döndürür. Alt sınıfın yazması gereken metot üst sınıfta raise NotImplementedError ile işaretlenir. Alt sınıf bu metodu yazmayı unutursa metot çağrıldığı anda NotImplementedError çıkar.
  • Eşit nesnelerin hash değeri aynı olmalıdır. __eq__ yazıp __hash__ yazmayan sınıfın nesneleri küme elemanı ya da sözlük anahtarı olamaz. Kümeye nesnenin yerine kodunu koyabilirsiniz.
  • Hata kodu döndürmek yerine istisna yükseltin. Metot yükseltir, kullanıcıyla konuşan kod yakalar.
  • Özel istisnalar çoğunlukla Exception sınıfından türetilir. Hata yerleşik bir türün özel biçimiyse (InvalidScoreError ile ValueError gibi) o türden 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ını gösterirken repr() kullandığını belirler.
  • __eq__ ile programın konusuna uygun bir eşitlik kuralı yazar ve bunu nesne kimliğinden (is) ayırır.
  • Alt sınıfın uygulaması gereken metodu raise NotImplementedError ile işaretler ve alt sınıf bu metodu yazmayı unutursa ne olacağını açıklar.
  • __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