6 - Özel Metotlar ve Operatör Aşırı Yükleme
2026
Geçen hafta polimorfizmde şunu görmüştük:
Bu hafta bu fikri Python’ın kendisine taşıyoruz:
Python da bir istemcidir.
print(obj), len(obj), obj[0], obj1 + obj2 gibi ifadelerle sınıfınızdan belli davranışlar bekler.
Bu dersin sonunda şunları ayırt edebiliyor olmanızı hedefliyoruz:
__str__ ile __repr__ arasındaki farkı__len__, __getitem__, __contains__, __iter__ gibi metotların hangi davranışları açtığınıNotImplemented döndürmenin ne zaman doğru tercih olduğunuBu konu önceki iki haftanın doğal devamıdır:
Kısaca:
Özel metotlar, Python ile aranızdaki sözleşmedir.
Bu noktada teknik ayrıntıyı değil, önce temel fikri görmek yeterli:
Bu davranışları bir sınıfa siz de kazandırabilirsiniz.
Yani bu dersin ilk sorusu şudur:
“Kendi sınıfımı Python’ın yerleşik davranışlarıyla nasıl uyumlu hale getiririm?”
| Yazdığımız ifade | Python’ın beklediği davranış |
|---|---|
print(obj) |
__str__ |
repr(obj) |
__repr__ |
len(obj) |
__len__ |
obj[key] |
__getitem__ |
obj[key] = value |
__setitem__ |
del obj[key] |
__delitem__ |
x in obj |
__contains__ |
for x in obj |
__iter__ |
obj1 + obj2 |
__add__ |
Bugün ağırlıklı olarak bu çekirdek davranışlara odaklanacağız.
__str__ ve __repr____str__: son kullanıcıya gösterilecek daha okunabilir temsil__repr__: geliştirici için daha açık, daha kesin temsilPratik kural:
str(obj) → “ekranda nasıl göreyim?”repr(obj) → “bu nesnenin içeriğini nasıl daha net anlayayım?”__repr__, mümkünse nesneyi yeniden üretmeye yakın bir gösterim verir; ama bu her zaman zorunlu değildir.
__str__ ve __repr__class Ders:
def __init__(self, kod, ad, kredi):
self.kod = kod
self.ad = ad
self.kredi = kredi
def __str__(self):
return f"{self.kod} - {self.ad} ({self.kredi} kredi)"
def __repr__(self):
return f"Ders('{self.kod}', '{self.ad}', {self.kredi})"
ders = Ders("NTP201", "Nesne Tabanlı Programlama 2", 4)
print(ders)
print(repr(ders))Burada henüz tüm özel metotları bilmenize gerek yok.
Önce şu gözlemi yapın:
print(ders) dediğimizde Python nesneyi ekrana nasıl uygun biçimde yazacağını arar.repr(ders) dediğimizde daha teknik bir temsil ister.Yani özel metotların mantığı şudur:
Python bir davranış bekler, siz o davranışı sınıfınıza eklersiniz.
__len__: Nesnenin “Kaç Tane?” Sorusuna Cevabı__len__, nesnenin büyüklüğünü veya eleman sayısını döndürmek için kullanılır.
Önemli nokta:
__getitem__, __setitem__, __delitem__Bu metotlar nesneye dizi ya da sözlük benzeri erişim kazandırır:
Bu üç metot aynı ailenin parçalarıdır; ancak çoğu durumda ilk anlamamız gereken davranış okuma davranışıdır.
obj[key]obj[key] = valuedel obj[key]Ama burada kritik soru şudur:
Nesneniz gerçekten indekslenebilir ya da anahtarla erişilebilir bir yapı mı?
Değilse sırf gösteriş için bu metotları eklemek iyi tasarım değildir.
Sözlük benzeri bir yapı tasarlıyorsanız, bulunamayan anahtarda çoğu zaman KeyError beklenir.
Bu yüzden her durumda sessizce None ya da “öğrenci yok” döndürmek risklidir; çünkü Python’ın standart davranışı hakkında yanlış bir beklenti oluşturabilir.
İki yaklaşım vardır:
class NotDefteri:
def __init__(self):
self.notlar = {}
def __getitem__(self, hafta_no):
return self.notlar[hafta_no]
def __setitem__(self, hafta_no, icerik):
self.notlar[hafta_no] = icerik
def __delitem__(self, hafta_no):
del self.notlar[hafta_no]
defter = NotDefteri()
defter[1] = "Sınıflar"
defter[2] = "Kalıtım"
print(defter[1])
del defter[2]defter[99] ifadesi çalıştığında ne olmalı?
NoneKeyErrorGenel olarak beklenen cevap çoğu durumda şudur:
“Nesnenin hangi davranışı taklit ettiğine bağlıdır; sözlük gibi ise KeyError beklenir.”
Operatör aşırı yükleme, sınıfınıza +, -, ==, < gibi operatörlerle anlamlı bir davranış kazandırır.
Buradaki ana ilke şudur:
Bir operatörü sadece gerçekten doğal ve sezgisel ise aşırı yükleyin.
Örneğin:
+ ile toplamak çoğu durumda anlamsızdır.| Operatör | Özel metot | Beklenen anlam |
|---|---|---|
+ |
__add__ |
toplama / birleştirme |
- |
__sub__ |
çıkarma |
* |
__mul__ |
çarpma / tekrar |
== |
__eq__ |
eşitlik |
< |
__lt__ |
sıralama karşılaştırması |
Aynı mantıkla başka operatörler de tanımlanabilir; ancak bugün çekirdekte en sık karşılaşılan davranışları öne çıkarıyoruz.
class Vektor:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vektor({self.x}, {self.y})"
def __add__(self, other):
if not isinstance(other, Vektor):
return NotImplemented
return Vektor(self.x + other.x, self.y + other.y)
def __sub__(self, other):
if not isinstance(other, Vektor):
return NotImplemented
return Vektor(self.x - other.x, self.y - other.y)NotImplemented?Bu detay önemlidir.
Şöyle yazmak teknik olarak zayıftır:
Çünkü other beklediğiniz tipte değilse hata kontrolsüz biçimde patlar.
Daha doğru yaklaşım:
NotImplemented döndürmekTypeError oluşmasını sağlamakBu, öğrencinin “çalışan örnek” ile “sağlam tasarım” arasındaki farkı görmesi için önemlidir.
class Para:
def __init__(self, miktar):
self.miktar = miktar
def __repr__(self):
return f"Para({self.miktar})"
def __add__(self, other):
if isinstance(other, Para):
return Para(self.miktar + other.miktar)
return NotImplemented
def __mul__(self, katsayi):
if not isinstance(katsayi, (int, float)):
return NotImplemented
return Para(self.miktar * katsayi)
def __str__(self):
return f"{self.miktar:.2f} TL"Karşılaştırma metotları, nesnelerin sıralanması ve eşitlik kontrolü için kullanılır.
Ama yine aynı ilke geçerlidir:
Neye göre eşitlik kurduğunuzu açık seçik belirlemelisiniz.
Soru:
Yanlış seçilen eşitlik tanımı, ileride ciddi mantık hataları üretir.
class Ogrenci:
def __init__(self, no, ad, ortalama):
self.no = no
self.ad = ad
self.ortalama = ortalama
def __repr__(self):
return f"Ogrenci({self.no}, '{self.ad}', {self.ortalama})"
def __eq__(self, other):
if not isinstance(other, Ogrenci):
return NotImplemented
return self.no == other.no
def __lt__(self, other):
if not isinstance(other, Ogrenci):
return NotImplemented
return self.ortalama < other.ortalamaBu örnekte:
Bu kararın bilinçli verilmesi gerekir, eşitlik ve sıralama ölçütleri farklı olduğunda tasarım kararının sonuçları dikkatle düşünülmelidir.
__contains__: in OperatörüBir nesnenin içinde eleman var mı sorusuna cevap vermek için kullanılır.
class Kurs:
def __init__(self, ad, ogrenciler=None):
self.ad = ad
self.ogrenciler = ogrenciler or []
def __contains__(self, ogrenci):
return ogrenci in self.ogrenciler
kurs = Kurs("Python", ["Ali", "Ayşe"])
print("Ali" in kurs)
print("Mehmet" in kurs)İleri not: __contains__ en açık ve doğrudan çözümdür. Bu metot yoksa Python bazı durumlarda iterasyon üzerinden de üyelik kontrolü yapabilir.
__iter__: Dolaşılabilir NesnelerBir nesneyi for döngüsünde kullanmak istiyorsanız, iterasyon protokolüne uygun davranmalıdır.
İlk yaklaşım olarak en temiz seçenek çoğu zaman şudur:
Var olan bir koleksiyonun iterator’unu döndürmek.
class DersProgrami:
def __init__(self, dersler):
self.dersler = dersler
def __iter__(self):
return iter(self.dersler)
program = DersProgrami(["Matematik", "Programlama", "Fizik"])
for ders in program:
print(ders)Bu örnekte __iter__, var olan listenin iterator’unu döndürür. Böylece nesne doğrudan for döngüsünde kullanılabilir.
Her desteklenebilen şey, kullanılmalı anlamına gelmez.
Operatör aşırı yüklemeyin.
Örnek:
sepet.birlestir(diger_sepet) çoğu zaman sepet + diger_sepetten daha açık olabilir.__getitem__ içinde standart davranıştan sapıp bunu belirtmemek__add__ ve __eq__ içinde tür kontrolünü atlamak__repr__ ile __str__ farkını belirsiz bırakmakBir OkumaListesi sınıfı yazın.
Sınıfın içinde bir kitap listesi tutulacak.
İstenen davranışlar:
len(liste) toplam kitap sayısını versinkitap_adi in liste ifadesi çalışsınfor kitap in liste ile dolaşılabilsin## Alıştırma 1: İskelet ve Beklenen Kullanım {.smaller}
Başlangıç iskeleti:
Beklenen kullanım:
Bu soruda üç davranış gerekiyor:
Her davranış için hangi özel metodun gerektiğini siz belirleyin.
Çözüm sırası olarak önce uzunluk, sonra üyelik, en son dolaşım davranışını düşünmek işinizi kolaylaştırır.
Bir TelefonRehberi sınıfı yazın.
Bu sınıf, kişi adına göre telefon numarası saklasın.
İstenen davranışlar:
rehber["Ali"] = "555-1234" şeklinde veri eklenebilsinprint(rehber["Ali"]) ile veri okunabilsindel rehber["Ali"] ile veri silinebilsin## Alıştırma 2: İskelet ve Beklenen Kullanım {.smaller}
Başlangıç iskeleti:
Beklenen kullanım:
Bu soru, sözlük benzeri davranış tasarlama sorusudur.
Burada düşünülmesi gereken nokta:
Rehberde olmayan bir kişi istendiğinde ne olmalı?
İki seçenek konuşulabilir:
KeyError üretmekEğer yapı sözlük gibi davranıyorsa, varsayılan beklenti çoğu zaman KeyError olacaktır.
Bir Nokta sınıfı yazın.
İstenen davranışlar:
n1 + n2 yeni bir nokta üretsinn1 == n2 karşılaştırması çalışsınprint(n1) daha okunabilir bir çıktı versin## Alıştırma 3: İskelet ve Beklenen Kullanım {.smaller}
Başlangıç iskeleti:
Beklenen kullanım:
Bu soruda üç farklı davranış bir araya geliyor:
Ek kontrol sorusu:
n1 + 5 ifadesi yazılırsa ne olmalı?
Burada uygun tür kontrolü yapıp NotImplemented döndürmek daha sağlam bir çözümdür.
NotImplemented önemli ayrıntılardır.Bu haftanın ana sorusu şuydu:
“Bu nesne Python’a hangi doğal davranışları sunmalı?”
Son karar için şu üç soruyu sorun: