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:
Sınıflar (classes)
Örnekler (instances)
Sınıf nitelikleri (class attributes)
Örnek nitelikleri (instance attributes)
Ö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ı:
Ö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>