Nesne Tabanlı Programlama (OOP)

Bu bölümde, programlama faaliyetlerimizin önemli bir kısmını oluşturacak olan nesne tabanlı programlama yaklaşımına bir giriş yaparak, bu yaklaşımın temel kavramlarından biri olan sınıflara değineceğiz. Bu bölümde amacımız, sınıflar üzerinden hem nesne tabanlı programlamayı tanımak, hem bu yaklaşıma ilişkin temel bilgileri edinmek, hem de etrafımızda gördüğümüz nesne tabanlı yapıların büyük çoğunluğunu anlayabilecek seviyeye gelmek olacaktır. Bu bölümü tamamladıktan sonra, nesne tabanlı programlamayı orta düzeyde bildiğimizi iddia edebileceğiz.

Giriş

Şimdiye kadar Python programlama dili ile ilgili olarak gördüğümüz konulardan öğrendiğimiz çok önemli bir bilgi var: Aslına bakarsak, bu programlama dilinin bütün felsefesi, ‘bir kez yazılan kodların en verimli şekilde tekrar tekrar kullanılabilmesi,’ fikrine dayanıyor.

Şimdi bir geriye dönüp baktığımızda, esasında bu fikrin izlerini ta ilk derslerimize kadar sürebiliyoruz. Mesela değişkenleri ele alalım. Değişkenleri kullanmamızdaki temel gerekçe, bir kez yazdığımız bir kodu başka yerlerde rahatça kullanabilmek. Örneğin, isim = 'Uzun İhsan Efendi' gibi bir tanımlama yaptıktan sonra, bu isim değişkeni aracılığıyla ‘Uzun İhsan Efendi’ adlı karakter dizisini her defasında tekrar tekrar yazmak zorunda kalmadan, kodlarımızın her yanında kullanabiliyoruz.

Aynı fikrin fonksiyonlar ve geçen bölümde incelediğimiz modüller için de geçerli olduğunu bariz bir şekilde görebilirsiniz. Gömülü fonksiyonlar, kendi tanımladığımız fonksiyonlar, hazır modüller, üçüncü şahıs modülleri hep belli bir karmaşık süreci basitleştirme, bir kez tanımlanan bir prosedürün tekrar tekrar kullanılabilmesini sağlama amacı güdüyor.

İşte bu fikir nesne tabanlı programlama ve dolayısıyla ‘sınıf’ (class) adı verilen özel bir veri tipi için de geçerlidir. Bu bölümde, bunun neden ve nasıl böyle olduğunu bütün ayrıntılarıyla ele almaya çalışacağız.

Bu arada, İngilizcede Object Oriented Programming olarak ifade edilen programlama yaklaşımı, Türkçede ‘Nesne Tabanlı Programlama’, ‘Nesne Yönelimli Programlama’ ya da ‘Nesneye Yönelik Programlama’ olarak karşılık bulur. Biz bu karşılıklardan, adı ‘Nesne Tabanlı Programlama’ olanı tercih edeceğiz.

Unutmadan, nesne tabanlı programlamaya girmeden önce değinmemiz gereken bir şey daha var. Eğer öğrendiğiniz ilk programlama dili Python ise, nesne tabanlı programlamayı öğrenmenin (aslında öyle olmadığı halde) zor olduğunu düşünebilir, bu konuyu biraz karmaşık bulabilirsiniz. Bu durumda da kaçınılmaz olarak kendi kendinize şu soruyu sorarsınız: Acaba ben nesne tabanlı programlamayı öğrenmek zorunda mıyım?

Bu sorunun kısa cevabı, eğer iyi bir programcı olmak istiyorsanız nesne tabanlı programlamayı öğrenmek zorundasınız, olacaktır.

Uzun cevap ise şu:

Nesne tabanlı programlama, pek çok yazılım geliştirme yönteminden yalnızca biridir. Siz bu yöntemi, yazdığınız programlarda kullanmak zorunda değilsiniz. Nesne tabanlı programlamadan hiç yararlanmadan da faydalı ve iyi programlar yazabilirsiniz elbette. Python sizi bu yöntemi kullanmaya asla zorlamaz. Ancak nesne tabanlı programlama yaklaşımı program geliştirme alanında oldukça yaygın kullanılan bir yöntemdir. Dolayısıyla, etrafta nesne tabanlı programlama yaklaşımından yararlanılarak yazılmış pek çok kodla karşılaşacaksınız. Hiç değilse karşılaştığınız bu kodları anlayabilmek için nesne tabanlı programlamayı biliyor ve tanıyor olmanız lazım. Aksi halde, bu yöntem kullanılarak geliştirilmiş programları anlayamazsınız.

Mesela, grafik bir arayüze sahip (yani düğmeli, menülü) programların ezici çoğunluğu nesne tabanlı programlama yöntemiyle geliştiriliyor. Grafik arayüz geliştirmenizi sağlayacak araçları tanımanızı, öğrenmenizi sağlayan kitaplar ve makaleler de bu konuları hep nesne tabanlı programlama yaklaşımı üzerinden anlatıyor.

Uyarı

Yalnız bu söylediğimizden, nesne tabanlı programlama sadece grafik arayüzlü programlar geliştirmeye yarar gibi bir anlam çıkarmamalısınız. Nesne tabanlı programlama, komut arayüzlü programlar geliştirmek için de kullanışlı bir programlama yöntemidir.

Sözün özü, nesne tabanlı programlamadan kaçamazsınız! İyi bir programcı olmak istiyorsanız, kendiniz hiç kullanmasanız bile, nesne tabanlı programlamayı öğrenmek zorundasınız. Hem şimdi nesne tabanlı programlamaya dudak bükseniz bile, bunu kullandıkça ve size sağladığı faydaları gördükçe onu siz de seveceksiniz…

Sınıflar

Nesne tabanlı programlamanın temelinde, yukarıdaki giriş bölümünde de adını andığımız ‘sınıf’ (class) adlı bir kavram bulunur. Bu bölümde, bu temel kavramı hakkıyla ele almaya çalışacağız.

Peki tam olarak nedir bu sınıf denen şey?

Çok kaba ve oldukça soyut bir şekilde tanımlayacak olursak, sınıflar, nesne üretmemizi sağlayan veri tipleridir. İşte nesne tabanlı programlama, adından da anlaşılacağı gibi, nesneler (ve dolayısıyla sınıflar) temel alınarak gerçekleştirilen bir programlama faaliyetidir.

‘Hiçbir şey anlamadım!’ dediğinizi duyar gibiyim. Çünkü yukarıdaki tanım, ‘nesne’ ne demek, ‘sınıf’ ne anlama geliyor gibi sorulara cevap vermiyor. Yani programcılık açısından ‘nesne’ ve ‘sınıf’ kelimelerini burada ne anlamda kullandığımızı, yukarıdaki tanıma bakarak kestiremiyoruz. Eğer siz de bu fikirdeyseniz okumaya devam edin…

Sınıflar Ne İşe Yarar?

Buraya gelene kadar Python’da pek çok veri tipi olduğunu öğrendik. Mesela önceki derslerimizde incelediğimiz listeler, demetler, karakter dizileri, sözlükler ve hatta fonksiyonlar hep birer veri tipidir. Bu tiplerin, verileri çeşitli şekillerde evirip çevirmemizi sağlayan birtakım araçlar olduğunu biliyoruz. İşte sınıflar da, tıpkı yukarıda saydığımız öteki veri tipleri gibi, verileri manipüle etmemizi sağlayan bir veri tipidir.

Peki bu bölümde ele alacağımız ‘sınıf’ (class) veri tipi ne işe yarar?

Dilerseniz bunu basit bir örnek üzerinde anlatmaya çalışalım.

Diyelim ki, kullanıcının girdiği bir kelimedeki sesli harfleri sayan bir kod yazmak istiyorsunuz. Bu amacı gerçekleştirebilmek için yazabileceğiniz en basit kod herhalde şu olacaktır:

sesli_harfler = 'aeıioöuü'
sayaç = 0

kelime = input('Bir kelime girin: ')

for harf in kelime:
    if harf in sesli_harfler:
        sayaç += 1

mesaj = '{} kelimesinde {} sesli harf var.'
print(mesaj.format(kelime, sayaç))

Düzgün bir şekilde çalışan, gayet basit kodlardır bunlar. Ayrıca amacımızı da kusursuz bir şekilde yerine getirir. Üstelik kodlardaki bütün öğeler tek bir isim/etki alanı (namespace, scope) içinde bulunduğu için, bunlara erişimde hiçbir zorluk çekmeyiz. Yani mesela sesli_harfler, sayaç, kelime, harf, mesaj değişkenlerine kodlar içinde her yerden erişebiliriz.

Not

Eğer isim/etki alanı ile ilgili söylediğimiz şeyi anlamadıysanız endişe etmeyin. Birazdan vereceğimiz örnekle durumu daha net kavrayacaksınız.

Ancak bu kodların önemli bir dezavantajı, kodlarda benimsediğimiz yaklaşımın genişlemeye pek müsait olmamasıdır. Daha doğrusu, yukarıdaki kodlara yeni kodlar ekledikçe programımız karmaşık hale gelecek, kodları anlamak zorlaşacaktır.

Kod yapısını biraz olsun rahatlatmak için bazı önlemler alabiliriz. Mesela kullanıcı tarafından girilen kelimedeki bir harfin sesli olup olmadığını denetleyen kodları bir fonksiyon içine alarak, o kısmı daha belirgin hale getirebiliriz:

sesli_harfler = 'aeıioöuü'
sayaç = 0

kelime = input('Bir kelime girin: ')

def seslidir(harf):
    return harf in sesli_harfler

for harf in kelime:
    if seslidir(harf):
        sayaç += 1

mesaj = '{} kelimesinde {} sesli harf var.'
print(mesaj.format(kelime, sayaç))

Burada, kontrol ettiğimiz harfin sesli_harfler adlı değişken içinde bulunup bulunmamasına göre True veya False çıktısı veren, seslidir() adlı bir fonksiyon tanımladık. Eğer kontrol ettiğimiz harf sesli_harfler değişkeni içinde geçiyorsa, yani bu bir sesli harf ise, seslidir() fonksiyonu True çıktısı verecektir. Aksi durumda ise bu fonksiyondan False çıktısı alacağız. Böylece sesli harf kontrolü yapmak istediğimiz her yerde yalnızca seslidir() fonksiyonunu kullanabileceğiz. Bu da bize, bir kez yazdığımız kodları tekrar tekrar kullanma imkanı verecek.

Eğer yukarıdaki kodları daha da genel amaçlı bir hale getirmek istersek, sayacı artıran kodları da bir fonksiyon içine almayı düşünebiliriz:

sesli_harfler = 'aeıioöuü'
sayaç = 0

kelime = input('Bir kelime girin: ')

def seslidir(harf):
    return harf in sesli_harfler

def artır():
    global sayaç
    for harf in kelime:
        if seslidir(harf):
            sayaç += 1
    return sayaç

mesaj = '{} kelimesinde {} sesli harf var.'
print(mesaj.format(kelime, artır()))

