print(obj) yazdığınızda ekrana ne çıkacağını, obj1 == obj2 karşılaştırmasının ne zaman True vereceğini ya da nesnenin bir listenin içinde nasıl görüneceğini 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. Hangi işlemin hangi özel metodu çağıracağını 6. haftada tanıdığınız Python veri modeli belirler. Bu hafta ayrıca programın konusuna özgü hataları, örneğin “yetersiz bakiye” hatasını, kendi istisna sınıflarınızla bildireceksiniz.
__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
NoteSınav dışı (bilgi amaçlı)
__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; 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? Bunu Python bilemez. Kararı programın ele aldığı konuya, yani problem alanına 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. Bu karar yalnız == işlecini etkilemez. Listede in ile arama ve remove() ile silme de aynı eşitlik tanımını kullanır.
__str__: kullanıcıya dönük temsil
Sınıfta __str__ tanımlıysa print(product) ve str(product) onun döndürdüğü metni kullanır. 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
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.
Hangi işlem hangi metodu çağırır?
Pratikte şu üç kurala dikkat edin:
Sınıfta __str__ yok ama __repr__ varsa str(obj) ve print(obj) geliştirici temsilini, yani __repr__ sonucunu kullanabilir.
Yalnız __str__ tanımlarsanız repr(obj) değişmez; <__main__.Product object at 0x...> biçimindeki varsayılan temsilde kalır.
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) nesnenin kullanıcıya dönük str temsilini yazar. print([obj]) ise listeyi yazdırır ve liste kendini metne çevirirken her elemanın repr temsilini kullanır. __str__ yazdığınız hâlde print(products) çıktısı değişmiyorsa nedeni çoğu zaman budur.
__eq__: iki nesne ne zaman eşit?
== artık bizim koyduğumuz kurala bakar: ürün kodları aynıysa True verir. is ise hâlâ iki adın aynı nesneyi gösterip göstermediğini, yani kimliği sorar. Burada iki ayrı nesne olduğu için False verir.
NoteEşitlik tasarım kararıdır
Product için ürün kodu, Book için ISBN, Student için öğrenci numarası iki nesneyi “aynı” saymanın ölçütü olabilir. __eq__ yazmadan önce bu kuralı problem alanına bakarak belirleyin.
NotImplemented ve NotImplementedError aynı şey değildir
Adları benzer ama işleri tamamen farklıdır:
Ad
Tür
Ne yapılır?
Tipik kullanım
NotImplemented
Python’da tek örneği olan özel bir değer
return ile döndürülür
__eq__ gibi iki nesneyi karşılaştıran metotta “bu türle karşılaştırmayı desteklemiyorum” demek
class Shape:def area(self):raiseNotImplementedError("Alt sınıf hesaplamalı")
Warningreturn NotImplementedError ve raise NotImplemented yazmayın
10. haftada NotImplementedError istisnasını raise ile yükselttiniz; bu hafta __eq__ içinde NotImplemented değerini return ile döndürüyorsunuz. İkisini karıştırmayın.
__eq__ ve hash sözleşmesi
3. haftada sözlük anahtarlarının ve küme elemanlarının hashlenebilir olması gerektiğini gördünüz. Eşit sayılan iki nesnenin hash değeri de aynı olmalıdır. Bir sınıfa __eq__ yazıp uygun bir __hash__ yazmazsanız o sınıfın nesneleri çoğu durumda hashlenemez olur. Bu derste __hash__ yazmıyorsunuz. Yalnız, değeri sonradan değişebilen (mutable) bir nesneyi set elemanı ya da dict anahtarı yapmadan önce eşitliğin ve hash’in nasıl tanımlanacağını düşünün.
Özel istisna hangi sınıftan türemeli?
Programın konusuna özgü hatalar (yetersiz bakiye, geçersiz sınav notu 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:
class InvalidScoreError(ValueError):pass
Bu durumda ç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.
Hatayı kendi adıyla bildirmek
withdraw() 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. Account hatayı kendi içinde print() ile yazıp geçerse çağıran kod para çekmenin başarısız olduğunu güvenilir biçimde anlayamaz.
Özel istisna
Alıştırma: __str__ ekle
returnf"{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’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 11.class InvalidScoreError(ValueError): pass yazmanın, Exception sınıfından türetmeye göre kazancı nedir?
A. Hata mesajı otomatik olarak üretilir
B. Çağıran kod ister InvalidScoreError ister daha genel ValueError olarak yakalayabilir
C.ValueError sınıfından türeyen istisna kendiliğinden günlüğe yazılır
D. Python hatayı kendiliğinden yakalar
E. İstisna daha hızlı yükseltilir
TipCevaplar ve gerekçeler
Madde 1. Doğru: C. Sınıfta __str__ tanımlı olduğu için print() onun döndürdüğü metni yazar.
A yanlış: Bu bir __repr__ çıktısı biçimidir; burada __repr__ tanımlı değildir.
B yanlış:print() her nesneyi alır ve onu metne çevirerek yazar.
D yanlış: Bu varsayılan temsildir; yalnız __str__ de __repr__ de 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: 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’un 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ış: İ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 elemanın __str__ metodunu hiç çağırmaz.
E yanlış: 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ış:__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: 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ış:NotImplemented bir istisna değildir; yükseltilemez.
B yanlış: Metot hata vermeden None döndürür; alt sınıfın metodu yazmadığı fark edilmez.
C yanlış:return NotImplemented, __eq__ gibi iki nesneyi karşılaştıran metotlarda kullanılır.
D yanlış: İstisna sınıfı döndürülmez; 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. Geçersiz sınav notu zaten geçersiz bir değerdir; ValueError sınıfından türetmek 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ış: Mesajı istisnayı yükseltirken siz verirsiniz.
C 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
__str__, __repr__ ve __eq__ gibi özel metotlar yazarsanız print(), repr() ve == sizin sınıfınızla da istediğiniz gibi çalışı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 hata sessiz kalmaz; metot çağrıldığı anda ortaya çıkar.
__eq__, iki nesnenin ne zaman “aynı” sayılacağını problem alanına göre sizin belirlediğiniz kurala bağlar. Hashlenebilirlik bununla ilişkili ayrı bir sözleşmedir: eşit nesnelerin hash değeri aynı olmalıdır.
Ö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.