Nesne Tabanlı Programlama (Devamı)

Geçen bölümde Python’da nesne tabanlı programlama konusunun temellerinden söz etmiştik. Bu bölümde ise nesne tabanlı programlamanın ayrıntılarına inmeye başlayacağız.

Sınıf Metotları

Nesne tabanlı programlamaya giriş yaptığımız geçen bölümde şunlara değindik:

  1. Sınıflar (classes)

  2. Örnekler (instances)

  3. Sınıf nitelikleri (class attributes)

  4. Örnek nitelikleri (instance attributes)

  5. Örnek metotları (instance methods)

Bunlar nesne tabanlı programlamanın en temel kavramlarıdır. Bunları iyice öğrendiyseniz, etrafta gördüğünüz kodların büyük bölümünü anlayabilecek kıvama gelmişsiniz demektir.

Ama elbette nesne tabanlı programlama yalnızca bu temel kavramlardan ibaret değil. Nesne tabanlı programlamanın derinlerine indikçe, bunların dışında başka pek çok kavramla daha karşılaşacağız. Mesela sınıf metotları (class methods) bu kavramlardan biridir. İşte bu bölümde, nesne tabanlı programlamanın ileri düzey kavramlarının ilki olan bu sınıf metotlarından (class methods) söz edeceğiz.

Dilerseniz ne ile karşı karşıya olduğumuzu anlayabilmek için basit bir örnek üzerinden ilerleyelim.

Hatırlarsanız bir önceki bölümde şöyle bir kod parçası vermiştik:

class Çalışan():
    personel = []

    def __init__(self, isim):
        self.isim = isim
        self.kabiliyetleri = []
        self.personele_ekle()

    def personele_ekle(self):
        self.personel.append(self.isim)
        print('{} adlı kişi personele eklendi'.format(self.isim))

    def personeli_görüntüle(self):
        print('Personel listesi:')
        for kişi in self.personel:
            print(kişi)

    def kabiliyet_ekle(self, kabiliyet):
        self.kabiliyetleri.append(kabiliyet)

    def kabiliyetleri_görüntüle(self):
        print('{} adlı kişinin kabiliyetleri:'.format(self.isim))
        for kabiliyet in self.kabiliyetleri:
            print(kabiliyet)

Bu kodlarda, bir personel listesi oluşturmamızı, personele ekleme yapmamızı, personeli görüntülememizi, personele yeni kabiliyet eklememizi ve eklediğimiz kabiliyetleri görüntüleyebilmemizi sağlayan örnek metotları var. Gelin bu kodlara bir de personel sayısını görüntülememizi sağlayacak bir başka örnek metodu daha ekleyelim:

class Çalışan():
    personel = []

    def __init__(self, isim):
        self.isim = isim
        self.kabiliyetleri = []
        self.personele_ekle()

    def personel_sayısını_görüntüle(self):
        print(len(self.personel))

    def personele_ekle(self):
        self.personel.append(self.isim)
        print('{} adlı kişi personele eklendi'.format(self.isim))

    def personeli_görüntüle(self):
        print('Personel listesi:')
        for kişi in self.personel:
            print(kişi)

    def kabiliyet_ekle(self, kabiliyet):
        self.kabiliyetleri.append(kabiliyet)

    def kabiliyetleri_görüntüle(self):
        print('{} adlı kişinin kabiliyetleri:'.format(self.isim))
        for kabiliyet in self.kabiliyetleri:
            print(kabiliyet)

Burada yeni olarak personel_sayısını_görüntüle() adlı bir örnek metodu tanımladık. Bu metot, bir sınıf niteliği olan personel’e erişerek bunun uzunluğunu ekrana basıyor. Böylece personelin kaç kişiden oluştuğunu öğrenmiş oluyoruz.

Bu yeni örnek metodunu aşağıdaki şekilde kullanabiliriz.

Öncelikle kodlarımızı barındıran modülü içe aktaralım:

>>> import çalışan

Daha sonra personel listesine birkaç çalışan ekleyelim:

>>> ahmet = çalışan.Çalışan('Ahmet')

Ahmet adlı kişi personele eklendi

>>> mehmet = çalışan.Çalışan('Mehmet')

Mehmet adlı kişi personele eklendi

>>> ayşe = çalışan.Çalışan('Ayşe')

Ayşe adlı kişi personele eklendi

Artık herhangi bir örnek değişkeni üzerinden personel sayısına erişebiliriz:

>>> ayşe.personel_sayısını_görüntüle()

3

Ancak kodların çalışma mantığı açısından burada bir tutarsızlıktan söz edebiliriz. Genel olarak bütün personele dair bilgi veren bir fonksiyona ahmet, mehmet, ayşe gibi bireysel örnek değişkenleri üzerinden erişmek kulağa sizce de biraz tuhaf gelmiyor mu? Neticede bu fonksiyon, aslında sınıfın herhangi bir örneği ile özellikle veya doğrudan ilişkili değil. Yani bu fonksiyon tek tek sınıf örneklerini değil, genel olarak sınıfın bütününü ilgilendiriyor. Bu bakımdan, personel_sayısını_görüntüle() fonksiyonunun örnek değişkenlerinden bağımsız bir biçimde kullanılabilmesi çok daha mantıklı olacaktır.

Ayrıca, bir örnek metodu olan personel_sayısını_görüntüle() fonksiyonunu örneklerden bağımsız olarak kullanamadığımız için, bu metot yardımıyla personel sayısının 0 olduğu bir durumu görüntülememiz de mümkün olmuyor. Çünkü bu fonksiyona erişebilmek için öncelikle sınıfı en az bir kez örneklemiş, yani sınıfın en az bir adet örneğini çıkarmış olmamız gerekiyor. Bu durum da kodlarımızın mantığı açısından son derece ciddi bir kısıtlamadır.

Yukarıda sıralanan gerekçeler doğrultusunda kodları hem daha tutarlı bir hale getirmek hem de personel sayısının 0 olduğu durumu göstermemizi engelleyen kısıtlamayı aşabilmek için şöyle bir şey deneyebilirsiniz:

def personel_sayısını_görüntüle():
    print(len(Çalışan.personel))

class Çalışan():
    personel = []

    def __init__(self, isim):
        self.isim = isim
        self.kabiliyetleri = []
        self.personele_ekle()

    def personele_ekle(self):
        self.personel.append(self.isim)
        print('{} adlı kişi personele eklendi'.format(self.isim))

    def personeli_görüntüle(self):
        print('Personel listesi:')
        for kişi in self.personel:
            print(kişi)

    def kabiliyet_ekle(self, kabiliyet):
        self.kabiliyetleri.append(kabiliyet)

    def kabiliyetleri_görüntüle(self):
        print('{} adlı kişinin kabiliyetleri:'.format(self.isim))
        for kabiliyet in self.kabiliyetleri:
            print(kabiliyet)

Burada personel sayısını görüntüleyen fonksiyonu sınıftan ayırdık. Böylece şu şekilde bir kullanım mümkün olabildi:

>>> import çalışan
>>> çalışan.personel_sayısını_görüntüle()

0

personel_sayısını_görüntüle() adlı fonksiyonu sınıftan ayırıp modül düzeyinde çalışan bir fonksiyon (veya bir başka deyişle global düzeyde çalışan bir fonksiyon) haline getirdiğimiz için, artık bu fonksiyon Çalışan() sınıfının herhangi bir örneğine bağımlı değil. Dolayısıyla bu fonksiyonu, Çalışan() sınıfı için bir örnek çıkarmak zorunda kalmadan da kullanabiliyoruz. Bu da bize personel sayısının 0 olduğu durumu gösterebilme imkanı tanıyor.

Bu fonksiyonu bir de birkaç örnek çıkardıktan sonra çalıştıralım…

Önce sınıfımızın birkaç örneğini çıkaralım:

>>> ahmet = çalışan.Çalışan('Ahmet')

Ahmet adlı kişi personele eklendi

>>> ayşe = çalışan.Çalışan('Ayşe')

Ayşe adlı kişi personele eklendi

>>> mehmet = çalışan.Çalışan('Mehmet')

Mehmet adlı kişi personele eklendi

Şimdi de personelin şu anda kaç kişiden oluştuğunu sorgulayalım:

>>> çalışan.personel_sayısını_görüntüle()

3

Gördüğünüz gibi, bu şekilde kodlarımız biraz daha tutarlı bir görünüme kavuştu. Ancak bu şekilde, bariz bir biçimde Çalışan() sınıfı ile ilişkili olan personel_sayısını_görüntüle() fonksiyonunu sınıftan ayırmış ve kod bütünlüğünü bozmuş olduk. Çünkü, her ne kadar personel_sayısını_görüntüle() fonksiyonu Çalışan() sınıfının herhangi bir örneği ile ilişkili olmasa da, anlam açısından bu fonksiyonun Çalışan() sınıfına ait olduğu besbelli. Ayrıca, yukarıdaki kodları barındıran modülün tamamını değil de, from çalışan import Çalışan gibi bir komutla yalnızca Çalışan() sınıfını içe aktarırsak, personel_sayısını_görüntüle() fonksiyonu dışarıda kalacaktır:

>>> from çalışan import Çalışan
>>> dir()

Gördüğünüz gibi, personel_sayısını_görüntüle() fonksiyonu listede yok. Dolayısıyla, sınıfla sıkı sıkıya ilişkili olan bu fonksiyonu sınıftan kopardığımız için, seçmeli içe aktarmalarda bu fonksiyon geride kalıyor ve böylece bu fonksiyonu kullanamaz hale geliyoruz.

Seçmeli içe aktarmalarda bu fonksiyon aktarım işlemiyle birlikte gelmediği için, ilgili fonksiyonu özel olarak içe aktarmamız gerekir:

>>> from çalışan import personel_sayısını_görüntüle

Bu şekilde çalışan modülü içinden personel_sayısını_görüntüle() adlı fonksiyonu özel olarak elle içe aktarmış olduk. Artık bu fonksiyonu şöyle kullanabiliriz:

>>> personel_sayısını_görüntüle()

Ancak bu da, her zaman tercih etmeyeceğiniz bir kısıtlama olabilir. O halde bu kısıtlamayı aşmak için gelin, ilgili fonksiyonu tekrar sınıf içine alalım:

class Çalışan():
    personel = []

    def __init__(self, isim):
        self.isim = isim
        self.kabiliyetleri = []
        self.personele_ekle()

    def personel_sayısını_görüntüle(self):
        print(len(self.personel))

    def personele_ekle(self):
        self.personel.append(self.isim)
        print('{} adlı kişi personele eklendi'.format(self.isim))

    def personeli_görüntüle(self):
        print('Personel listesi:')
        for kişi in self.personel:
            print(kişi)

    def kabiliyet_ekle(self, kabiliyet):
        self.kabiliyetleri.append(kabiliyet)

    def kabiliyetleri_görüntüle(self):
        print('{} adlı kişinin kabiliyetleri:'.format(self.isim))
        for kabiliyet in self.kabiliyetleri:
            print(kabiliyet)

Yukarıdaki kodlarda ilgili fonksiyona bir örnek adıyla değil de, sınıf adıyla erişmek için ilk etapta şu kodu denemek aklınıza gelmiş olabilir:

>>> from çalışan import Çalışan
>>> Çalışan.personel_sayısını_görüntüle()

Ancak bu kod size şöyle bir hata mesajı verir:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: personel_sayısını_görüntüle() missing
1 required positional argument: 'self'

Çünkü burada siz Çalışan.personel_sayısını_görüntüle() komutunu vererek aslında sınıfın bir metoduna (class method) erişmeye çalışıyorsunuz. Ancak kodlarımızın içinde bir sınıf metodu yok. Zira, yukarıda sınıf adıyla erişmeye çalıştığımız personel_sayısını_görüntüle() fonksiyonu bir sınıf metodu değil, bir örnek metodudur. Örnek metotlarına da sınıf adlarıyla erişmemizin mümkün olmadığını, bu tür metotlara erişebilmek için sınıfı en az bir kez örneklemiş olmamız gerektiğini biliyorsunuz.

Burada, __init__() ve personel_sayısını_görüntüle() dışında şu örnek metotları var: personel_sayısını_görüntüle(), personele_ekle(), personeli_görüntüle(), kabiliyet_ekle(), kabiliyetleri_görüntüle(). Bunları birer örnek metodu yapan şeyin, self kelimesi olduğunu biliyoruz…