Hatırlarsanız, ilk başta yazdığımız kodların en büyük avantajının, kodlarda geçen bütün öğelerin tek bir isim/etki alanında bulunması olduğunu söylemiştik. Bu sayede bütün öğelere her yerden erişebiliyorduk. Yukarıdaki kodlarda ise birden fazla isim/etki alanı var:

  1. sesli_harfler, sayaç, kelime ve mesaj değişkenlerinin bulunduğu global isim/etki alanı.

  2. seslidir() fonksiyonunun lokal isim/etki alanı.

  3. artır() fonksiyonunun lokal isim/etki alanı.

Bildiğiniz gibi, global isim alanında bulunan değişkenlere her yerden ulaşabiliyoruz. Ancak bunları her yerden değiştiremiyoruz. Yani mesela global isim alanında bulunan sayaç değişkeninin değerini, seslidir() fonksiyonu içinden görüntüleyebiliriz.

Bunu teyit edelim:

sesli_harfler = 'aeıioöuü'
sayaç = 0

kelime = input('Bir kelime girin: ')

def seslidir(harf):
    print('sayaç değişkeninin değeri şu anda: ', sayaç)
    return harf in sesli_harfler

def artır():
    global sayaç
    for harf in kelime:
        if seslidir(harf):
            sayaç += 1
    return sayaç

mesaj = '{} kelimesinde {} sesli harf var.'
print(mesaj.format(kelime, artır()))

Gördüğünüz gibi, global isim alanındaki sayaç değişkeninin değerini seslidir() fonksiyonu içinde kullanabildik. Ama eğer bu değişken üzerinde değişiklik yapacaksak ilave adımlar atmak zorundayız. Dolayısıyla, mesela artır() fonksiyonunun etki alanından, global etki alanındaki sayaç değişkeni üzerinde değişiklik yapabilmek için global deyimini kullanmamız gerekiyor. Bu şekilde, global isim alanında bulunan sayaç adlı değişkenin değerini artırabiliyoruz.

Dikkat ederseniz, artır() fonksiyonunda iki tane global değişken var: sayaç ve kelime. Ama biz bunlardan yalnızca sayaç değişkenini global olarak belirledik. Öbür global değişkenimiz kelime için ise bu işlemi yapmadık. Çünkü kelime adlı değişkeni değiştirmek gibi bir niyetimiz yok. Biz bu değişkeni sadece kullanmakla yetiniyoruz. O yüzden bu değişkeni global olarak belirlemek zorunda değiliz.

Ancak bildiğiniz gibi, global deyimini kullanmak pek tavsiye edilen bir şey değil. Eğer siz de bu deyimi kullanmak istemezseniz, yukarıdaki kodları şu şekilde yazmayı yeğleyebilirsiniz:

sesli_harfler = 'aeıioöuü'
sayaç = 0

kelime = input('Bir kelime girin: ')

def seslidir(harf):
    return harf in sesli_harfler

def artır(sayaç):
    for harf in kelime:
        if seslidir(harf):
            sayaç += 1
    return sayaç

mesaj = '{} kelimesinde {} sesli harf var.'
print(mesaj.format(kelime, artır(sayaç)))

Gördüğünüz gibi, bu kodlarda global deyimini kullanmak yerine, artır() fonksiyonuna verdiğimiz sayaç parametresi üzerinden global isim alanıyla iletişim kurarak, sayaç değişkenini manipüle edebildik. Sadece değerini kullandığımız global değişken kelime için ise özel bir şey yapmamıza gerek kalmadı.

Bu arada, tabii ki, artır() fonksiyonunda parametre olarak kullandığımız kelime sayaç olmak zorunda değil. Kodlarımızı mesela şöyle de yazabilirdik:

sesli_harfler = 'aeıioöuü'
sayaç = 0

kelime = input('Bir kelime girin: ')

def seslidir(harf):
    return harf in sesli_harfler

def artır(n):
    for harf in kelime:
        if seslidir(harf):
            n += 1
    return n

mesaj = '{} kelimesinde {} sesli harf var.'
print(mesaj.format(kelime, artır(sayaç)))

Önemli olan, artır() fonksiyonunun, bizim global isim alanıyla iletişim kurmamızı sağlayacak bir parametre alması. Bu parametrenin adının ne olduğunun bir önemi yok.

Yukarıdaki kodlarda birkaç değişiklik daha yaparak, bu kodları iyice genişletilebilir hale getirebiliriz:

sesli_harfler = 'aeıioöuü'
sayaç = 0

def kelime_sor():
    return input('Bir kelime girin: ')

def seslidir(harf):
    return harf in sesli_harfler

def artır(sayaç, kelime):
    for harf in kelime:
        if seslidir(harf):
            sayaç += 1
    return sayaç

def ekrana_bas(kelime):
    mesaj = "{} kelimesinde {} sesli harf var."
    print(mesaj.format(kelime, artır(sayaç, kelime)))

def çalıştır():
    kelime = kelime_sor()
    ekrana_bas(kelime)

çalıştır()

Bu kodlarda, fonksiyonlara verdiğimiz parametreler yardımıyla, farklı fonksiyonların lokal etki alanlarında yer alan öğeler arasında nasıl iletişim kurduğumuza dikkat edin. Bir önceki kodlarda global etki alanında bulunan kelime değişkenini bu kez çalıştır() fonksiyonunun lokal etki alanı içine yerleştirdiğimiz için, artır() fonksiyonu içindeki kelime değişkeni boşa düştü. O yüzden, bu değişkeni artır() fonksiyonuna bir parametre olarak verdik ve ekrana_bas() fonksiyonu içinde bu fonksiyonu çağırırken, hem sayaç hem de kelime argümanlarını kullandık.

Ayrıca, kullanıcıya kelime sorup, aldığı kelimeyi ekrana basan kod parçalarını, yani programımızı başlatan kodları çalıştır() başlığı altında toplayarak bu kısmı tam anlamıyla ‘modüler’, yani esnek ve takılıp çıkarılabilir bir hale getirdik.

Gördüğünüz gibi, yazdığımız kodların olabildiğince anlaşılır ve yönetilebilir olmasını sağlayabilmek için, bu kodları küçük birtakım birimlere böldük. Bu şekilde hem hangi işlevin nerede olduğunu bulmak kolaylaştı, hem kodların görünüşü daha anlaşılır oldu, hem de bu kodlara ileride yeni özellikler eklemek basitleşti. Unutmayın, bir programcının görevi yalnızca çalışan kodlar yazmak değildir. Programcı aynı zamanda kodlarının okunaklılığını artırmak ve bakımını kolaylaştırmakla da yükümlüdür.

Bu bakımdan, programcı ile kod arasındaki ilişkiyi, yazar ile kitap arasındaki ilişkiye benzetebilirsiniz. Tıpkı bir programcı gibi, yazarın da görevi aklına gelenleri bir kağıda gelişigüzel boca etmek değildir. Yazar, yazdığı kitabın daha anlaşılır olmasını sağlamak için kitabına bir başlık atmalı, yazdığı yazıları alt başlıklara ve paragraflara bölmeli, ayrıca noktalama işaretlerini yerli yerinde kullanarak yazılarını olabildiğince okunaklı hale getirmelidir. Bir ana başlığı ve alt başlıkları olmayan, sadece tek bir büyük paragraftan oluşan, içinde hiçbir noktalama işaretinin kullanılmadığı bir makaleyi okumanın veya bu makaleye sonradan yeni bir şeyler eklemenin ne kadar zor olduğunu düşünün. İşte aynı şey bir programcının yazdığı kodlar için de geçerlidir. Eğer yazdığınız kodları anlaşılır birimlere bölmeden ekrana yığarsanız bu kodları ne başkaları okuyup anlayabilir, ne de siz ileride bu kodlara yeni işlevler ekleyebilirsiniz.

Python programlama dili, kodlarınızı olabildiğince anlaşılır, okunaklı ve yönetilebilir hale getirmeniz için size pek çok araç sunar. Önceki derslerde gördüğümüz değişkenler, fonksiyonlar ve modüller bu araçlardan yalnızca birkaçıdır. İşte bu bölümde inceleyeceğimiz sınıflar da kodlarımızı ehlileştirmek için kullanacağımız son derece faydalı araçlardır.

Birazdan, ‘sınıf’ denen bu faydalı araçları enine boyuna inceleyeceğiz. Ama gelin isterseniz, anlatmaya devam etmeden önce, verdiğimiz son kodları biraz daha kurcalayalım.

Hatırlarsanız, geçen bölümde, yazdığımız Python kodlarının aynı zamanda hem bağımsız bir program olarak hem de bir modül olarak kullanılabileceğini söylemiştik.

Mesela, yukarıdaki kodları sayac.py adlı bir dosyaya kaydettiğimizi varsayarsak, bu programı komut satırı üzerinden python sayac.py gibi bir kodla çalıştırabiliyoruz. Biz bu programı bu şekilde komut satırı üzerinden veya üzerine çift tıklayarak çalıştırdığımızda, bu kodları bağımsız bir program olarak çalıştırmış oluyoruz. Gelin bir de bu kodları bir modül olarak nasıl içe aktaracağımızı inceleyelim.

Şimdi, sayac.py programının bulunduğu dizin altında Python komut satırını başlatalım ve orada şu komutu vererek sayac modülünü içe aktaralım:

>>> import sayac

Bu komutu verdiğimiz anda, sayac.py programı çalışmaya başlayacaktır. Ancak bizim istediğimiz şey bu değil. Biz sayac.py programının çalışmaya başlamasını istemiyoruz. Bizim istediğimiz şey, bu sayac.py dosyasını bağımsız bir program olarak değil, bir modül olarak kullanmak ve böylece bu modül içindeki nitelik ve fonksiyonlara erişmek. Tam bu noktada şöyle bir soru aklımıza geliyor: Acaba bir insan neden bir programı modül olarak içe aktarmak istiyor olabilir?

Bir Python dosyasına modül olarak erişmek istemenizin birkaç sebebi olabilir. Mesela bir program yazıyorsunuzdur ve amacınız yazdığınız kodların düzgün çalışıp çalışmadığını test etmektir. Bunun için, programınızı etkileşimli kabuk ortamına bir modül olarak aktarıp, bu modülün test etmek istediğiniz kısımlarını tek tek çalıştırabilirsiniz. Aynı şekilde, kendi yazdığınız veya başkası tarafından yazılmış bir program içindeki işlevsellikten başka bir program içinde de yararlanmak istiyor olabilirsiniz. İşte bunun için de, ilgili programı, başka bir program içinden çağırarak, yani o programı öteki program içine bir modül olarak aktararak, ilgili modül içindeki işlevleri kullanabilirsiniz.

Diyelim ki biz, yukarıda yazdığımız sayac.py adlı dosya içindeki kodların düzgün çalışıp çalışmadığını kontrol etmek istiyoruz. Bunun için sayac.py dosyasındaki kodlarda şu değişikliği yapalım:

