6 - Özel Metotlar ve Operatör Aşırı Yükleme
2026
Geçen hafta polimorfizmde aynı çağrının farklı nesnelerde farklı davranabildiğini gördük.
Bu hafta bu fikri Python’ın yerleşik işlemlerine taşıyoruz.
Python da nesnelerinizden bazı davranışlar bekler.
Örneğin:
print(obj)len(obj)obj[0]obj1 + obj2Bu ifadelerin çalışabilmesi için sınıfınızın Python’ın veri modeline uygun davranması gerekir.
Bu dersin odağı şudur:
Kendi sınıfımı Python’ın yerleşik davranışlarıyla nasıl uyumlu hale getiririm?
Bu amaçla özellikle şu başlıklara bakacağız:
Amaç; hangi davranışın ne zaman anlamlı olduğunu görmek.
__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))Bu örnekte henüz bütün özel metotları bilmeniz gerekmiyor.
Şimdilik şu fikri görmek yeterlidir:
print(ders) yazdığımızda Python nesneyi nasıl göstereceğini arar.repr(ders) yazdığımızda daha teknik bir temsil ister.Yani temel mantık şudur:
Python bir davranış bekler, siz o davranışı sınıfınıza eklersiniz.
__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 verebilir; ancak asıl amaç daha teknik ve daha az belirsiz bir temsil sunmaktır.
__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 noktalar:
Şu ana kadar iki örnek gördük:
Aslında Python başka birçok ifadede de benzer biçimde özel metotlar arar.
| Yazdığımız ifade | Temel olarak ilişkili metot |
|---|---|
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 bu temel eşleşmelerin mantığına odaklanacağız.
__getitem__, __setitem__, __delitem__Bu metotlar nesneye dizi ya da sözlük benzeri erişim kazandırır:
obj[key]obj[key] = valuedel obj[key]Bu üç metot aynı ailenin parçalarıdır; ancak çoğu durumda ilk anlamamız gereken davranış okuma davranışıdır.
Kritik soru şudur:
Nesneniz gerçekten indekslenebilir ya da anahtarla erişilebilir bir yapı mı?
Değilse sırf konu işlendi diye 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 şudur:
“Nesnenin hangi davranışı taklit ettiğine bağlıdır; sözlük gibi ise KeyError beklenir.”
__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 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.
Başlangıç düzeyinde en temiz yaklaşım ç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.
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 en sık karşılaşılan davranışlara odaklanı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ürmek,TypeError oluşmasını sağlamak.Bu, “çalışan örnek” ile “sağlam tasarım” arasındaki farkı görmek 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ı üretebilir.
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 örnek özellikle tartışma üretmek için seçilmiştir. Gerçek sistemlerde eşitlik ve sıralama ölçütlerinin birlikte nasıl sonuçlar doğurduğu dikkatle düşünülmelidir.
Her desteklenebilen şey, kullanılmalı anlamına gelmez.
operatör aşırı yüklemeyin.
Örnek:
sepet.birlestir(diger_sepet) çoğu zaman sepet + diger_sepet ifadesinden 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ırakmakŞimdi üç farklı davranış ailesi için kısa uygulamalar yapacağız:
Amaç sadece kodu çalıştırmak değil, hangi davranışın hangi özel metotla tasarlandığını görmek.
Bir 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şılabilsinBaş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 silinebilsinBaş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ı versinBaş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: