Python Sınıflarında Sihirli Metotlar (Dunder)

Python üzerinde nesne yönelimli bir tasarım için sınıflar ve metot türlerine konusuna şurada biraz değinmiştim. Şimdi bir adım daha ileri gidip daha ileri seviye sınıflar tasarlamak için kaçınılmaz olan bazı özel metotlardan bahsedeyim.

Bu özel metotlar, Python üzerinde önceden tanımlı ve görevi olan metotlardır. Ortak özellikleri ise iki alt çizgi ile başlayıp bitiyor olmalar, isimlerini de buradan alıyorlar. (dunder double underscore).

Görevi olduğunu söyledik bu metotların, peki ne gibi görevler? Basit bir örnek olarak bize uzunluk veren len fonksiyonunu sınıfımızda kullanmak istiyorsak, yani len içine sınıfımızın bir örneğini koyunca bir değer dönmesini istiyorsak, sınıfımız içinde __len__ adında bir metot tanımlayıp uygun sayıyı döndürerek bunu sağlayabiliriz.

class LenYok:
    uzunluk = 5
    
lenyok = LenYok()

print(len(lenyok))
# TypeError: object of type 'LenYok' has no len()

class LenVar:
    uzunluk = 5
    
    def __len__(self):
        return self.uzunluk
    
lenvar = LenVar()

print(len(lenvar))
# 5

Örnekte kodlar yeterince açık. 2 sınıfımız var ve birinde __len__ dunder metodu tanımlı. len fonksiyonuna verdiği tepkileri ise hemen altlarına ekledim. Bir sınıf TypeError dönerken diğeri belirlediğimiz değeri dönüyor. Genel bazı dunder metotlara da tek tek değineyim.

__init__

Sınıfımızın oluşturulurken çağrılan metodu. Zaten biraz Python kodu okumuş herkesin karşılaştığı bir metot. Parametre olarak o örnek için kullanacağımız değişkenleri vererek sınıfımızı örneğine özelleştirebiliriz.

class Araba:
    def __init__(self, marka, renk):
        self.marka = marka
        self.renk = renk

araba = Araba('Honda', 'Siyah')
# marka ve renk değerlerimizi Araba sınıfı oluşturulduğu an tanımlanır

__str__

str fonksiyonuna sınıfınızın vereceği cevaptır. Yukarıdaki örneğe devam edersek;

class Araba:
    # öncesi

    def __str__(self):
        return('Bu {} renkli bir {} marka arabadır'.format(self.renk, self.marka))

araba = Araba('Honda', 'Siyah')
print(str(araba)) # ya da print(araba)
# Bu Siyah renkli bir Honda marka arabadır

__repr__

repr için bir cevap üretir. __str__ ile benzerdir fakat __str__ daha çok son kullanıcıya hitap eder, dönüşler okunabilir olmalıdır. __repr__ ise arka planda kalan loglama gibi tanımlar için kullanılır.

__len__, __getitem__

Araba örneğimize yolcuları da tanımladığımızı ve bu yolcuları bir döngü içine sokmamız gerektiği bir senaryomuz olsun. Burada for ve len gibi fonksiyonlar için mevcut sınıf TypeError verecektir. Başlıkta dunder metotlarla sınıfımıza bir döngü içinde ve örneğimiz üzerinde köşeli parantez kullanıldığında nası davranması gerektiğini söyleyebiliriz.

class Araba:
    yolcular = []
    # önceki
    def yolcu_ekle(self, yolcular):
        self.yolcular = yolcular
        
    def __len__(self):
        return len(self.yolcular)
    
    def __getitem__(self, position):
        return self.yolcular[position]

araba = Araba('Honda', 'Siyah')
araba.yolcu_ekle(['anne', 'baba', 'çocuk'])

print(araba[1]) # baba

for yolcu in araba:
    print(yolcu)
# anne
# baba
# çocuk

Burada __getitem__ aldığı position parametresi ile hangi sıradaki değeri dönmemiz gerektiğini bize söylüyor. Bu metot içinde ne döneceğimizi sınıfımızın işlevine kalmış bir şey, yolcu yerine arabanın lastik durumunu da dönebilirdik (örneğin basitliği açısından bunlar kullanılmıştır, Araba isimli bir sınıfın for içinde yolcuları dönmesi anlamlı ve kullanışlı olmayacaktır.)

__reversed__

Python içindeki reversed fonksiyonu için kullanılır, sıralı değişkenimizi ters sıralayarak geri dönmemiz beklenir.

__eq__, __lt__ …

Sınıfımıza ait iki örneği karşılaştırmak için bazı ifadeler kullanabiliriz, bu durumlar için de bir dunder metotları var. İki arabamız birbirine eşit mi bunu öğrenmek istediğimiz bir senaryoda;

class Araba:
    # öncesi
    def __eq__(self, other):
        return (self.marka == other.marka and self.renk == other.renk)
    
    def __lt__(self, other):
        return (len(self.yolcular) < len(other.yolcular))

araba = Araba('Honda', 'Siyah')
araba2 = Araba('Honda', 'Siyah')
araba_mavi = Araba('Honda', 'Mavi')

print(araba == araba2)
# True
print(araba == araba_mavi)
# False
print(araba < araba2)
# False

Sadece gerekli karşılaştırma metotlarını eklemek uygun bir çözüm değil, PEP8 kurallarına da uymayan bir durum. Diğer karşılaştırma dunder metotları;
__eq__; == operatörü için
__ne__; != operatörü için
__lt__; < operatörü için
__le__; <= operatörü için
__gt__; > operatörü için
__ge__; >= operatörü için

Bir Cevap Yazın