sesli_harfler = 'aeıioöuü'
sayaç = 0

def kelime_sor():
    return input('Bir kelime girin: ')

def seslidir(harf):
    return harf in sesli_harfler

def artır(sayaç, kelime):
    for harf in kelime:
        if seslidir(harf):
            sayaç += 1
    return sayaç

def ekrana_bas(kelime):
    mesaj = "{} kelimesinde {} sesli harf var."
    print(mesaj.format(kelime, artır(sayaç, kelime)))

def çalıştır():
    kelime = kelime_sor()
    ekrana_bas(kelime)

if __name__ == '__main__':
    çalıştır()

Gördüğünüz gibi, burada çalıştır() fonksiyonunu if __name__ == '__main__' bloğuna aldık. Buna göre, eğer __name__ niteliğinin değeri ‘__main__’ ise çalıştır() fonksiyonu işlemeye başlayacak. Aksi halde herhangi bir şey olmayacak.

Şimdi sayac.py programını komut satırı üzerinden python sayac.py gibi bir komutla çalıştırın. Programınız normal bir şekilde çalışacaktır. Çünkü, bildiğiniz gibi, bir Python programı bağımsız bir program olarak çalıştırıldığında __name__ niteliğinin değeri ‘__main__’ olur. Dolayısıyla da çalıştır() fonksiyonu işlemeye başlar.

Şimdi de etkileşimli kabuğu tekrar açın ve şu komutu vererek modülü içe aktarın:

>>> import sayac

Bu defa programımız çalışmaya başlamadı. Çünkü bu kez, programımızı bir modül olarak içe aktardığımız için, __name__ niteliğinin değeri ‘__main__’ değil, ilgili modülün adı oldu (yani bizim örneğimizde sayac).

Böylece __name__ niteliğinin farklı durumlarda farklı bir değere sahip olmasından yararlanarak, programınızın farklı durumlarda farklı tepkiler vermesini sağlamış olduk.

sayac modülünü içe aktardıktan sonra, bu modülün içinde neler olduğunu nasıl kontrol edebileceğinizi biliyorsunuz:

>>> dir(sayac)

['__builtins__', '__cached__', '__doc__', '__file__', '__loader__',
 '__name__', '__package__', '__spec__', 'artır', 'ekrana_bas',
 'kelime_sor', 'sayaç', 'sesli_harfler', 'seslidir', 'çalıştır']

Bu listede, sayac modülüne ait bütün nitelik ve fonksiyonları görebiliyoruz. Bunları, başka modüllerde olduğu gibi kullanma imkanına sahibiz.

Mesela bu listede görünen seslidir() fonksiyonunu kullanalım:

>>> sayac.seslidir('ö')

True

>>> sayac.seslidir('ç')

False

Gördüğünüz gibi, sayac.py içinde tanımladığımız seslidir() fonksiyonunu, rastgele harflerin birer sesli harf olup olmadığını denetlemek için de kullanabiliyoruz. Bu şekilde aynı zamanda seslidir() fonksiyonunun düzgün bir şekilde çalışıp çalışmadığını, sesli olan ve olmayan harfleri başarılı bir şekilde birbirinden ayırt edip edemediğini de test etmiş oluyoruz.

Devam edelim:

>>> sayac.sesli_harfler

'aeıioöuü'

Modüllerin ne kadar faydalı araçlar olabileceğini bu örnek gayet net bir şekilde gösteriyor. Eğer ileride sesli harfleri kullanmamızı gerektiren başka bir program yazacak olursak, bu harfleri yeniden tanımlamak yerine, sayac.py dosyasından içe aktarabiliriz.

Bütün bu örnekler sayesinde, sınıfları daha iyi anlamamızı sağlayacak altyapıyı oluşturmuş, bir yandan da eski bilgilerimizi pekiştirmiş olduk. Dilerseniz, sınıfları anlatmaya geçmeden önce, yukarıda verdiğimiz kodları sınıflı bir yapı içinde nasıl ifade edebileceğimizi de görelim.

Elbette aşağıdaki kodları anlamanızı şu aşamada sizden beklemiyoruz. Bu bölümün sonuna vardığımızda, zihninizde her şey berraklaşmış olacak. Siz şimdilik sadece aşağıdaki kodlara bakın ve hem okunaklılık hem de yönetilebilirlik bakımından bu kodların bize ne gibi faydalar sağlıyor olabileceğine dair fikir yürütmeye çalışın. Anlamadığınız kısımlar olursa bunları geçin gitsin. Anladığınız kısımlar ise yanınıza kâr kalsın.

class HarfSayacı:
    def __init__(self):
        self.sesli_harfler = 'aeıioöuü'
        self.sayaç = 0

    def kelime_sor(self):
        return input('Bir kelime girin: ')

    def seslidir(self, harf):
        return harf in self.sesli_harfler

    def artır(self):
        for harf in self.kelime:
            if self.seslidir(harf):
                self.sayaç += 1
        return self.sayaç

    def ekrana_bas(self):
        mesaj = "{} kelimesinde {} sesli harf var."
        sesli_harf_sayısı = self.artır()
        print(mesaj.format(self.kelime, sesli_harf_sayısı))

    def çalıştır(self):
        self.kelime = self.kelime_sor()
        self.ekrana_bas()

if __name__ == '__main__':
    sayaç = HarfSayacı()
    sayaç.çalıştır()

Hakkında herhangi bir fikre sahip olmadığınız bir kod parçasını anlamanın en iyi yolu, anlamadığınız kısmı kodlardan çıkarıp, kodları bir de o şekilde çalıştırmaktır. Mesela yukarıdaki __init__, self ve class gibi öğelerin ismini değiştirin, bunları kodlardan çıkarın veya başka bir yere koyun. Elde ettiğiniz sonuçları gözlemleyerek bu kodlar hakkında en azından bir fikir sahibi olabilirsiniz.

Gelin isterseniz, henüz yukarıdaki kodları anlayabilecek kadar sınıf bilgisine sahip olmasak da, bu kodları şöyle bir üstünkörü gözden geçirerek, bu kodların programcılık deneyimimiz açısından bize ne gibi bir katkı sunuyor olabileceğini anlamaya çalışalım.

Yukarıdaki kodlarda dikkatimizi çeken ilk şey, bu kodların son derece derli toplu görünüyor olmasıdır. Öyle ki, HarfSayacı adlı sınıf içindeki fonksiyonlar sanki ipe dizilir gibi dizilmiş.

HarfSayacı adlı sınıf ile bu sınıf yapısı içinde yer alan fonksiyonlar arasındaki ilişki gayet net bir şekilde görünüyor. Eğer ileride bu sayaca yeni bir işlev eklemek istersek, neyi nereye yerleştirmemiz gerektiği çok açık. Mesela ilerde bu kodlara sesli harflerle birlikte bir de sessiz harf denetim işlevi eklemek istersek, gerekli değişiklikleri kolayca yapabiliriz:

class HarfSayacı:
    def __init__(self):
        self.sesli_harfler = 'aeıioöuü'
        self.sessiz_harfler = 'bcçdfgğhjklmnprsştvyz'
        self.sayaç_sesli = 0
        self.sayaç_sessiz = 0

    def kelime_sor(self):
        return input('Bir kelime girin: ')

    def seslidir(self, harf):
        return harf in self.sesli_harfler

    def sessizdir(self, harf):
        return harf in self.sessiz_harfler

    def artır(self):
        for harf in self.kelime:
            if self.seslidir(harf):
                self.sayaç_sesli += 1
            if self.sessizdir(harf):
                self.sayaç_sessiz += 1
        return (self.sayaç_sesli, self.sayaç_sessiz)

    def ekrana_bas(self):
        sesli, sessiz = self.artır()
        mesaj = "{} kelimesinde {} sesli {} sessiz harf var."
        print(mesaj.format(self.kelime, sesli, sessiz))

    def çalıştır(self):
        self.kelime = self.kelime_sor()
        self.ekrana_bas()

if __name__ == '__main__':
    sayaç = HarfSayacı()
    sayaç.çalıştır()

Ayrıca sınıflı kodlarda, farklı etki alanları ile iletişim kurmak, sınıfsız kodlara kıyasla daha zahmetsizdir. Sınıflı ve sınıfsız kodlarda fonksiyonlara verdiğimiz parametreleri birbirleri ile kıyaslayarak bu durumu kendiniz de görebilirsiniz.

Sınıflı yapıların daha pek çok avantajlı yönü vardır. İşte biz bu bölümde bunları size tek tek göstermeye çalışacağız.

Sınıf Tanımlamak

Nesne tabanlı programlama yaklaşımı, özellikle birtakım ortak niteliklere ve davranış şekillerine sahip gruplar tanımlamak gerektiğinde son derece kullanışlıdır. Mesela şöyle bir örnek düşünün: Diyelim ki çalıştığınız işyerinde, işe alınan kişilerin kayıtlarını tutan bir veritabanınız var. Bir kişi işe alındığında, o kişiye dair belli birtakım bilgileri bu veritabanına işliyorsunuz. Mesela işe alınan kişinin adı, soyadı, unvanı, maaşı ve buna benzer başka bilgiler…

Çalışmaya başlayacak kişileri temsil eden bir ‘Çalışan’ grubunu, bu grubun nitelikleri ile faaliyetlerini tutacak yapıyı ve bu grubun bütün öğelerinin taşıyacağı özellikleri nesne tabanlı programlama yaklaşımı ile kolayca kodlayabilirsiniz.

Aynı şekilde, mesela yazdığınız bir oyun programı için, bir ‘Asker’ grubunu nesne tabanlı programlama mantığı içinde tanımlayarak, bu grubun her bir üyesinin sahip olacağı nitelikleri, kabiliyetleri ve davranış şekillerini kodlayabilir; mesela askerlerin sağa sola nasıl hareket edeceklerini, hangi durumlarda puan/enerji/güç kazanacaklarını veya kaybedeceklerini, bir asker ilk kez oluşturulduğunda hangi özellikleri taşıyacağını ve aklınıza gelebilecek başka her türlü özelliği tek tek belirleyebilirsiniz.

Amacınız ne olursa olsun, atmanız gereken ilk adım, ilgili sınıfı tanımlamak olmalıdır. Zira fonksiyonlarda olduğu gibi, bir sınıfı kullanabilmek için de öncelikle o sınıfı tanımlamamız gerekiyor. Mesela, yukarıda bahsettiğimiz işe uygun olarak, Çalışan adlı bir sınıf tanımlayalım:

class Çalışan:
    pass

Yukarıdaki, boş bir sınıf tanımıdır. Hatırlarsanız fonksiyonları tanımlamak için def adlı bir ifadeden yararlanıyorduk. İşte sınıfları tanımlamak için de class adlı bir ifadeden yararlanıyoruz. Bu ifadenin ardından gelen Çalışan kelimesi ise bu sınıfın adıdır.

Eğer arzu ederseniz, yukarıdaki sınıfı şu şekilde de tanımlayabilirsiniz:

class Çalışan():
    pass

Yani sınıf adından sonra parantez kullanmayabileceğiniz gibi, kullanabilirsiniz de. Her ikisi de aynı kapıya çıkar. Ayrıca sınıf adlarında, yukarıda olduğu gibi büyük harf kullanmak ve birden fazla kelimeden oluşan sınıf adlarının ilk harflerini büyük yazıp bunları birleştirmek adettendir. Yani:

class ÇalışanSınıfı():
    pass

Veya parantezsiz olarak:

class ÇalışanSınıfı:
    pass

Gördüğünüz gibi sınıf tanımlamak fonksiyon tanımlamaya çok benziyor. Fonksiyonları tanımlarken nasıl def deyimini kullanıyorsak, sınıfları tanımlamak için de class deyimini kullanıyoruz.

Örnek olması açısından, yukarıda bahsettiğimiz ‘Asker’ grubu için de bir sınıf tanımlayalım:

class Asker:
    pass

… veya:

class Asker():
    pass

Python’da sınıfları nasıl tanımlayacağımızı öğrendiğimize göre, bu sınıfları nasıl kullanacağımızı incelemeye geçebiliriz.

Sınıf Nitelikleri

Yukarıda, boş bir sınıfı nasıl tanımlayacağımızı öğrendik. Elbette tanımladığımız sınıflar hep boş kalmayacak. Bu sınıflara birtakım nitelikler ekleyerek bu sınıfları kullanışlı hale getirebiliriz. Mesela:

class Çalışan():
    kabiliyetleri = []
    unvanı = 'işçi'

Burada unvanı ve kabiliyetleri adlı iki değişken tanımladık. Teknik dilde bu değişkenlere ‘sınıf niteliği’ (class attribute) adı verilir.

Biraz önce, sınıf tanımlamayı öğrenirken sınıf tanımlamanın fonksiyon tanımlamaya çok benzediğini söylemiştik. Gerçekten de öyledir. Ancak fonksiyonlarla sınıflar arasında (başka farkların dışında) çok önemli bir fark bulunur. Bildiğiniz gibi, bir fonksiyonu tanımladıktan sonra, o fonksiyonun işlemeye başlaması için, o fonksiyonun mutlaka çağrılması gerekir. Çağrılmayan fonksiyonlar çalışmaz. Mesela yukarıdaki sınıfa benzeyen şöyle bir fonksiyon tanımladığımızı düşünün:

def çalışan():
    kabiliyetleri = []
    unvanı = 'işçi'

    print(kabiliyetleri)
    print(unvanı)

Bu fonksiyonun çalışması için, kodlarımızın herhangi bir yerinde bu fonksiyonu çağırmamız lazım:

çalışan()

Ancak sınıflar farklıdır. Bunu görmek için yukarıdaki fonksiyonu bir sınıf haline getirelim:

class Çalışan():
    kabiliyetleri = []
    unvanı = 'işçi'

    print(kabiliyetleri)
    print(unvanı)

Bu kodları mesela deneme.py adlı bir dosyaya kaydedip çalıştırdığınızda, unvanı ve kabiliyetleri değişkenlerinin değerinin ekrana basıldığını göreceksiniz.

Aynı şey, yukarıdaki kodların bir modül olarak içe aktarıldığı durumlarda da geçerlidir. Yani yukarıdaki kodların deneme.py adlı bir dosyada bulunduğunu varsayarsak, bu modülü şu komutla içe aktardığımızda, sınıfı kodlarımızın herhangi bir yerinde çağırmamış olmamıza rağmen sınıf içeriği çalışmaya başlayacaktır:

>>> import deneme

[]
işçi

Eğer sınıf niteliklerinin ne zaman çalışacağını kendiniz kontrol etmek isterseniz, bu nitelikleri sınıf dışında kullanabilirsiniz:

class Çalışan():
    kabiliyetleri = []
    unvanı = 'işçi'

print(Çalışan.kabiliyetleri)
print(Çalışan.unvanı)

Burada Çalışan() adlı sınıfın niteliklerine nasıl eriştiğimize dikkat edin. Gördüğünüz gibi, sınıf niteliklerine erişmek için doğrudan sınıfın adını parantezsiz bir şekilde kullanıyoruz. Eğer sınıf adlarını parantezli bir şekilde yazarsak başka bir şey yapmış oluruz. Bundan biraz sonra bahsedeceğiz. Biz şimdilik, sınıf niteliklerine erişmek için sınıf adlarını parantezsiz kullanmamız gerektiğini bilelim yeter.

Hatırlarsanız, bu bölüme başlarken, nesne tabanlı programlama yaklaşımının, özellikle birtakım ortak niteliklere ve davranış şekillerine sahip gruplar tanımlamak gerektiğinde son derece kullanışlı olduğunu söylemiştik. Gelin isterseniz yukarıdaki Çalışan() sınıfına birkaç nitelik daha ekleyerek bu iddiamızı destekleyelim:

class Çalışan():
    kabiliyetleri = []
    unvanı = 'işçi'
    maaşı = 1500
    memleketi = ''
    doğum_tarihi = ''

Burada belli kabiliyetleri, unvanı, maaşı, memleketi ve doğum_tarihi olan bir Çalışan() sınıfı tanımladık. Yani ‘Çalışan’ adlı bir grubun ortak niteliklerini belirledik. Elbette her çalışanın memleketi ve doğum tarihi farklı olacağı için sınıf içinde bu değişkenlere belli bir değer atamadık. Bunların birer karakter dizisi olacağını belirten bir işaret olması için yalnızca memleketi ve doğum_tarihi adlı birer boş karakter dizisi tanımladık.

Yukarıda tanımladığımız sınıf niteliklerine, doğrudan sınıf adını kullanarak erişebileceğimizi biliyorsunuz:

print(Çalışan.maaşı)
print(Çalışan.memleketi)
print(Çalışan.doğum_tarihi)

Eğer isterseniz bu sınıfa yeni sınıf nitelikleri de ekleyebilirsiniz:

Çalışan.isim = 'Ahmet'
Çalışan.yaş = 40

Gayet güzel…

Ancak burada şöyle bir sorun var: Biz yukarıdaki gibi doğrudan sınıf adını kullanarak öğelere eriştiğimizde kodlarımız tek kullanımlık olmuş oluyor. Yani bu şekilde ancak tek bir Çalışan() nesnesi (‘nesne’ kavramına ilerde değineceğiz), dolayısıyla da tek bir çalışan oluşturma imkanı elde edebiliyoruz. Ama biz, mantıken, sınıf içinde belirtilen özellikleri taşıyan, Ahmet, Mehmet, Veli, Selim, Selin ve buna benzer, istediğimiz sayıda çalışan oluşturabilmeliyiz. Peki ama nasıl?

Sınıfların Örneklenmesi

Biraz önce şöyle bir sınıf tanımlamıştık:

class Çalışan():
    kabiliyetleri = []
    unvanı = 'işçi'
    maaşı = 1500
    memleketi = ''
    doğum_tarihi = ''

Daha önce de söylediğimiz gibi, sınıflar belli birtakım ortak özelliklere sahip gruplar tanımlamak için biçilmiş kaftandır. Burada da, her bir çalışan için ortak birtakım nitelikler tanımlayan Çalışan() adlı bir sınıf oluşturduk. Ancak elbette bu sınıfın bir işe yarayabilmesi için, biraz önce de değindiğimiz gibi, bu sınıfı temel alarak, bu sınıfta belirtilen nitelikleri taşıyan birden fazla sınıf üyesi meydana getirebilmemiz lazım.

Şimdi dikkatlice bakın:

class Çalışan():
    kabiliyetleri = []
    unvanı = 'işçi'
    maaşı = 1500
    memleketi = ''
    doğum_tarihi = ''

ahmet = Çalışan()

Burada sınıfımızı ahmet adlı bir değişkene atadık.

İşte bu işleme teknik dilde ‘örnekleme’ veya ‘örneklendirme’ (instantiation) adı verilir. Bu işlemi fonksiyon çağırma ile kıyaslayabiliriz: Python programlama dilinde bir fonksiyonu kullanışlı hale getirme işlemine ‘çağırma’, bir sınıfı kullanışlı hale getirme işlemine ise ‘örnekleme’ adı veriyoruz.

Örnekleme kavramını daha iyi anlayabilmek için başka bir sınıf daha oluşturalım:

class Asker():
    rütbesi = 'Er'
    standart_teçhizat = ['G3', 'kasatura', 'süngü', 'el bombası']
    gücü = 60
    birliği = ''

Burada da belli birtakım niteliklere sahip Asker() adlı bir sınıf tanımladık. Bu sınıfın niteliklerine doğrudan sınıf adını kullanarak erişebileceğimizi biliyorsunuz:

Asker.rütbesi
Asker.standart_teçhizat
Asker.gücü
Asker.birliği

Ama bu sınıfın bir işe yarayabilmesi için, bu sınıfa bir ‘referans’ oluşturmamız lazım, ki daha sonra bu sınıfa bu referans üzerinden atıfta bulunabilelim. Yani bu sınıfı çağırırken buna bir isim vermeliyiz, ki bu isim üzerinden sınıfa ve niteliklerine erişebilelim.

Mesela bu sınıfa daha sonra atıfta bulunabilmek amacıyla, bu sınıf için mehmet adlı bir referans noktası oluşturalım:

mehmet = Asker()

İşte, teknik olarak ifade etmemiz gerekirse, sınıfları bir isme atama işlemine örnekleme (veya örneklendirme) adı veriyoruz.

Burada ahmet ve mehmet, ait oldukları sınıfların birer ‘sureti’ veya başka bir deyişle ‘örneği’dir (instance). mehmet’in, Asker() adlı sınıfın bir örneği, ahmet’inse Çalışan() adlı sınıfın bir örneği olması demek, mehmet’in ve ahmet’in, ilgili sınıfların bütün özelliklerini taşıyan birer üyesi olması demektir.

Uyarı

Bu bağlamda ‘örnek’ kelimesini ‘misal’ anlamında kullanmadığımıza özellikle dikkatinizi çekmek isterim. Türkçede ‘örnek’ kelimesi ile karşıladığımız ‘instance’ kavramı, nesne tabanlı programlamanın önemli teknik kavramlarından biridir.

Biz bir sınıfı çağırdığımızda (yani Asker() veya Çalışan() komutunu verdiğimizde), o sınıfı örneklemiş oluyoruz. Örneklediğimiz sınıfı bir değişkene atadığımızda ise o sınıfın bir örneğini çıkarmış, yani o sınıfın bütün özelliklerini taşıyan bir üye meydana getirmiş oluyoruz.

Bu arada, elbette bu teknik terimleri ezberlemek zorunda değilsiniz. Ancak nesne tabanlı programlamaya ilişkin metinlerde bu terimlerle sık sık karşılaşacaksınız. Eğer bu terimlerin anlamını bilirseniz, okuduğunuz şey zihninizde daha kolay yer edecek, aksi halde, sürekli ne demek olduğunu bilmediğiniz terimlerle karşılaşmak öğrenme motivasyonunuza zarar verecektir.