Daha önce de söylediğimiz gibi, her ne kadar Python’da sınıf niteliklerine hem örnekler hem de doğrudan sınıf adları üzerinden erişebilsek de örnek niteliklerine ve örnek metotlarına yalnızca örnekler üzerinden erişebiliriz. Bir metoda, sınıf adı ile erişebilmek için, ilgili metodu bir sınıf metodu olarak tanımlamış olmamız gerekir. Peki ama nasıl?

@classmethod Bezeyicisi ve cls

Bildiğiniz gibi, örnek metotlarını oluşturmak için self adlı bir kelimeden yararlanıyorduk. Tanımladığımız örnek metotlarının parametre listesinde ilk sıraya yerleştirdiğimiz bu kelimeyi kullanarak, sınıf içinde örnek metotlarına erişebiliyoruz. İşte sınıf metotları için de benzer bir işlem yapacağız.

Çok basit bir örnek verelim:

class Sınıf():
    sınıf_niteliği = 0

    def __init__(self, param1, param2):
        self.param1 = param1
        self.param2 = param2
        self.örnek_niteliği = 0

    def örnek_metodu(self):
        self.örnek_niteliği += 1
        return self.örnek_niteliği

    def sınıf_metodu(cls):
        cls.sınıf_niteliği += 1
        return cls.sınıf_niteliği

Burada örnek_metodu() ile sınıf_metodu() arasındaki fark, ilkinde self, ikincisinde ise cls kullanmamız. Ancak hatırlarsanız, self kelimesinin Python açısından bir zorunluluk olmadığını söylemiştik. Tıpkı self gibi, aslında cls kelimesi de Python açısından bir zorunluluk değildir. Yani cls yerine de istediğimiz kelimeyi kullanabilirdik. Bu metotlarda önemli olan, parametre listesinin ilk sırasını işgal eden kelimenin ne olduğudur. Dolayısıyla yukarıdaki örnekte Python açısından örnek_metodu() ile sınıf_metodu() arasında hiçbir fark bulunmaz. Python her iki metodu da birer örnek metodu olarak değerlendirir. Bu iki örnek metodu arasındaki fark, ilkinde sınıf örneklerini temsil edecek kelimenin self, ikincisinde ise cls olarak belirlenmiş olmasıdır. Python self veya cls kelimelerine özel bir önem atfetmez. Ama Python topluluğu içinde, örnek metotları için self, sınıf metotları için ise cls kullanmak çok güçlü bir gelenektir.

Sözün özü, sınıf_metodu() fonksiyonunun ilk parametresini cls yapmış olmamız bu metodun bir sınıf metodu olabilmesi için gereklidir, ama yeterli değildir. Python’da bir sınıf metodu oluşturabilmek için bir parçaya daha ihtiyacımız var:

class Sınıf():
    sınıf_niteliği = 0

    def __init__(self, param1, param2):
        self.param1 = param1
        self.param2 = param2
        self.örnek_niteliği = 0

    def örnek_metodu(self):
        self.örnek_niteliği += 1
        return self.örnek_niteliği

    @classmethod
    def sınıf_metodu(cls):
        cls.sınıf_niteliği += 1
        return cls.sınıf_niteliği

İşte Python’da bir sınıf metodunu örnek metodundan ayıran asıl öğe, yukarıdaki örnekte gördüğümüz @classmethod ifadesidir. Python’da isminin önünde @ işareti olan bu tür öğelere ‘bezeyici’ (decorator) adı verilir. Gördüğünüz gibi, @classmethod bezeyicisi, yukarıdaki örnekte bir fonksiyonu sınıf metoduna dönüştürme işlevi görüyor. İlerleyen derslerimizde bezeyicilerin başka özelliklerinden de söz edeceğiz. Gelin isterseniz şimdi yukarıda öğrendiğimiz özelliği Çalışan() adlı sınıfa uygulayalım:

class Çalışan():
    personel = []

    def __init__(self, isim):
        self.isim = isim
        self.kabiliyetleri = []
        self.personele_ekle()

    @classmethod
    def personel_sayısını_görüntüle(cls):
        print(len(cls.personel))

    def personele_ekle(self):
        self.personel.append(self.isim)
        print('{} adlı kişi personele eklendi'.format(self.isim))

    @classmethod
    def personeli_görüntüle(cls):
        print('Personel listesi:')
        for kişi in cls.personel:
            print(kişi)

    def kabiliyet_ekle(self, kabiliyet):
        self.kabiliyetleri.append(kabiliyet)

    def kabiliyetleri_görüntüle(self):
        print('{} adlı kişinin kabiliyetleri:'.format(self.isim))
        for kabiliyet in self.kabiliyetleri:
            print(kabiliyet)

Yukarıda personel_sayısını_görüntüle() adlı fonksiyonun yanı sıra, personeli_görüntüle() adlı fonksiyonu da bir sınıf metodu haline getirdik. Çünkü tıpkı personel_sayısını_görüntüle() fonksiyonu gibi, personeli_görüntüle() fonksiyonu da aslında tek tek örneklerden ziyade sınıfın genelini ilgilendiriyor. Dolayısıyla bu fonksiyona da sınıf adı üzerinden erişebilmek gayet makul ve mantıklı bir iştir.

Sınıf metotlarımızı başarıyla tanımladığımıza göre artık yukarıdaki sınıfı şu şekilde kullanabiliriz:

>>> from çalışan import Çalışan
>>> Çalışan.personel_sayısını_görüntüle()

0

Bir sınıf metodu olarak tanımladığımız personel_sayısını_görüntüle() fonksiyonu artık ilgili sınıfın herhangi bir örneğine bağımlı olmadığı için, sınıfı örneklemek zorunda kalmadan, yalnızca sınıf adını kullanarak personel_sayısını_görüntüle() fonksiyonuna erişebiliyoruz. Bu da bize personel sayısının 0 olduğu durumu görüntüleyebilme imkanı veriyor…

Ayrıca, personel_sayısını_görüntüle() adlı sınıf metodumuz, fiziksel olarak da sınıfın içinde yer aldığı için, seçmeli içe aktarmalarda sınıfın öteki öğeleriyle birlikte bu metot da aktarılacaktır:

>>> from çalışan import Çalışan
>>> dir(Çalışan)

