Ö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ı
__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 NotImplementedErrorile 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çindereturn NotImplementedkalıbının ayrıntısı__hash__yazımı__lt__,__add__gibi diğer özel metotlar vefunctools.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:
- Bir ürünü ekrana
P1 - Klavyebiçiminde yazdırmak - Hata ayıklarken sepetteki ürünleri bütün alanlarıyla ekrana yazdırmak
- Kodu aynı olan iki ürün kaydını aynı ürün saymak
- Gün içinde kaç farklı ürün satıldığını saymak
- 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:
print(obj)vestr(obj)önce__str__metoduna bakar. Sınıfta__str__yoksa__repr__sonucunu kullanır.repr(obj)yalnız__repr__metoduna bakar. Yalnız__str__tanımlarsanızrepr(obj)varsayılan temsilde kalır:<__main__.Product object at 0x...>.- Liste gibi koleksiyonlar elemanlarını gösterirken
repr()kullanır. Bu yüzdenprint([product]),__str__yerine__repr__sonucunu gösterir.
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.
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 NotImplementedclass Shape:
def area(self):
raise NotImplementedError("Alt sınıf hesaplamalı")return 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.
Ö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):
passKendi 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.
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__yoksaprint()__repr__sonucunu kullanır, tersi olmaz.- Listeler elemanlarını gösterirken
reprkullanı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.isyine kimliği sorar. __eq__başka türden bir nesneyle karşılaşıncaNotImplementeddöndürür. Alt sınıfın yazması gereken metot üst sınıftaraise NotImplementedErrorile işaretlenir. Alt sınıf bu metodu yazmayı unutursa metot çağrıldığı andaNotImplementedErrorçı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
Exceptionsınıfından türetilir. Hata yerleşik bir türün özel biçimiyse (InvalidScoreErrorileValueErrorgibi) 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 NotImplementedErrorile 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.