Esasında nesne tabanlı programlamayı öğrencilerin gözünde zor kılan şey, bu programlama yaklaşımının özünden ziyade, içerdiği terimlerdir. Gerçekten de nesne tabanlı programlama, pek çok çetrefilli teknik kavramı bünyesinde barındıran bir sistemdir. Bu nedenle öğrenciler bu konuya ilişkin bir şeyler okurken, muğlak kavramların arasında kaybolup konunun esasını gözden kaçırabiliyor. Eğer nesne tabanlı programlamaya ilişkin kavramları hakkıyla anlarsanız, bu yaklaşıma dair önemli bir engeli aşmışsınız demektir.

Öte yandan, nesne tabanlı programlamaya ilişkin kavramları anlamak sadece Türkçe okuyup yazanlar için değil, aynı zamanda İngilizce bilip ilgili makaleleri özgün dilinden okuyanlar için de zor olabilir. O yüzden biz bu bölümde, kavramların Türkçeleri ile birlikte İngilizcelerini de vererek, İngilizce bilenlerin özgün metinleri okurken konuyu daha iyi anlamalarını sağlamaya çalışacağız. Dolayısıyla, bir kavramdan bahsederken onun aslının ne olduğunu da belirtmemiz, İngilizce bilip de konuyu daha ileri bir düzeyde araştırmak isteyenlere kolaylık sağlayacaktır.

Ne diyorduk? Eğer elimizde şöyle bir kod varsa:

class Sipariş():
    firma = ''
    miktar = 0
    sipariş_tarihi = ''
    teslim_tarihi = ''
    stok_adedi = 0


jilet = Sipariş()

Burada class, sınıfı tanımlamamıza yarayan bir öğedir. Tıpkı fonksiyonlardaki def gibi, sınıfları tanımlamak için de class adlı bir parçacığı kullanıyoruz.

Sipariş ise, sınıfımızın adı oluyor. Biz sınıfımızın adını parantezli veya parantezsiz olarak kullanma imkanına sahibiz.

Sınıfın gövdesinde tanımladığımız şu değişkenler birer sınıf niteliğidir (class attribute):

firma = ''
miktar = 0
sipariş_tarihi = ''
teslim_tarihi = ''
stok_adedi = 0

jilet = Sipariş() komutunu verdiğimizde ise, biraz önce tanımladığımız sınıfı örnekleyip (instantiation), bunu jilet adlı bir örneğe (instance) atamış oluyoruz. Yani jilet, Sipariş() adlı sınıfın bir örneği olmuş oluyor. Bir sınıftan istediğimiz sayıda örnek çıkarabiliriz:

kalem = Sipariş()
pergel = Sipariş()
çikolata = Sipariş()

Bu şekilde Sipariş() sınıfını üç kez örneklemiş, yani bu sınıfın bütün özelliklerini taşıyan üç farklı üye meydana getirmiş oluyoruz.

Bu sınıf örneklerini kullanarak, ilgili sınıfın niteliklerine (attribute) erişebiliriz:

kalem = Sipariş()

kalem.firma
kalem.miktar
kalem.sipariş_tarihi
kalem.teslim_tarihi
kalem.stok_adedi

Bildiğiniz gibi, eriştiğimiz bu nitelikler birer sınıf niteliği olduğu için, sınıfı hiç örneklemeden, bu niteliklere doğrudan sınıf adı üzerinden de erişebilirdik:

Sipariş.firma
Sipariş.miktar
Sipariş.sipariş_tarihi
Sipariş.teslim_tarihi
Sipariş.stok_adedi

Özellikle, örneklenmesine gerek olmayan, yalnızca bir kez çalışacak sınıflarda, sınıf niteliklerine örnekler üzerinden değil de doğrudan sınıf adı üzerinden erişmek daha pratik olabilir. Ancak yukarıda olduğu gibi, tek bir sınıftan, ortak niteliklere sahip birden fazla üye oluşturmamız gereken durumlarda sınıfı bir örneğe atayıp, sınıf niteliklerine bu örnek üzerinden erişmek çok daha akıllıca olacaktır. Ancak her koşulda sınıfların niteliklerine doğrudan sınıf adları üzerinden erişmek yerine örnekler üzerinden erişmeyi tercih etmenizin de hiçbir sakıncası olmadığını bilin.

Gelin şimdi yukarıda öğrendiklerimizi kullanarak ufak tefek uygulama çalışmaları yapalım.

Sınıfımız şu olsun:

class Sipariş():
    firma = ''
    miktar = 0
    sipariş_tarihi = ''
    teslim_tarihi = ''
    stok_adedi = 0

Bildiğiniz gibi, ufak tefek kod çalışmaları yapmak için Python’ın etkileşimli kabuğu son derece uygun bir ortamdır. O halde yukarıdaki sınıfı sipariş.py adlı bir dosyaya kaydedelim, bu dosyanın bulunduğu konumda bir etkileşimli kabuk ortamı açalım ve sipariş.py dosyasını bir modül olarak içe aktaralım:

>>> import sipariş

Böylece sipariş modülü içindeki nitelik ve metotlara erişim sağladık. Bunu teyit edelim:

>>> dir(sipariş)

['Sipariş', '__builtins__', '__cached__', '__doc__', '__file__',
 '__loader__', '__name__', '__package__', '__spec__']

Sipariş() adlı sınıfı listenin en başında görebilirsiniz. O halde gelin bu sınıfı örnekleyerek kullanılabilir hale getirelim:

>>> gofret = sipariş.Sipariş()

Elbette Sipariş() adlı sınıf sipariş adlı modül içinde bulunduğundan, bu sınıfa sipariş önekiyle erişiyoruz. Tabii biz isteseydik modülü şu şekilde de içe aktarabilirdik:

>>> from sipariş import Sipariş

Böylece Sipariş() sınıfına öneksiz olarak erişebilirdik:

>>> gofret = Sipariş()

Ancak mevcut isim alanını kirletmemek ve bu alanı nereden geldiği belli olmayan birtakım nitelik ve metotlarla doldurmamak için biz import modül_adı biçimini tercih ediyoruz. Aksi halde, bu kodları okuyanlar, Sipariş() adlı sınıfın sipariş adlı bir modüle ait olduğunu anlamayacak, bu sınıfı ilk olarak mevcut dosya içinde bulmaya çalışacaklardır. Ama biz modül adını sınıf adına eklediğimizde modülün nereden geldiği gayet açık bir şekilde anlaşılabiliyor. Böylece hem kodları okuyan başkalarının işini hem de birkaç ay sonra kendi kodlarımıza tekrar bakmak istediğimizde kendi işimizi kolaylaştırmış oluyoruz.

Neyse… Lafı daha fazla dolandırmadan kaldığımız yerden devam edelim…

Sınıfımızı şu şekilde içe aktarmış ve örneklemiştik:

>>> import sipariş
>>> gofret = sipariş.Sipariş()

Gelin şimdi bir de gofret örneğinin (instance) içeriğini kontrol edelim:

>>> dir(gofret)

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__',
 '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',
 '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__',
 '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
 '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'firma',
 'miktar', 'sipariş_tarihi', 'stok_adedi', 'teslim_tarihi']

Gördüğünüz gibi, sınıf içinde tanımladığımız bütün sınıf nitelikleri (firma, miktar, sipariş_tarihi, stok_adedi ve teslim_tarihi) bu liste içinde var.

Bu sınıf niteliklerinden, adı firma olanı kullanarak siparişin hangi firmadan yapılacağını belirleyebiliriz:

>>> gofret.firma = 'Öz İstihza ve Şerikleri Gıda, Ticaret Anonim Şirketi'

Böylece, sınıf içindeki bir niteliğe yeni bir değer atamış olduk. İsterseniz sipariş miktarını da belirleyelim:

>>> gofret.miktar = 1000

Öteki sınıf niteliklerini de ihtiyacınıza göre ayarlayabilir, hatta bu sınıfa yeni nitelikler de ekleyebilirsiniz.

Gelin isterseniz pratik olması bakımından bir örnek daha verelim.

Elimizde şöyle bir sınıf olsun:

class Çalışan():
    kabiliyetleri = []
    unvanı = 'işçi'
    maaşı = 1500
    memleketi = ''
    doğum_tarihi = ''

Burada kabiliyetleri, unvanı, maaşı, memleketi ve doğum_tarihi adlı beş adet değişken tanımladık. Teknik dilde bu değişkenlere ‘sınıf niteliği’ (class attribute) adı verildiğini biliyorsunuz.

Çalışan() sınıfı içindeki niteliklere erişmek için birkaç tane örnek çıkaralım:

ahmet = Çalışan()
mehmet = Çalışan()
ayşe = Çalışan()

Bu şekilde Çalışan() sınıfının üç farklı örneğini oluşturmuş olduk. Bu sınıfın niteliklerine, oluşturduğumuz bu örnekler üzerinden erişebiliriz:

print(ahmet.kabiliyetleri)
print(ahmet.unvanı)

print(mehmet.maaşı)
print(mehmet.memleketi)

print(ayşe.kabiliyetleri)
print(ayşe.doğum_tarihi)

Çıkardığımız örnekler aracılığıyla sınıf nitelikleri üzerinde değişiklik de yapabiliyoruz:

ahmet.kabiliyetleri.append('prezantabl')

Şimdi burada bir duralım. Çünkü burada çok sinsi bir sorunla karşı karşıyayız. Dikkatlice bakın.

Çalışan() sınıfı için bir ahmet örneği oluşturalım:

ahmet = Çalışan()

Buna ‘prezantabl’ kabiliyetini ekleyelim:

ahmet.kabiliyetleri.append('prezantabl')

Bu kabiliyetin eklendiğini teyit edelim:

print(ahmet.kabiliyetleri)

Şimdi Çalışan() sınıfının bir başka örneğini oluşturalım:

selim = Çalışan()

Bu örneğin kabiliyetlerini kontrol edelim:

print(selim.kabiliyetleri)

Gördüğünüz gibi, yalnızca ahmet örneğine eklemek istediğimiz ‘prezantabl’ kabiliyeti selim örneğine de eklenmiş. Ancak normal şartlarda arzu edilen bir şey değildir bu. Zira bu durum aslında programımızdaki bir tasarım hatasına işaret eder. Peki ama bu durumun sebebi nedir?

Hatırlarsanız, sınıf niteliklerinden bahsederken, bu niteliklerin önemli bir özelliğinin, sınıf çağrılmadan çalışmaya başlamaları olduğunu söylemiştik. Sınıf niteliklerinin bir başka önemli özelliği de, bu niteliklere atanan değerlerin ve eğer yapılabiliyorsa bu değerler üzerinde sonradan yapılan değişikliklerin o sınıfın bütün örneklerini etkiliyor olmasıdır. Eğer ilgili sınıf niteliği; karakter dizisi, demet ve sayı gibi değiştirilemeyen (immutable) bir veri tipi ise bu sınıf niteliği üzerinde zaten değişiklik yapamazsınız. Yaptığınız şey ancak ilgili sınıf niteliğini yeniden tanımlamak olacaktır. Ancak eğer sınıf niteliği, liste, sözlük ve küme gibi değiştirilebilir (mutable) bir veri tipi ise bu nitelik üzerinde yapacağınız değişiklikler bütün sınıf örneklerine yansıyacaktır. Yazdığınız program açısından bu özellik arzu ettiğiniz bir şey olabilir veya olmayabilir. Önemli olan, sınıf niteliklerinin bu özelliğinin farkında olmanız ve kodlarınızı bu bilgi çerçevesinde yazmanızdır. Mesela yukarıdaki örnekte kabiliyetleri listesine eklenen öğelerin bütün örneklere yansıması istediğimiz bir şey değil. Ama eğer sınıfımız şöyle olsaydı:

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

Burada personel_listesi adlı bir sınıf niteliği tanımladık. Eğer bu listenin, personele eklenen bütün elemanları barındırmasını planlıyorsak bu listenin her örneklemede büyümesi elbette istediğimiz bir şey olacaktır.

Peki o halde biz değerinin her örnekte ortak değil de her örneğe özgü olmasını istediğimiz nitelikleri nasıl tanımlayacağız? Elbette sınıf nitelikleri yerine örnek nitelikleri denen başka bir kavramdan yararlanarak…

Örnek Nitelikleri

Şimdiye kadar öğrendiklerimiz, sınıflarla faydalı işler yapmamız için pek yeterli değildi. Sınıflar konusunda ufkumuzun genişleyebilmesi için, sınıf niteliklerinin (class attributes) yanı sıra, nesne tabanlı programlamanın önemli bir parçası olan örnek niteliklerinden (instance attributes) de söz etmemiz gerekiyor. Hem örnek niteliklerini öğrendikten sonra, bunların sınıf nitelikleri ile arasındaki farkları görünce sınıf niteliklerini de çok daha iyi anlamış olacaksınız.

__init__ Fonksiyonu ve self

Buraya gelene kadar, sınıflar ile ilgili verdiğimiz kod parçaları yalnızca sınıf niteliklerini içeriyordu. Mesela yukarıda tanımladığımız Çalışan() sınıfı içindeki unvanı ve kabiliyetleri adlı değişkenlerin birer sınıf niteliği olduğunu biliyoruz.

Sınıf nitelikleri dışında, Python’da bir de örnek nitelikleri bulunur.

Bildiğiniz gibi, Python’da sınıf niteliklerini tanımlamak için yapmamız gereken tek şey, sınıf tanımının hemen altına bunları alelade birer değişken gibi yazmaktan ibarettir:

class Sınıf():
    sınıf_niteliği1 = 0
    sınıf_niteliği2 = 1

Örnek niteliklerini tanımlamak için ise iki yardımcı araca ihtiyacımız var: __init__() fonksiyonu ve self.

Bu iki aracı şu şekilde kullanıyoruz:

class Çalışan():
    def __init__(self):
        self.kabiliyetleri = []

Bu arada, __init__() fonksiyonunun nasıl yazıldığına dikkat ediyoruz. init kelimesinin sağında ve solunda ikişer adet alt çizgi (_) bulunduğunu gözden kaçırmıyoruz. Ayrıca, __init__() fonksiyonunu def ifadesine bitişik yazmamaya da bilhassa özen gösteriyoruz.

‘init’ kelimesinin solunda ve sağında bulunan alt çizgiler sizi sakın ürkütmesin. Aslında __init__(), alelade bir fonksiyondan başka bir şey değildir. Bu fonksiyonun öteki fonksiyonlardan tek farkı, sınıflar açısından biraz özel bir anlam taşıyor olmasıdır. Bu özel fonksiyonun görevi, sınıfımızı örneklediğimiz sırada, yani mesela ahmet = Çalışan() gibi bir komut verdiğimiz anda oluşturulacak nitelikleri ve gerçekleştirilecek işlemleri tanımlamaktır. Bu fonksiyonun ilk parametresi her zaman self olmak zorundadır. Bu açıklama ilk anda kulağınıza biraz anlaşılmaz gelmiş olabilir. Ama hiç endişe etmeyin. Bu bölümün sonuna vardığınızda bu iki öğeyi, adınızı bilir gibi biliyor olacaksınız.

Hatırlarsanız, sınıf niteliklerini anlatırken bunların önemli bir özelliğinin, sınıfın çağrılmasına gerek olmadan çalışmaya başlaması olduğunu söylemiştik:

class Çalışan():
    selam = 'merhaba'
    print(selam)

Bu kodları çalıştırdığımız anda ekrana ‘merhaba’ çıktısı verilecektir. Örnek nitelikleri ise farklıdır:

class Çalışan():
    def __init__(self):
        self.kabiliyetleri = []
        print(self.kabiliyetleri)

Bu kodları çalıştırdığınızda herhangi bir çıktı almazsınız. Bu kodların çıktı verebilmesi için sınıfımızı mutlaka örneklememiz lazım:

class Çalışan():
    def __init__(self):
        self.kabiliyetleri = []
        print(self.kabiliyetleri)

Çalışan()

Çünkü self.kabiliyetleri bir sınıf niteliği değil, bir örnek niteliğidir. Örnek niteliklerine erişebilmek için de ilgili sınıfı mutlaka örneklememiz gerekir. Ayrıca sınıf niteliklerinin aksine, örnek niteliklerine sınıf adları üzerinden erişemeyiz. Yani self.kabiliyetleri adlı örnek niteliğine erişmeye yönelik şöyle bir girişim bizi hüsrana uğratacaktır:

Çalışan.kabiliyetleri

Bu örnek niteliğine erişmek için örneklendirme mekanizmasından yararlanmamız lazım:

Çalışan().kabiliyetleri #parantezlere dikkat!

Gelin isterseniz, örneklendirme işlemini daha kullanışlı bir hale getirmek için, örneklendirdiğimiz sınıfı bir örneğe atayalım, yani bu sınıfın bir örneğini çıkaralım:

ahmet = Çalışan()

ahmet = Çalışan() kodu yardımıyla, Çalışan sınıfının bir örneğini çıkardık ve buna ahmet adını verdik. İşte tam bu anda __init__() fonksiyonu çalışmaya başladı ve ahmet örneği için, kabiliyetleri adlı boş bir örnek niteliği oluşturdu.

Peki yukarıda kodlarımızı yazarken __init__() fonksiyonuna parametre olarak verdiğimiz ve kabiliyetleri listesinin başında kullandığımız self kelimesi ne oluyor?

Öncelikle bilmemiz gereken şey, self kelimesinin, Python programlama dilinin söz diziminin gerektirdiği bir öğe olduğudur. Bu kelime, Çalışan() adlı sınıfın örneklerini temsil eder. Peki ‘self kelimesinin bir sınıfın örneklerini temsil ediyor olması’ ne anlama geliyor?

Bildiğiniz gibi, bir sınıfın örneğini şu şekilde çıkarıyoruz:

ahmet = Çalışan()

Bu ahmet örneğini kullanarak, Çalışan() sınıfının içindeki kabiliyetleri adlı örnek niteliğine sınıf dışından erişebiliriz:

print(ahmet.kabiliyetleri)

İşte self kelimesi, yukarıdaki kodda yer alan ahmet kelimesini temsil ediyor. Yani ahmet.kabiliyetleri şeklinde bir kod yazabilmemizi sağlayan şey, __init__() fonksiyonu içinde belirttiğimiz self kelimesidir. Eğer bu kelimeyi kullanmadan şöyle bir kod yazarsak:

class Çalışan():
    def __init__():
        kabiliyetleri = []

…artık aşağıdaki kodlar yardımıyla kabiliyetleri niteliğine erişemeyiz:

ahmet = Çalışan()
print(ahmet.kabiliyetleri)

Şimdi aynı kodları bir de şöyle yazalım:

class Çalışan():
    def __init__(self):
        kabiliyetleri = []

ahmet = Çalışan()
print(ahmet.kabiliyetleri)

Burada __init__() fonksiyonunda ilk parametre olarak self’i belirttik. Ama kabiliyetleri niteliğinin başına self eklemedik. Dolayısıyla yazdığımız kodlar yine hata verdi. Çünkü, ahmet.kabiliyetleri şeklinde ifade ettiğimiz kodlardaki ahmet kelimesini karşılayacak herhangi bir öğe sınıf içinde bulunmuyor…

Bu arada, örnek isimlerini (mesela ahmet) yalnızca örnek niteliklerine erişmek için kullanmıyoruz. Bunları aynı zamanda sınıf niteliklerine erişmek için de kullanabiliyoruz. Dolayısıyla eğer yukarıdaki sınıf tanımı içinde, self.kabiliyetleri adlı örnek niteliği’nin yanısıra personel adlı bir sınıf niteliği de bulunsaydı:

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

    def __init__(self):
        self.kabiliyetleri = []

Şu kodları yazdığımızda:

ahmet = Çalışan()
print(ahmet.personel)

…o sınıf niteliğine erişebilirdik. Ancak eğer __init__() fonksiyonu altındaki kabiliyetleri niteliğine erişmek istiyorsak, bu niteliğin başına self kelimesini getirerek, bu niteliği bir örnek niteliği haline getirmeli ve böylece, ahmet.kabiliyetleri kodundaki ahmet kelimesini temsil edecek bir öğeyi sınıf içinde oluşturmalıyız.

Bu süreç tam olarak şöyle işler:

Biz ahmet.kabiliyetleri şeklinde bir komut verdiğimizde, Python ilk olarak ilgili sınıfın __init__() fonksiyonu içinde kabiliyetleri adlı bir örnek niteliği arar. Elbette Python’ın bu örnek niteliğini bulabilmesi için, __init__() fonksiyonu içinde, bu fonksiyonun ilk parametresi ile aynı öneki taşıyan bir niteliğin yer alması gerekir. Yani eğer __init__() fonksiyonunun ilk parametresi self ise, Python bu fonksiyon içinde self.kabiliyetleri adlı bir örnek niteliği bulmaya çalışır. Eğer bulamazsa, Python bu kez kabiliyetleri adlı bir sınıf niteliği arar. Eğer onu da bulamazsa tabii ki hata verir…

Gelin isterseniz bu mekanizmayı teyit edelim:

class Çalışan():
    kabiliyetleri = ['sınıf niteliği']

    def __init__(self):
        self.kabiliyetleri = ['örnek niteliği']

Gördüğünüz gibi, burada aynı adı taşıyan bir sınıf niteliği ile bir örnek niteliğimiz var. Python’da hem sınıf niteliklerine, hem de örnek niteliklerine örnek isimleri üzerinden erişebileceğimizi söylemiştik. Yani eğer örneğimizin ismi ahmet ise, hem kabiliyetleri adlı sınıf niteliğine hem de self.kabiliyetleri adlı örnek niteliğine aynı şekilde erişiyoruz:

ahmet = Çalışan()
print(ahmet.kabiliyetleri)

Peki ama acaba yukarıdaki kodlar bize örnek niteliğini mi verir, yoksa sınıf niteliğini mi?