Listede sınıf metodumuzun da olduğunu görüyorsunuz.

Personele üye ekledikten sonra bu metodu nasıl kullanacağımızı biliyorsunuz:

>>> ahmet = Çalışan('Ahmet')

Ahmet adlı kişi personele eklendi

>>> mehmet = Çalışan('Mehmet')

Mehmet adlı kişi personele eklendi

>>> ayşe = Çalışan('Ayşe')

Ayşe adlı kişi personele eklendi

>>> Çalışan.personel_sayısını_görüntüle()

3

Gördüğünüz gibi, sınıf metodumuza doğrudan sınıf adını kullanarak erişebiliyoruz. Elbette bu durum, sınıf metoduna örnek adları üzerinden de erişmemize engel değil. Eğer arzu edersek personel_sayısını_görüntüle() adlı sınıf metodunu şu şekilde de çağırabiliriz:

>>> ayşe.personel_sayısını_görüntüle()

3

>>> ahmet.personel_sayısını_görüntüle()

3

>>> mehmet.personel_sayısını_görüntüle()

3

Ancak örnek metotlarına ve örnek niteliklerine atıfta bulunmak için örnek adlarını kullanmak, sınıf metotları ve sınıf niteliklerine atıfta bulunmak için ise sınıf adlarını tercih etmek daha akıllıca olabilir.

personel_sayısını_görüntüle() fonksiyonu için söylediğimiz bu sözler, personeli_görüntüle() fonksiyonu için de aynen geçerlidir.

Sözün özü, sınıfın herhangi bir örneğine bağlı olmayan bir işlem yapan, ama anlamsal olarak da sınıfla ilişkili olduğu için sınıf dışında bırakmak istemediğiniz fonksiyonları birer sınıf metodu olarak tanımlayabilirsiniz.

Alternatif İnşacılar

Sınıf metotlarının, işimize yarayabilecek bir başka özelliği ise, bunların bir ‘alternatif inşacı’ (alternative constructor) olarak kullanılabilecek olmasıdır. “Alternatif neyci?” diye sorduğunuzu rahatlıkla duyabiliyorum…

Gelin isterseniz ‘alternatif inşacı’ kavramını bir dizi örnek üzerinde kabataslak da olsa açıklamaya çalışalım.

Şimdi elinizde şöyle bir kitap listesi olduğunu düşünün:

liste = [('9789753424080', 'Greenberg', 'Sana Gül Bahçesi Vadetmedim', 'Metis'),
         ('975872519X', 'Evren', 'Postmodern Bir Kız Sevdim', 'İthaki'),
         ('9789754060409', 'Nietzsche', 'Böyle Buyurdu Zerdüşt', 'Cem')]

Bu liste, her bir kitap için, sırasıyla o kitabın ISBN numarasını, yazarını, ismini ve yayınevini gösteren birer demetten oluşuyor. Amacımız, bu listeden çeşitli ölçütlere göre sorgulama yapabilen bir program yazmak. Yazdığımız program; isbn, isim, eser ve yayınevi ölçütlerine göre bu listeden veri alabilmemizi sağlayacak.

İlk denememizi yapalım:

liste = [('9789753424080', 'Greenberg', 'Sana Gül Bahçesi Vadetmedim', 'Metis'),
         ('975872519X', 'Evren', 'Postmodern Bir Kız Sevdim', 'İthaki'),
         ('9789754060409', 'Nietzsche', 'Böyle Buyurdu Zerdüşt', 'Cem')]

def sorgula(ölçüt=None, değer=None):
    for li in liste:
        if not ölçüt and not değer:
            print(*li, sep=', ')

        elif ölçüt == 'isbn':
            if değer == li[0]:
                print(*li, sep=', ')

        elif ölçüt == 'yazar':
            if değer == li[1]:
                print(*li, sep=', ')

        elif ölçüt == 'eser':
            if değer == li[2]:
                print(*li, sep=', ')

        elif ölçüt == 'yayınevi':
            if değer == li[3]:
                print(*li, sep=', ')

Burada öncelikle kitap listemizi tanımladık. Daha sonra da sorgulama işlemini gerçekleştirecek sorgula() adlı bir fonksiyon yazdık.

Bu fonksiyon toplam iki parametre alıyor: ölçüt ve değer. Bu parametrelerin öntanımlı değerlerini None olarak belirledik. Böylece bu fonksiyonu herhangi bir argüman vermeden de çalıştırabileceğiz.

Fonksiyon gövdesinde ilk yaptığımız iş, fonksiyon argümansız çalıştırıldığında, yani ölçüt ve değer için herhangi bir değer belirlenmediğinde ne olacağını ayarlamak:

for li in liste:
    if not ölçüt and not değer:
        print(*li, sep=', ')

Eğer ölçüt ve değer parametreleri için herhangi bir değer belirtilmemişse, yani bunlar None olarak bırakılmışsa, kitap listesinin tamamını, her bir öğe arasına birer virgül yerleştirerek ekrana basıyoruz.

Eğer sorgula() fonksiyonu çağrılırken ölçüt parametresine ‘isbn’ argümanı, değer parametresine ise bir ISBN değeri verilmişse şu işlemi yapıyoruz:

elif ölçüt == 'isbn':
    if değer == li[0]:
        print(*li, sep=', ')

Burada yaptığımız şey şu: Eğer ölçüt ‘isbn’ ise, fonksiyona verilen değer argümanını, kitap listesi içindeki her bir demetin ilk sırasında arıyoruz. Çünkü ISBN bilgileri demetlerin ilk sırasında yer alıyor. Eğer bu koşul sağlanırsa listenin ilgili kısmını ekrana basıyoruz:

if değer == li[0]:
    print(*li, sep=', ')

Bu mantığı kullanarak öteki ölçütler için de birer sorgu kodu yazıyoruz:

elif ölçüt == 'yazar':
    if değer == li[1]:
        print(*li, sep=', ')

elif ölçüt == 'eser':
    if değer == li[2]:
        print(*li, sep=', ')

elif ölçüt == 'yayınevi':
    if değer == li[3]:
        print(*li, sep=', ')

Her bir değer’i, listenin ilgili sırasında aradığımıza dikkat edin. Yazar bilgisi demetlerin ikinci sırasında yer aldığı için li[1]’i, aynı gerekçeyle eser için li[2]’yi, yayınevi için ise li[3]’ü sorguluyoruz.

Gelelim bu fonksiyonu nasıl kullanacağımıza…

Her zaman söylediğimiz gibi, Python’ın etkileşimli kabuğu mükemmel bir test ortamıdır. O halde şimdi bu kodları klist.py adlı bir dosyaya kaydedelim ve dosyanın bulunduğu dizinde bir etkileşimli kabuk oturumu başlatarak modülümüzü içe aktaralım:

>>> import klist

Önce klist modülü içindeki sorgula() fonksiyonunu argümansız olarak çağıralım:

>>> klist.sorgula()

9789753424080, Greenberg, Sana Gül Bahçesi Vadetmedim, Metis
975872519X, Evren, Postmodern Bir Kız Sevdim, İthaki
9789754060409, Nietzsche, Böyle Buyurdu Zerdüşt, Cem

Tam da beklediğimiz gibi, fonksiyon argümansız çağrıldığında bütün kitap listesini, her bir öğe arasında bir virgül olacak şekilde ekrana basıyor.

Şimdi de mesela ISBN numarasına göre birkaç sorgu işlemi gerçekleştirelim:

>>> klist.sorgula('isbn', '9789754060409')

9789754060409, Nietzsche, Böyle Buyurdu Zerdüşt , Cem

>>> klist.sorgula('isbn', '975872519X')

975872519X, Evren, Postmodern Bir Kız Sevdim, İthaki

>>> klist.sorgula('isbn', '9789753424080')

9789753424080, Greenberg, Sana Gül Bahçesi Vadetmedim, Metis

Burada, sorgula() fonksiyonunun ilk parametresine argüman olarak ‘isbn’ değerini verdik. Böylece programımız ISBN numarasına göre sorgu yapmak istediğimizi anladı. Daha sonra da ikinci argüman olarak istediğimiz bir ISBN numarasını yazdık ve sorgu işlemini tamamladık.

Bir de yayınevine göre sorgulama yapalım:

>>> klist.sorgula('yayınevi', 'Metis')

9789753424080, Greenberg, Sana Gül Bahçesi Vadetmedim, Metis

>>> klist.sorgula('yayınevi', 'İthaki')

975872519X, Evren, Postmodern Bir Kız Sevdim, İthaki

>>> klist.sorgula('yayınevi', 'Cem')

9789754060409, Nietzsche, Böyle Buyurdu Zerdüşt, Cem

Gördüğünüz gibi, fonksiyonumuz gayet güzel çalışıyor…

Yukarıda verdiğimiz kodlar, bahsettiğimiz amaç için yazılabilecek tek alternatif değildir elbette. Mesela yukarıdaki if-else yapısını bir sözlük içine yerleştirerek çok daha sade bir program elde edebiliriz.

Dikkatlice inceleyin:

liste = [('9789753424080', 'Greenberg', 'Sana Gül Bahçesi Vadetmedim', 'Metis'),
         ('975872519X', 'Evren', 'Postmodern Bir Kız Sevdim', 'İthaki'),
         ('9789754060409', 'Nietzsche', 'Böyle Buyurdu Zerdüşt', 'Cem')]


def sorgula(ölçüt=None, değer=None):
    d = {'isbn'     : [li for li in liste if değer == li[0]],
         'yazar'    : [li for li in liste if değer == li[1]],
         'eser'     : [li for li in liste if değer == li[2]],
         'yayınevi' : [li for li in liste if değer == li[3]]}

    for öğe in d.get(ölçüt, liste):
        print(*öğe, sep = ', ')

Burada bütün if-else cümleciklerini birer liste üretecine dönüştürüp, d adlı sözlüğün anahtarları olarak belirledik. Artık sorgulama işlemlerini bir if-else yapısı içinde değil de, bir sözlük içinden gerçekleştireceğiz.

Hangi parametrenin hangi listeyi çağıracağını belirleyen sözlüğümüzü yazdıktan sonra, sözlüklerin get() metodunu kullanarak, ölçüt argümanının değerine göre sözlükten veri çekiyoruz. Eğer sözlükte bulunmayan bir ölçüt değeri verilirse tüm listeyi ekrana basıyoruz.

Bu arada, eğer d sözlüğü içindeki liste üreteçlerinin birbirini tekrar eder bir yapıda olması sizi rahatsız ediyorsa, bu kısmı bir yardımcı fonksiyon aracılığıyla sadeleştirebilirsiniz:

liste = [('9789753424080', 'Greenberg', 'Sana Gül Bahçesi Vadetmedim', 'Metis'),
         ('975872519X', 'Evren', 'Postmodern Bir Kız Sevdim', 'İthaki'),
         ('9789754060409', 'Nietzsche', 'Böyle Buyurdu Zerdüşt', 'Cem')]

def bul(değer, sıra):
    return [li for li in liste if değer == li[sıra]]

def sorgula(ölçüt=None, değer=None):
    d = {'isbn'     : bul(değer, 0),
         'yazar'    : bul(değer, 1),
         'eser'     : bul(değer, 2),
         'yayınevi' : bul(değer, 3)}

    for öğe in d.get(ölçüt, liste):
        print(*öğe, sep = ', ')

Burada bütün liste üreteçlerini tek bir bul() fonksiyonu içinde oluşturarak, sorgula() fonksiyonu içindeki d sözlüğüne gönderdik.

Bu kodları da aynı ilk program örneğinde olduğu gibi kullanıyoruz:

>>> import klist
>>> klist.sorgula()

9789753424080, Greenberg, Sana Gül Bahçesi Vadetmedim, Metis
975872519X, Evren, Postmodern Bir Kız Sevdim, İthaki
9789754060409, Nietzsche, Böyle Buyurdu Zerdüşt, Cem

>>> klist.sorgula('yazar', 'Nietzsche')

9789754060409, Nietzsche, Böyle Buyurdu Zerdüşt, Cem

>>> klist.sorgula('eser', 'Sana Gül Bahçesi Vadetmedim')

9789753424080, Greenberg, Sana Gül Bahçesi Vadetmedim, Metis

Yukarıdaki kodları yazmanın daha başka alternatifleri de var. Mesela, eğer arzu ederseniz, yukarıdaki kodları bir sınıf yapısı içinde de ifade edebilirsiniz:

class Sorgu():
    def __init__(self):
        self.liste = [('9789753424080', 'Greenberg', 'Sana Gül Bahçesi Vadetmedim', 'Metis'),
                      ('975872519X', 'Evren', 'Postmodern Bir Kız Sevdim', 'İthaki'),
                      ('9789754060409', 'Nietzsche', 'Böyle Buyurdu Zerdüşt', 'Cem')]

    def bul(self, değer, sıra):
        return [li for li in self.liste if değer == li[sıra]]

    def sorgula(self, ölçüt=None, değer=None):
        d = {'isbn'     : self.bul(değer, 0),
             'yazar'    : self.bul(değer, 1),
             'eser'     : self.bul(değer, 2),
             'yayınevi' : self.bul(değer, 3)}

        for öğe in d.get(ölçüt, self.liste):
            print(*öğe, sep = ', ')

Burada kitap listesini bir örnek niteliği olarak tanımlamak suretiyle sınıfın her yerinden kullanılabilir hale getirdik.

Ardından da bul() ve sorgula() adlı fonksiyonları, birer örnek metodu biçiminde sınıf içine yerleştirdik.

Bu sınıfı da şu şekilde kullanabiliriz:

>>> import klist
>>> sorgu = klist.Sorgu()
>>> sorgu.sorgula()

9789753424080, Greenberg, Sana Gül Bahçesi Vadetmedim, Metis
975872519X, Evren, Postmodern Bir Kız Sevdim, İthaki
9789754060409, Nietzsche, Böyle Buyurdu Zerdüşt, Cem

>>> sorgu.sorgula('yazar', 'Evren')

975872519X, Evren, Postmodern Bir Kız Sevdim, İthaki

Elbette, bu örnekte, ilk yazdığımız kodları bir sınıf yapısı içinde tarif etmenin bize pek bir katkısı yok. Burada yaptığımız şey esasında bütün kodları ‘Sorgu’ adlı bir etki alanı içine taşımaktan fazlası değil. Ama böyle bir imkanınızın da olduğunu bilmeniz her halükarda sizin için faydalı olacaktır.

Gelelim yukarıdaki kodları yazmanın son alternatifine:

class Sorgu():
    def __init__(self, değer=None, sıra=None):
        self.liste = [('9789753424080', 'Greenberg', 'Sana Gül Bahçesi Vadetmedim', 'Metis'),
                      ('975872519X', 'Evren', 'Postmodern Bir Kız Sevdim', 'İthaki'),
                      ('9789754060409', 'Nietzsche', 'Böyle Buyurdu Zerdüşt', 'Cem')]

        if not değer and not sıra:
            l = self.liste
        else:
            l = [li for li in self.liste if değer == li[sıra]]

        for i in l:
            print(*i, sep=', ')

    @classmethod
    def isbnden(cls, isbn):
        cls(isbn, 0)

    @classmethod
    def yazardan(cls, yazar):
        cls(yazar, 1)

    @classmethod
    def eserden(cls, eser):
        cls(eser, 2)

    @classmethod
    def yayınevinden(cls, yayınevi):
        cls(yayınevi, 3)

Burada da, her bir ölçütü ayrı birer sınıf metodu olarak tanımladık. Böylece bu ölçütleri yapısal olarak birbirinden ayırmış olduk. Yukarıdaki sınıfı şu şekilde kullanabiliriz:

Önce modülümüzü içe aktaralım:

>>> from klist import Sorgu

ISBN numarasına göre bir sorgu gerçekleştirelim:

>>> Sorgu.isbnden("9789753424080")

9789753424080, Greenberg, Sana Gül Bahçesi Vadetmedim, Metis

Gördüğünüz gibi, sınıf metodu yaklaşımı, gayet temiz bir sorgu kodu üretmemize imkan tanıyor.

Bir de yazara ve esere göre sorgulayalım:

>>> Sorgu.yazardan("Greenberg")

9789753424080, Greenberg, Sana Gül Bahçesi Vadetmedim, Metis

>>> Sorgu.eserden("Postmodern Bir Kız Sevdim")

975872519X, Evren, Postmodern Bir Kız Sevdim, İthaki

Bunlar da gayet güzel görünüyor.

Şimdi bir de bütün listeyi alalım:

>>> hepsi = Sorgu()

9789753424080, Greenberg, Sana Gül Bahçesi Vadetmedim, Metis
975872519X, Evren, Postmodern Bir Kız Sevdim, İthaki
9789754060409, Nietzsche, Böyle Buyurdu Zerdüşt, Cem

Gördüğünüz gibi, sınıfı parametresiz olarak örneklediğimizde bütün listeyi elde ediyoruz.

İşte ‘alternatif inşa’ denen işlem tam olarak budur. Yukarıdaki örnekte isbnden(), yazardan(), eserden() ve yayınevinden() adlı sınıf metotları, Sorgu() adlı sınıfı alternatif şekillerde inşa etmemizi sağlıyor [1].

Normal şartlarda, bir sınıfı, __init__() fonksiyonuna verdiğimiz parametreler aracılığıyla inşa ediyoruz (birkaç sayfa sonra ‘inşa’ kavramından daha ayrıntılı olarak bahsedeceğiz).

Mesela:

class Giriş():
    def __init__(self, mesaj='Müşteri numaranız: '):
        cevap = input(mesaj)
        print('Hoşgeldiniz!')

Burada tanımladığımız Giriş() sınıfı, bir müşteri numarası aracılığıyla sisteme giriş imkanı sağlıyor:

>>> from sistem import Giriş #kodlarımız sistem.py dosyası içinde
>>> Giriş()

Eğer biz aynı zamanda bir parola ve TC Kimlik Numarası ile de giriş imkanı sağlamak istersek, başka yöntemlerin yanı sıra, sınıf metotlarından da yararlanabiliriz:

class Giriş():
    def __init__(self, mesaj='Müşteri numaranız: '):
        cevap = input(mesaj)
        print('Hoşgeldiniz!')

    @classmethod
    def paroladan(cls):
        mesaj = 'Lütfen parolanızı giriniz: '
        cls(mesaj)

    @classmethod
    def tcknden(cls):
        mesaj = 'Lütfen TC kimlik numaranızı giriniz: '
        cls(mesaj)

