Python Metot Türleri

Python sınıflarında, temel olarak 3 farklı şekilde metot tanımı yapılabilir.

  • Örnek (Instance) metotları
  • Statik (@staticmethod) metotlar
  • Sınıf (@classmethod) metotları

Bunların arasındaki başlıca fark, aldıkları argümanlardır. Örnek metotlarına, ilk argüman olarak, objenin kendisine bir referans gönderilir. Bu argümana, geleneksel olarak self adı verilir. Statik metotlar, kendisini çağıran sınıf veya örnek hakkında herhangi bir bilgiye sahip değildir. Bunlar, işlevini kaybetmeden, sınıf dışında da aynı şekilde tanımlanabilir. Sınıf metotları ise, otomatik olarak, kendisini çağıran sınıfa veya örneğin sınıfına bir referans alır. Bu argümana da geleneksel olarak cls adı verilir.

Örnek (Instance) Metotları

Python'da yeni bir sınıf tanımlandığınız, yazdığınız metotlar öntanımlı olarak, örnek (instance) metotlarıdır. Örnek metotları, ilk argüman olarak, kendisini çağıran örneğe bir referans alır. Böylece, geçerli örneğin niteliklerine erişim ve müdahale imkanı bulurlar. Aşağıdaki kodları inceleyelim;