Böyle bir durumda, yukarıda bahsettiğimiz mekanizma nedeniyle, self.kabiliyetleri şeklinde ifade ettiğimiz örnek niteliği, kabiliyetleri adlı sınıf niteliğini gölgeler. Bu yüzden de print(ahmet.kabiliyetleri) komutu, örnek niteliğini, yani self.kabiliyetleri listesini verir. Yukarıdaki kodları çalıştırarak siz de bu durumu teyit edebilirsiniz. Zira bu kodlar bize, self.kabiliyetleri listesinin değeri olan ‘örnek niteliği’ çıktısını verecektir…

Peki ya siz sınıf niteliği olan kabiliyetleri listesine erişmek isterseniz ne olacak?

İşte bunun için, sınıf örneğini değil de, sınıf adını kullanacaksınız:

class Çalışan():
    kabiliyetleri = ['sınıf niteliği']

    def __init__(self):
        self.kabiliyetleri = ['örnek niteliği']

#sınıf niteliğine erişmek için
#sınıf adını kullanıyoruz
print(Çalışan.kabiliyetleri)

#örnek niteliğine erişmek için
#örnek adını kullanıyoruz
ahmet = Çalışan()
print(ahmet.kabiliyetleri)

Ancak elbette, aynı adı taşıyan bir sınıf niteliği ile bir örnek niteliğini aynı sınıf içinde tanımlamak daha baştan iyi bir fikir değildir, ama yazdığınız bir sınıf yanlışlıkla aynı ada sahip sınıf ve örnek nitelikleri tanımlamanız nedeniyle beklenmedik bir çıktı veriyorsa, siz Python’ın bu özelliğinden haberdar olduğunuz için, hatanın nereden kaynaklandığını kolayca kestirebilirsiniz.

Sözün kısası, Python’ın söz dizimi kuralları açısından, eğer bir örnek niteliği tanımlıyorsak, bu niteliğin başına bir self getirmemiz gerekir. Ayrıca bu self kelimesini de, örnek niteliğinin bulunduğu fonksiyonun parametre listesinde ilk sıraya yerleştirmiş olmalıyız. Unutmayın, örnek nitelikleri sadece fonksiyonlar içinde tanımlanabilir. Fonksiyon dışında örnek niteliği tanımlayamazsınız. Yani şöyle bir şey yazamazsınız:

class Çalışan():
    self.n = 0

    def __init__(self):
        self.kabiliyetleri = []

Çünkü self kelimesi ancak ve ancak, içinde geçtiği fonksiyonun parametre listesinde ilk sırada kullanıldığında anlam kazanır.

Bu noktada size çok önemli bir bilgi verelim: Python sınıflarında örnek niteliklerini temsil etmesi için kullanacağınız kelimenin self olması şart değildir. Bunun yerine istediğiniz başka bir kelimeyi kullanabilirsiniz. Mesela:

class Çalışan():
    def __init__(falanca):
        falanca.kabiliyetleri = []

Dediğimiz gibi, self kelimesi, bir sınıfın örneklerini temsil ediyor. Siz sınıf örneklerini hangi kelimenin temsil edeceğini kendiniz de belirleyebilirsiniz. Mesela yukarıdaki örnekte, __init__() fonksiyonunun ilk parametresini falanca olarak belirleyerek, örnek niteliklerinin falanca kelimesi ile temsil edilmesini sağlamış olduk. Python’da bu konuya ilişkin kural şudur: Sınıf içindeki bir fonksiyonun ilk parametresi ne ise, o fonksiyon içindeki örnek niteliklerini temsil eden kelime de odur. Örneğin, eğer şöyle bir sınıf tanımlamışsak:

class XY():
    def __init__(a, b, c):
        a.örnek_niteliği = []

Burada __init__() fonksiyonunun ilk parametresi a olduğu için, örnek niteliğini temsil eden kelime de a olur. Dolayısıyla örnek_niteliği adlı örnek niteliğimizin başına da önek olarak bu a kelimesini getiriyoruz.

__init__() fonksiyonunun ilk parametresi a olarak belirlendikten sonra, bu fonksiyon içindeki bütün örnek nitelikleri, önek olarak a kelimesini alacaktır:

class XY():
    def __init__(a, b, c):
        a.örnek_niteliği1 = []
        a.örnek_niteliği2 = 23
        a.örnek_niteliği3 = 'istihza'

ANCAK! Her ne sebeple olursa olsun, örnek niteliklerini temsil etmek için self dışında bir kelime kullanmayın. Python bu kelimeyi bize dayatmasa da, self kullanımı Python topluluğu içinde çok güçlü ve sıkı sıkıya yerleşmiş bir gelenektir. Bu geleneği kimse bozmaz. Siz de bozmayın.

Sözün özü, tek başına self kelimesinin hiçbir anlamının olmadığını asla aklınızdan çıkarmayın. Bu kelimenin Python açısından bir anlam kazanabilmesi için, ilgili fonksiyonun parametre listesinde ilk sırada belirtiliyor olması lazım. Zaten bu yüzden, dediğimiz gibi, self kelimesinin Python açısından bir özelliği yoktur. Yani şöyle bir kod yazmamızın, Python söz dizimi açısından hiçbir sakıncası bulunmaz:

class Çalışan():
    def __init__(osman):
        osman.kabiliyetleri = []

Çünkü Python, örnek niteliklerini temsil eden kelimenin ne olduğuyla asla ilgilenmez. Python için önemli olan tek şey, temsil işi için herhangi bir kelimenin belirlenmiş olmasıdır. Tabii, biz, daha önce de ısrarla söylediğimiz gibi, örnek niteliklerini self dışında bir kelime ile temsil etmeye teşebbüs etmeyeceğiz ve kodlarımızı şu şekilde yazmaktan şaşmayacağız:

class Çalışan():
    def __init__(self):
        self.kabiliyetleri = []

İşte yukarıdaki kodda gördüğümüz self parametresi ve self öneki, birbirlerine bağımlı kavramlardır. Fonksiyonun ilk parametresi ne ise, örnek niteliklerinin öneki de o olacaktır.

Bu arada, örnek niteliklerini anlatmaya başlamadan önce sınıf niteliklerine ilişkin sinsi bir durumdan söz etmiştik hatırlarsanız. Buna göre, eğer elimizde şöyle bir kod varsa:

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

Biz bu sınıf içindeki kabiliyetleri listesine ekleme yaptığımızda, bu durum o sınıfın bütün örneklerini etkiliyordu.

Yukarıdaki kodları deneme.py adlı bir dosyaya kaydettiğimizi varsayarsak:

>>> import deneme
>>> ahmet = deneme.Çalışan()
>>> ahmet.kabiliyetleri.append('konuşkan')
>>> ahmet.kabiliyetleri

['konuşkan']

>>> mehmet = deneme.Çalışan()
>>> print(mehmet.kabiliyetleri)

['konuşkan']

İşte bu durumu önlemek için örnek metotlarından yararlanabiliyoruz:

class Çalışan():
    def __init__(self):
        self.kabiliyetleri = []

Yukarıdaki kodları yine deneme.py adlı bir dosyaya kaydettiğimizi varsayarsak:

>>> import deneme
>>> ahmet = deneme.Çalışan()
>>> ahmet.kabiliyetleri.append('konuşkan')
>>> ahmet.kabiliyetleri

['konuşkan']

>>> mehmet = deneme.Çalışan()
>>> print(mehmet.kabiliyetleri)

[]

Gördüğünüz gibi, ahmet örneğine eklediğimiz ‘konuşkan’ öğesi, olması gerektiği gibi, mehmet örneğinde bulunmuyor. Birazdan bu konu üzerine birkaç kelam daha edeceğiz.

Örnek Metotları

Buraya kadar sınıflar, örnekler, sınıf nitelikleri ve örnek nitelikleri konusunda epey bilgi edindik. Gelin şimdi isterseniz bu öğrendiklerimizi kullanarak az çok anlamlı bir şeyler yazmaya çalışalım. Böylece hem şimdiye kadar öğrendiklerimizi gözden geçirmiş ve pekiştirmiş oluruz, hem de bu bölümde ele alacağımız ‘örnek metotları’ (instance methods) kavramını anlamamız kolaylaşır:

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)

Sınıfımızı tanımladık. Gelin isterseniz bu kodları açıklamaya başlamadan önce nasıl kullanacağımızı görelim.

Bildiğiniz gibi, Python kodlarını test etmenin en iyi yolu, bunları etkileşimli kabuk üzerinde çalıştırmaktır. Özellikle bir program yazarken, tasarladığınız sınıfların, fonksiyonların ve öteki öğelerin düzgün çalışıp çalışmadığını test etmek için etkileşimli kabuğu sıklıkla kullanacaksınız.

O halde, yukarıdaki kodları barındıran dosyanın bulunduğu dizin altında bir etkileşimli kabuk oturumu başlatalım ve dosya adının çalışan.py olduğunu varsayarak kodlarımızı bir modül şeklinde içe aktaralım:

>>> import çalışan

Daha sonra sınıfımızın iki farklı örneğini çıkaralım:

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

Ahmet adlı kişi personele eklendi

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

Mehmet adlı kişi personele eklendi

Bu şekilde çalışan adlı modül içindeki Çalışan() adlı sınıfı sırasıyla ‘Ahmet’ ve ‘Mehmet’ parametreleri ile çağırarak ç1 ve ç2 adlı iki farklı sınıf örneği oluşturmuş olduk. Bu arada, sınıfımızı örneklediğimiz anda __init__() fonksiyonunun devreye girdiğine dikkat ediyoruz.

personele_ekle() adlı fonksiyonu self.personele_ekle() şeklinde __init__() fonksiyonu içinden çağırdığımız için, sınıfımızı örneklediğimiz anda hem personelin kendisi personel listesine eklendi, hem de bu kişinin personele eklendiğine dair bir mesaj gösterildi.

Tanımladığımız sınıfın niteliklerine, çıkardığımız örnekler üzerinden erişebiliriz:

>>> ç1.isim

'Ahmet'

>>> ç2.isim

'Mehmet'

Yine bu örnekler üzerinden, bu nitelikleri değiştirebiliriz de:

>>> ç1.isim = 'Mahmut'
>>> ç1.personel[0] = 'Mahmut'

Böylece ilk çalışanın ismini ‘Mahmut’ olarak değiştirdik:

>>> ç1.isim

'Mahmut'

>>> ç1.personel

['Mahmut', 'Mehmet']

Tanımladığımız sınıf içindeki fonksiyonları kullanarak, çalışanlarımıza birkaç kabiliyet ekleyelim:

>>> ç1.kabiliyet_ekle('prezantabl')
>>> ç1.kabiliyet_ekle('konuşkan')

ç1 örneğinin kabiliyetlerini görüntüleyelim:

>>> ç1.kabiliyetleri_görüntüle()

Mahmut adlı kişinin kabiliyetleri:
prezantabl
konuşkan