Bu şekilde yukarıdaki sınıfı aşağıdaki gibi de inşa etme imkanına kavuşuyoruz:

>>> Giriş.paroladan()

veya:

>>> Giriş.tcknden()

Sınıf metotları içinde kullandığımız cls(mesaj) satırları, Giriş() adlı sınıfı farklı bir parametre ile çağırmamızı sağlıyor. Gördüğünüz gibi, bu sınıfın mesaj parametresinin öntanımlı değeri ‘Müşteri numaranız: ‘. Sınıfımızı farklı bir şekilde çağırabilmek için, cls(mesaj) kodları yardımıyla sınıfın mesaj parametresini ‘Lütfen parolanızı giriniz: ‘ ve ‘Lütfen TC kimlik numaranızı giriniz: ‘ değerleri ile yeniden çalıştırıyoruz.

Daha önce de birkaç kez vurguladığımız gibi, cls kelimesi Python açısından bir zorunluluk değildir. Yani yukarıdaki sınıfı mesela şöyle de yazabilirdik:

class Giriş():
    def __init__(self, mesaj='Müşteri numaranız: '):
        cevap = input(mesaj)
        print('Hoşgeldiniz!')

    @classmethod
    def paroladan(snf):
        mesaj = 'Lütfen parolanızı giriniz: '
        snf(mesaj)

    @classmethod
    def tcknden(snf):
        mesaj = 'Lütfen TC kimlik numaranızı giriniz: '
        snf(mesaj)

Ancak, tıpkı self kelimesinde olduğu gibi, cls de Python topluluğu içinde son derece yerleşik bir gelenektir. Bu geleneği bozmak isteyeceğinizi zannetmiyorum.

İlk bakışta sınıf metotları size pek gerekli değilmiş gibi gelebilir. Ama eğer bu metotların gerçek dünyadaki kullanımına ilişkin bir örnek verirsek belki fikriniz değişir.

Sınıf metotlarının kullanımına ilişkin güzel bir örneği datetime modülünde görebilirsiniz.

Ayrıca bakınız

Aşağıdaki örneği daha iyi anlayabilmek için datetime Modülü ve time Modülü belgelerine bakınız.

Bir standart kütüphane modülü olan datetime’ın kaynak dosyasını açarsanız (kaynak dosyanın nerede olduğunu nasıl öğrenebilirim diye soran arkadaşlara teessüflerimi iletiyorum…), orada date sınıfının şöyle yazıldığını göreceksiniz:

class date:
    __slots__ = '_year', '_month', '_day'

    def __new__(cls, year, month=None, day=None):
        if (isinstance(year, bytes) and len(year) == 4 and
            1 <= year[2] <= 12 and month is None):  # Month is sane
            # Pickle support
            self = object.__new__(cls)
            self.__setstate(year)
            return self
        _check_date_fields(year, month, day)
        self = object.__new__(cls)
        self._year = year
        self._month = month
        self._day = day
        return self

    @classmethod
    def fromtimestamp(cls, t):
        y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
        return cls(y, m, d)

    @classmethod
    def today(cls):
        t = _time.time()
        return cls.fromtimestamp(t)

    @classmethod
    def fromordinal(cls, n):
        y, m, d = _ord2ymd(n)
        return cls(y, m, d)

Gördüğünüz gibi, burada üç tane sınıf metodu var:

@classmethod
def fromtimestamp(cls, t):
    ...

@classmethod
def today(cls):
    ...

@classmethod
def fromordinal(cls, n):
    ...

Normal şartlarda datetime modülü içindeki date sınıfını şu şekilde kullanıyoruz:

>>> import datetime
>>> bugün = datetime.date(2015, 6, 16)

Bu şekilde, date sınıfına sırasıyla yıl, ay ve gün bilgisi girerek bugün adlı bir tarih nesnesi oluşturmuş oluyoruz. Bu şekilde herhangi bir tarihi elle oluşturabilirsiniz.

Eğer amacınız bugünün tarihini oluşturmaksa, yıl, ay ve gün bilgilerini yukarıdaki gibi date sınıfına elle girebileceğiniz gibi, today() adlı sınıf metodunu da kullanabilirsiniz:

>>> bugün = datetime.date.today()

İşte böylece, date sınıfının size sunduğu bir alternatif inşacı (today()) vasıtasıyla bugünün tarihini otomatik olarak elde etmiş oldunuz.

Aynı şekilde, eğer elinizde bir zaman damgası varsa ve siz bu zaman damgasından bir tarih elde etmek istiyorsanız yine date sınıfının sunduğu bir başka alternatif inşacıdan yararlanabilirsiniz:

>>> import time
>>> zaman_damgası = time.time()
>>> bugün = datetime.date.fromtimestamp(zaman_damgası)

Eğer elinizde tam sayı biçimli bir Gregoryen tarih verisi varsa bu veriyi kullanarak da bir tarih nesnesi elde edebilirsiniz:

>>> gregoryen = 735765
>>> bugün = datetime.date.fromordinal(gregoryen)

datetime.date(2015, 6, 16)

Uzun lafın kısası, alternatif inşacılar, bir sınıftan nesne oluşturmak için bize alternatif yollar sunan son derece faydalı araçlardır. Bu arada, eğer bu bölümde değindiğimiz bazı kavramları anlamakta zorlandıysanız hiç canınızı sıkmayın. Bir sonraki bölümü işledikten sonra, burada anlatılanlar kafanıza çok daha sağlam bir şekilde yerleşmiş olacak.

Statik Metotlar

Python’da örnek metotları ve sınıf metotları dışında bir de statik metotlar bulunur. Bildiğiniz gibi, örnek nitelikleri üzerinde işlem yapacağımız zaman örnek metotlarını kullanıyoruz. Aynı şekilde sınıf nitelikleri üzerinde işlem yapacağımız zaman ise sınıf metotlarından faydalanıyoruz. Örnek metotları içinde herhangi bir örnek niteliğine erişmek istediğimizde self kelimesini kullanıyoruz. Sınıf metotları içinde bir sınıf niteliğine erişmek için ise cls kelimesini kullanıyoruz. İşte eğer bir sınıf içindeki herhangi bir fonksiyonda örnek veya sınıf niteliklerinin hiçbirine erişmeniz gerekmiyorsa, statik metotları kullanabilirsiniz.