class ikisayi(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def toplam(self):
        return self.a + self.b
x = ikisayi(3,5)
print(x.toplam) # 1
print(ikisayi.toplam) # 2
print(x.toplam()) # 3
print(ikisayi.toplam(x)) # 4

Programın çıktısı:

#1: <bound method ikisayi.toplam of <__main__.ikisayi object at 0x021F0390>>
#2: <unbound method ikisayi.toplam>
#3: 8
#4: 8

Yukarıda, görmeye alışık olduğunuz türden bir sınıf tanımımız var. Bu sınıf, iki tane sayı hakkında veri tutmak için kullanılıyor. Yine, bolca karşınıza çıkacağı gibi __init__ başlangıç metodu var. toplam metodu ise, normal şekilde tanımlanmış, yani bir örnek (instance) metodu. toplam metodumuz, her örnek metodunda olduğu gibi, zorunlu bir ilk argüman alıyor. Bu argümana, çalışma anında oluşturulmuş bir ikisayı objesi geçirilecek. Böylece, toplam metodu, hangi objenin a niteliğiyle b niteliğini toplaması gerektiğini bilebilecek.

Program çıktısına dikkat edersek, x.toplam metodunun bound method olduğunu görüyoruz.Yani, ikisayi.toplam metodu belirtilen adresdeki ikisayi objesine bağlanmış. Yani, x.toplam metodu çağırıldığında, ikisayi.toplam metodu, ilk argüman olarak, o adresdeki objeyi alacak.

Diğer yandan, aynı metoda, sınıf üzerinde eriştiğimizde, bir unbound method buluyoruz.Yani, bu method, herhangi bir örneğe bağlanmamış. Dolayısıyla, otomatik bir ilk argüman almayacak. Eğer ikisayi.toplam() şeklinde çağırırsanız, aşağıdaki hatayı alırsınız:

TypeError: unbound method toplam() must be called with ikisayi instance as first argument

#3 ve #4 numaralı ifadeler aynı çıktıyı veriyor, çünkü, teknik olarak aynı şeyler. Siz, x.toplam() çağırdığınızda, Python bunu kendi içinde ikisayi.toplam(x) şekline dönüştürüyor.

Statik Metotlar

Statik Metotlar, kendisini hangi sınıf veya örneğin çağırdığını bilmez. Sadece kendine verilen argümanları bilir, örnek veya sınıf metotları gibi, gizli bir ilk argüman almazlar. Bu yönden bakıldığında, bu fonksiyonun sınıf içinde yazılmasıyla, sınıf dışında yazılması arasında, hiçbir fark yoktur. Ancak, aynı modül içerisindeki birçok fonksiyonu anlamsal bütünler içinde toplamak gerektiğinde kullanılabilir. Bunları tanımlamak için, metot tanımından önce @staticmethod dekoratörü kullanılır.

Sınıf Metotları

Diğer yandan, sınıf metotları, ilk argüman olarak, kendisini çağıran sınıfa veya kendisini çağıran örneğin sınıfına mecburi/otomatik bir referans alır. Bunu o sınıfın bir örneğini oluşturmak istediğiniz durumlarda (bkz: Factory) kullanabilirsiniz. Kendisini çağıran sınıfa bir referans aldığı için, alt sınıflarda da istenildiği gibi çalışacaktır. Örneğin, diyelim ki, bir telefon numarasını temsil eden bir sınıfınız var:

class TelNo(object):
    def __init__(self, ulkekodu, alankodu, numara):
        self.ulkekodu = ulkekodu
        self.alankodu = alankodu
        self.numara = numara

Burada, bir telefon numarasının, ülke kodu, alan kodu ve numrasını ayrı değişkenlerde tutan bir sınıf tanımladık. Muhtelemen, elimizdeki telefon numaraları, bir dosya veya veritabanında "+90 507 7997272" gibi karakter dizileri içerisinde tutuluyordur. Dolayısıyla, böyle karakter dizilerinden TelNo objesi üreten bir fonksiyona ihtiyacımız olacak.

def str_to_telno(string):
    return TelNo(*string.split(" "))
mytel = str_to_telno("+90 507 7997272")

str_to_telno fonksiyonu, verilen karakter dizisini boşluklarından bölüp, bunlardan yeni bir TelNo objesi oluşturuyor. Telefon numarası tutabilmek güzel ama, bu numarayı arayabilmemiz daha da güzel olurdu. Bunun için, TelNo'nun aranabilen bir alt sınıfını oluşturalım.

class AranabilenTelNo(TelNo):
    def ara(self):
        "bu numaraya VOIP üzerinden bağlantı kur"
        pass # gerçekten bu fonksiyonu kodlamamı beklemiyordunuz umarım :)
def str_to_aranabilentelno(string):
    return AranabilenTelNo(*string.split(" "))

Aynı TelNo'da yaptığımız gibi, bir karakter dizisinden AranabilenTelNo oluşturan bir fonksiyon da tanımladık.

Bu yöntemin şöyle bir sıkıntısı var. Diyelim ki, ileride, mesaj atılabilen telno, mms gönderilebilen tel no vs. gibi bir çok alt sınıf tanımlayacaksınız. Her seferinde tek tek bu dönüşüm işlemini yapmak hem yorucu olacak, hem de kodlar çorba olacak.

Buna alternatif olarak, TelNo objesinde, from_string isminde bir @classmethod tanımladığımızda, ne olduğuna bakalım;

class TelNo(object):
    def __init__(self, ulkekodu, alankodu, numara):
        self.ulkekodu = ulkekodu
        self.alankodu = alankodu
        self.numara = numara
    @classmethod
    def from_string(cls, string):
        return cls(*string.split(" "))
class AranabilenTelNo(TelNo):
    def ara(self):
        pass
mytel = TelNo.from_string("+90 507 7997272")
print(type(mytel)) # 1
myaranabilentel = AranabilenTelNo.from_string("+90 507 7997272")
print(type(myaranabilentel)) # 2

Program Çıktısı:

#1: <class '__main__.TelNo'>
#2: <class '__main__.AranabilenTelNo'>

Program çıktısından görebileceğiniz gibi, iki sınıfın from_string metotları, doğru sınıfa ait objeler oluşturuyor. Böylece, hem alt sınıf için tekrar ayrı fonksiyon yazmak zorunda kalmadık, hem de, doğru sınıfa ait bir örnek oluşturabildik.

Dikkat ettiyseniz, @classmethod ile tanımladığımız metotları, doğrudan sınıf üzerinden çağırdık. Halbuki, Örnek (Instance) metotlarını, sınıfın bir örneği üzerinden çağırıyorduk. Özetle, sınıfın kendisi ile ilgili işlem yapılacak durumlarda, @classmethod kullanabiliriz.s