Şimdi de ç2 örneğine bir kabiliyet ekleyelim ve eklediğimiz kabiliyeti görüntüleyelim:

>>> ç2.kabiliyet_ekle('girişken')
>>> ç2.kabiliyetleri_görüntüle()

Mehmet adlı kişinin kabiliyetleri:
girişken

Gördüğünüz gibi, bir sınıf örneğine eklediğimiz kabiliyet öteki sınıf örneklerine karışmıyor. Bu, örnek niteliklerinin sınıf niteliklerinden önemli bir farkıdır. Zira sınıf nitelikleri bir sınıfın bütün örnekleri tarafından paylaşılır. Ama örnek nitelikleri her bir örneğe özgüdür. Bu özellikten biraz sonra daha ayrıntılı olarak söz edeceğiz. Biz şimdilik okumaya devam edelim.

Sınıf örneklerimizin herhangi biri üzerinden personel listesine de ulaşabileceğimizi biliyoruz:

>>> ç1.personeli_görüntüle()

Personel listesi:
Mahmut
Mehmet

Gayet güzel…

Yukarıda anlattıklarımız sınıflar hakkında size epey fikir vermiş olmalı. Konuyu daha da derinlemesine anlayabilmek için, artık bu sınıfı incelemeye geçebiliriz.

Sınıfımızı önümüze alalım:

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 öncelikle her zamanki gibi sınıfımızı tanımlıyoruz:

class Çalışan():
    ...

Daha sonra bu sınıfa personel adlı bir sınıf niteliği ekliyoruz:

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

Sınıf niteliklerinin özelliği, o sınıfın bütün örnekleri tarafından paylaşılıyor olmasıdır. Yani herhangi bir örneğin bu nitelik üzerinde yaptığı değişiklik, öteki örneklere de yansıyacaktır. Hele bir de bu sınıf niteliği, listeler gibi değiştirilebilir (mutable) bir veri tipi ise, bu durum hiç de istemediğiniz sonuçlar doğurabilir. Bununla ilgili bir örneği yukarıda vermiştik. Hatırlarsanız, kabiliyetleri adlı, liste veri tipinde bir sınıf niteliği oluşturduğumuzda, bu listeye eklediğimiz öğeler, hiç istemediğimiz halde öbür örneklere de sirayet ediyordu. Elbette, sınıf niteliklerinin bu özelliği, o anda yapmaya çalıştığınız şey açısından gerekli bir durum da olabilir. Mesela yukarıdaki kodlarda, listelerin ve sınıf niteliklerinin bu özelliği bizim amacımıza hizmet ediyor. Yukarıdaki sınıfı çalıştırdığımızda, eklenen her bir kişiyi bu personel listesine ilave edeceğiz. Dolayısıyla bu nitelik üzerinde yapılan değişikliklerin bütün örneklere yansıması bizim istediğimiz bir şey.

Neyse… Lafı daha fazla uzatmadan, kodlarımızı açıklamaya kaldığımız yerden devam edelim…

Sınıfımızı ve sınıf niteliğimizi tanımladıktan sonra __init__() adlı özel fonksiyonumuzu oluşturuyoruz:

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

Bu fonksiyonun özelliği, sınıfın örneklenmesi ile birlikte otomatik olarak çalıştırılacak olmasıdır. Biz burada, self.isim ve self.kabiliyetleri adlı iki adet örnek niteliği tanımladık. Bu örnek niteliklerine sınıfımızın her tarafından erişebileceğiz.

Yukarıda, tanımladığımız sınıfı nasıl kullanacağımızı gösterirken, Çalışan() sınıfını şu şekilde örneklediğimizi hatırlıyorsunuz:

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

Burada sınıfımızı ‘Ahmet’ adlı bir argümanla örneklediğimize dikkatinizi çekmek isterim. İşte bu argüman, biraz önce __init__() fonksiyonunu tanımlarken belirttiğimiz isim parametresine karşılık geliyor. Dolayısıyla, bir sınıfı çağırırken/örneklerken kullanacağımız argümanları, bu __init__() fonksiyonunun parametreleri olarak tanımlıyoruz.

Daha sonra bu isim parametresini, __init__() fonksiyonunun gövdesi içinde bir örnek niteliği haline getiriyoruz:

self.isim = isim

Bunu yapmamızın gerekçesi, isim parametresini sınıfımızın başka bölgelerinde de kullanabilmek. self kelimesini parametremizin başına yerleştirerek, bu parametreyi sınıfın başka yerlerinden de erişilebilir hale getiriyoruz.

isim parametresini, self.isim kodu yardımıyla bir örnek niteliğine dönüştürdükten sonra self.kabiliyetleri adlı bir başka örnek niteliği daha tanımlıyoruz. Bu liste, sınıf örneklerine eklediğimiz kabiliyetleri tutacak.

Bunun ardından şöyle bir kod görüyoruz:

self.personele_ekle()

Burada, personele_ekle() adlı bir örnek metoduna (instance method) atıfta bulunuyoruz. Örnek metotları, bir sınıfın örnekleri vasıtasıyla çağrılabilen fonksiyonlardır. Bu fonksiyonların ilk parametresi her zaman self kelimesidir. Ayrıca bu fonksiyonlara sınıf içinde atıfta bulunurken de yine self kelimesini kullanıyoruz. Tıpkı yukarıdaki örnekte olduğu gibi…

Bir örnek metodu olduğunu söylediğimiz personele_ekle() fonksiyonunu şu şekilde tanımladık:

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

Burada, bir sınıf niteliği olan personel değişkenine nasıl eriştiğimize çok dikkat etmenizi istiyorum. Daha önce de söylediğimiz gibi, sınıf niteliklerine sınıf dışındayken örnekler üzerinden erişebiliyoruz. self kelimesi, bir sınıfın örneklerini temsil ettiği için, bir sınıf niteliğine sınıf içinden erişmemiz gerektiğinde self kelimesini kullanabiliriz.

Sınıf niteliklerine, örnekler dışında, sınıf adıyla da erişebileceğinizi biliyorsunuz. Dolayısıyla isterseniz yukarıdaki kodları şöyle de yazabilirdiniz:

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

Bir öncekinden farklı olarak, bu defa sınıf niteliğine doğrudan sınıf adını (Çalışan) kullanarak eriştik.

Ayrıca bu fonksiyonda, bir örnek niteliği olan self.isim değişkenine de erişebiliyor olduğumuza dikkat edin. Unutmayın, self sınıfların çok önemli bir öğesidir. Bu öğeyi kullanarak hem örnek niteliklerine, hem sınıf niteliklerine, hem de örnek metotlarına ulaşabiliyoruz. Tanımladığımız bu personele_ekle() adlı örnek metodunu __init__() fonksiyonu içinden self.personele_ekle() kodu ile (yani yine self kelimesini kullanarak) çağırdığımızı hatırlıyorsunuz.

personele_ekle() fonksiyonunun ardından arka arkaya üç fonksiyon daha tanımladık:

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 fonksiyonlar da, tıpkı personele_ekle() gibi, birer örnek metodudur. Bu örnek metotlarının da ilk parametrelerinin hep self olduğuna dikkat ediyoruz. Örnek metotlarına sınıf dışından örnek isimleri (ahmet, mehmet gibi) aracılığıyla, sınıf içinden ise, örnek isimlerini temsil eden self kelimesi aracılığıyla eriştiğimizi biliyorsunuz.

Şimdi bir duralım…

Bu noktaya kadar epey konuştuk, epey örnek verdik. Sınıflar hakkında yeterince bilgi sahibi olduğumuza göre, nihayet en başta verdiğimiz harf sayacı kodlarını rahatlıkla anlayabilecek düzeye eriştik:

class HarfSayacı:
    def __init__(self):
        self.sesli_harfler = 'aeıioöuü'
        self.sessiz_harfler = 'bcçdfgğhjklmnprsştvyz'
        self.sayaç_sesli = 0
        self.sayaç_sessiz = 0

    def kelime_sor(self):
        return input('Bir kelime girin: ')

    def seslidir(self, harf):
        return harf in self.sesli_harfler

    def sessizdir(self, harf):
        return harf in self.sessiz_harfler

    def artır(self):
        for harf in self.kelime:
            if self.seslidir(harf):
                self.sayaç_sesli += 1
            if self.sessizdir(harf):
                self.sayaç_sessiz += 1
        return (self.sayaç_sesli, self.sayaç_sessiz)

    def ekrana_bas(self):
        sesli, sessiz = self.artır()
        mesaj = "{} kelimesinde {} sesli {} sessiz harf var."
        print(mesaj.format(self.kelime, sesli, sessiz))

    def çalıştır(self):
        self.kelime = self.kelime_sor()
        self.ekrana_bas()

if __name__ == '__main__':
    sayaç = HarfSayacı()
    sayaç.çalıştır()

Gelin isterseniz bu kodlara da şöyle bir bakalım…

Burada sınıfımızı şu şekilde tanımladık:

class HarfSayacı:
    ...

Sınıf adını parantezli bir şekilde yazabileceğimizi de biliyorsunuz:

class HarfSayacı():
    ...

Daha sonra, __init__() fonksiyonu içinde dört adet örnek niteliği tanımladık:

self.sesli_harfler = 'aeıioöuü'
self.sessiz_harfler = 'bcçdfgğhjklmnprsştvyz'
self.sayaç_sesli = 0
self.sayaç_sessiz = 0

Bunların birer örnek niteliği olduğunu, başlarına getirdiğimiz self kelimesinden anlıyoruz. Çünkü bildiğiniz gibi, self kelimesi, ilgili sınıfın örneklerini temsil ediyor. Bir sınıf içinde örnek niteliklerine ve örnek metotlarına hep bu self kelimesi aracılığıyla erişiyoruz.

Bu sınıf içinde, ilk parametreleri self olan şu örnek metotlarını görüyoruz:

def kelime_sor(self):
    ...

def seslidir(self, harf):
    ...

def sessizdir(self, harf):
    ...

def artır(self):
    ...

def ekrana_bas(self):
    ...

def çalıştır(self):
    ...

Sınıfla birlikte bütün örnek değişkenlerini ve örnek metotlarını tanımladıktan sonra programımızı çalıştırma aşamasına geliyoruz:

if __name__ == '__main__':
    sayaç = HarfSayacı()
    sayaç.çalıştır()

Buna göre, eğer programımız bağımsız olarak çalıştırılıyorsa öncelikle HarfSayacı() adlı sınıfı örneklendiriyoruz:

sayaç = HarfSayacı()

Daha sonra da sayaç örneği üzerinden HarfSayacı() adlı sınıfın çalıştır() metoduna erişerek programımızı başlatıyoruz.

Böylece, Python’da nesne tabanlı programlama ve sınıflara dair öğrenmemiz gereken bütün temel bilgileri edinmiş olduk. Şu ana kadar öğrendikleriniz sayesinde, etrafta göreceğiniz sınıflı kodların büyük bölümünü anlayabilecek durumdasınız. Bir sonraki bölümde, nesne tabanlı programlamanın ayrıntılarına inmeye başlayacağız.

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>