@staticmethod Bezeyicisi

Buraya gelene kadar öğrendiğimiz örnek ve sınıf metotlarını nasıl kullanacağımızı biliyorsunuz:

class Sınıf():
    sınıf_niteliği = 0

    def __init__(self, veri):
        self.veri = veri

    def örnek_metodu(self):
        return self.veri

    @classmethod
    def sınıf_metodu(cls):
        return cls.sınıf_niteliği

Burada örnek_metodu(), self yardımıyla örnek niteliklerine erişiyor. sınıf_metodu() ise cls yardımıyla sınıf niteliklerine erişiyor. Sınıf metodu tanımlamak için ayrıca @classmethod bezeyicisini de kullanıyoruz. İşte eğer sınıf içinde tanımlayacağınız fonksiyon herhangi bir örnek ya da sınıf niteliği üzerinde herhangi bir işlem yapmayacaksa şöyle bir şey yazabilirsiniz:

class Sınıf():
    sınıf_niteliği = 0

    def __init__(self, veri):
        self.veri = veri

    def örnek_metodu(self):
        return self.veri

    @classmethod
    def sınıf_metodu(cls):
        return cls.sınıf_niteliği

    @staticmethod
    def statik_metot():
        print('merhaba statik metot!')

Gördüğünüz gibi, statik metotları tanımlamak için @staticmethod bezeyicisini kullanıyoruz. Statik metotlar, ilk parametre olarak self veya cls benzeri kelimeler almaz. Çünkü bu tür sınıfların örnek veya sınıf nitelikleri ile herhangi bir işi yoktur.

Peki statik metotlar ne işe yarar?

Bu metotlar sınıf metotlarına çok benzer. Tıpkı sınıf metotlarında olduğu gibi, anlamsal olarak sınıfla ilgili olan, ancak sınıf metotlarının aksine bu sınıfın herhangi bir niteliğine erişmesine gerek olmayan fonksiyonları, sınıf dışına atmak yerine, birer statik metot olarak sınıf içine yerleştirebiliriz.

Basit bir örnek verelim:

class Mat():
    '''Matematik işlemleri yapmamızı sağlayan
    bir sınıf.'''

    @staticmethod
    def pi():
        return 22/7

    @staticmethod
    def karekök(sayı):
        return sayı ** 0.5

Burada Mat() adlı bir sınıf tanımladık. Bu sınıf içinde iki adet statik metodumuz var: pi() ve karekök(). Gördüğünüz gibi, bu iki fonksiyon, örnek ve sınıf metotlarının aksine ilk parametre olarak self veya cls almıyor. Çünkü bu iki sınıfın da sınıf veya örnek nitelikleriyle herhangi bir işi yok.

Statik metotları hem örnekler hem de sınıf adları üzerinden kullanabiliriz.

Yukarıdaki kodların mat.py adlı bir dosyada yer aldığını varsayarsak:

>>> from mat import Mat
>>> m = Mat()
>>> m.pi() #örnek üzerinden

3.142857142857143

>>> m.karekök(144) #örnek üzerinden

12.0

>>> Mat.pi() #sınıf üzerinden
3.142857142857143

>>> Mat.karekök(144) #sınıf üzerinden

12.0

Statik metotların özellikle sınıf adları üzerinden kullanılabilmesi, bu tür metotları epey kullanışlı hale getirir. Böylece sınıfı örneklemek zorunda kalmadan, sınıf içindeki statik metotlara ulaşabiliriz.

Elbette eğer isteseydik biz bu fonksiyonları şöyle de tanımlayabilirdik:

class Mat():
    '''Matematik işlemleri yapmamızı sağlayan
    bir sınıf.'''

    def pi(self):
        return 22/7

    def karekök(self, sayı):
        return sayı ** 0.5

Burada bu iki fonksiyonu birer örnek metodu olarak tanımladık. Bu fonksiyonları bu şekilde tanımladığımızda, bunlara örnekler üzerinden erişebiliriz:

>>> from mat import Mat
>>> m = Mat()
>>> m.pi()

3.142857142857143

>>> m.karekök(144)

12.0

Ancak bildiğiniz gibi, örnek metotlarına sınıf adları üzerinden erişemeyiz:

>>> Mat.pi()

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: pi() missing 1 required positional argument: 'self'

Aynı şekilde bunları sınıf metodu olarak da tanımlayabilirdik:

class Mat():
    '''Matematik işlemleri yapmamızı sağlayan
    bir sınıf.'''

    @classmethod
    def pi(cls):
        return 22/7

    @classmethod
    def karekök(cls, sayı):
        return sayı ** 0.5

Bu metotları böyle tanımladığımızda, bu metotlara hem örnekler üzerinden hem de sınıf adı üzerinden erişebiliriz:

>>> from mat import Mat
>>> m = Mat()
>>> m.pi() #örnek üzerinden

3.142857142857143

>>> m.karekök(144) #örnek üzerinden

12.0

>>> Mat.pi() #sınıf üzerinden
3.142857142857143

>>> Mat.karekök(144) #sınıf üzerinden

12.0

Gördüğünüz gibi, kullanım açısından sınıf metotları ile statik metotlar aynı. Ancak Mat() sınıfı içindeki fonksiyonları birer sınıf metodu olarak tanımladığımızda gereksiz yere cls parametresi kullanmış oluyoruz. Fonksiyon içinde herhangi bir yerde kullanılmadığı için, yukarıdaki örnekte cls parametresinin hiçbir amaca hizmet etmediğine dikkat edin.

Statik metotların çok sık kullanılan araçlar olmadığını da belirterek yolumuza devam edelim.

Dipnotları:

Yorumlar

Önemli Not

Sorularınızı yorumlarda dile getirmek yerine Yazbel Forumunda sorarsanız çok daha hızlı cevap alabilirsiniz.
Belgelerdeki bir hata veya eksiği dile getirecekseniz lütfen yorumları kullanmak yerine Github'da bir konu (issue) açın.
Eğer yazdığınız yorum içinde kod kullanacaksanız kodlarınızı <pre><code> etiketleri içine alın. Örneğin:
        <pre><code class="python">
        print("Merhaba Dünya!")
        </code></pre>