Nesne Tabanlı Programlama
Pardus-Linux.org | Wiki sitesinden
[değiştir] Nesne Tabanlı Programlama – OOP (NTP)
Bu yazımızda çok önemli bir konuyu işlemeye başlayacağız: Python’da “Nesne Tabanlı Programlama” (Object Oriented Programming). Yabancılar bu ifadeyi “OOP” olarak kısaltıyor. Gelin isterseniz biz de bunu Türkçe’de NTP olarak kısaltalım…
Şimdilik bu “Nesne Tabanlı Programlama”nın ne olduğu ve tanımı bizi ilgilendirmiyor. Biz şimdilik işin teorisiyle pek uğraşmayıp pratiğine bakacağız. NTP’nin pratikte nasıl işlediğini anlarsak, teorisini araştırıp öğrenmek de daha kolay olacaktır.
[değiştir] Neden Nesne Tabanlı Programlama?
İsterseniz önce kendimizi biraz yüreklendirip cesaretlendirelim. Şu soruyu soralım kendimize: Nesne Tabanlı Programlama'ya hiç girmesem olmaz mı?
Bu soruyu cevaplandırmadan önce bakış açımızı şöyle belirleyelim. Daha doğrusu bu soruyu iki farklı açıdan inceleyelim: NTP'yi öğrenmek ve NTP'yi kullanmak...
Eğer yukarıdaki soruya, "NTP'yi kullanmak" penceresinden bakarsak, cevabımız, "Evet," olacaktır. Yani, "Evet, NTP'yi kullanmak zorunda değilsiniz". Bu bakımdan NTP'yle ilgilenmek istemeyebilirsiniz, çünkü Python başka bazı dillerin aksine NTP'yi dayatmaz. İyi bir Python programcısı olmak için NTP'yi kullanmasanız da olur. NTP'yi kullanmadan da gayet başarılı programlar yazabilirsiniz. Bu bakımdan önünüzde bir engel yok.
Ama eğer yukarıdaki soruya "NTP'yi öğrenmek" penceresinden bakarsak, cevabımız, "Hayır", olacaktır. Yani, "Hayır, NTP'yi öğrenmek zorundasınız!". Bu bakımdan NTP'yle ilgilenmeniz gerekir, çünkü siz NTP'yi kullanmasanız da başkaları bunu kullanıyor. Dolayısıyla, NTP'nin bütün erdemlerini bir kenara bıraksak dahi, sırf başkalarının yazdığı kodları anlayabilmek için bile olsa, elinizi NTP'yle kirletmeniz gerekecektir... Bir de şöyle düşünün: Gerek internet üzerinde olsun, gerekse basılı yayınlarda olsun, Python'a ilişkin pek çok kaynakta kodlar bir noktadan sonra NTP yapısı içinde işlenmektedir. Bu yüzden özellikle başlangıç seviyesini geçtikten sonra karşınıza çıkacak olan kodları anlayabilmek için bile NTP'ye bir aşinalığınızın olması gerekir.
Dolayısıyla en başta sorduğumuz soruya karşılık ben de size şu soruyu sormak isterim:
"Daha nereye kadar kaçacaksınız bu NTP'den?"
Dikkat ederseniz, bildik anlamda NTP'nin faydalarından, bize getirdiği kolaylıklardan hiç bahsetmiyoruz. Zira şu anda içinde bulunduğumuz noktada bunları bilmenin bize pek faydası dokunmayacaktır. Çünkü daha NTP'nin ne olduğunu dahi bilmiyoruz ki cicili bicili cümlelerle bize anlatılacak "faydaları" özümseyebilelim... NTP'yi öğrenmeye çalışan birine birkaç sayfa boyunca "NTP şöyle iyidir, NTP böyle hoştur," demenin pek faydası olmayacaktır. Çünkü böyle bir çaba, konuyu anlatan kişiyi ister istemez okurun henüz bilmediği kavramları kullanarak bazı şeyleri açıklamaya çalışmaya itecektir. Bu da okurun zihninde birtakım fantastik cümlelerin uçuşmasından başka bir işe yaramayacaktır. Dolayısıyla, NTP'nin faydalarını size burada bir çırpıda saymak yerine, öğrenme sürecine bırakıyoruz bu "özümseme" işini... NTP'yi öğrendikten sonra, bu programlama tekniğinin Python deneyiminize ne tür bir katkı sağlayacağını, size ne gibi bir fayda getireceğini kendi gözlerinizle göreceksiniz.
En azından biz bu noktada şunu rahatlıkla söyleyebiliriz: NTP'yi öğrendiğinizde Python Programlama'da bir anlamda "boyut atlamış" olacaksınız. Sonunda özgüveniniz artacak, orada burada Python'a ilişkin okuduğunuz şeyler zihninizde daha anlamlı izler bırakmaya başlayacaktır.
[değiştir] Sınıflar
NTP’de en önemli kavram “sınıflar”dır. Zaten NTP denince ilk akla gelen şey de genellikle “sınıflar” olmaktadır. Sınıflar yapı olarak “fonksiyonlara” benzetilebilir. Hatırlarsanız, fonksiyonlar yardımıyla farklı değişkenleri ve veri tiplerini, tekrar kullanılmak üzere bir yerde toplayabiliyorduk. İşte sınıflar yardımıyla da farklı fonksiyonları, veri tiplerini, değişkenleri, metotları gruplandırabiliyoruz.
[değiştir] Sınıf Tanımlamak
Öncelikle bir sınıfı nasıl tanımlayacağımıza bakmamız gerekiyor. Hemen, bir sınıfı nasıl tanımlayacağımızı bir örnekle görmeye çalışalım:
Python’da bir sınıf oluşturmak için şu yapıyı kullanıyoruz:
class IlkSinif:
Böylece sınıfları oluşturmak için ilk adımı atmış olduk. Burada dikkat etmemiz gereken bazı noktalar var:
- Hatırlarsanız fonksiyonları tanımlarken “def” parçacığından yararlanıyorduk. Mesela:
def deneme():
- Sınıfları tanımlarken ise “class” parçacığından faydalanıyoruz:
class IlkSinif:
- Tıpkı fonksiyonlarda olduğu gibi, isim olarak herhangi bir kelimeyi seçebiliriz. Mesela yukarıdaki fonksiyonda “deneme” adını seçmiştik. Yine yukarıda gördüğümüz sınıf örneğinde de “IlkSinif” adını kullandık. Tabii isim belirlerken Türkçe karakter kullanamıyoruz…
- Sınıf adlarını belirlerken kullanacağımız kelimenin büyük harf veya küçük harf olması önemli değildir. Ama seçilen kelimelerin ilk harflerini büyük yazmak adettendir. Mesela "class Sinif" veya "class HerhangiBirKelime". Gördüğünüz gibi sınıf adı birden fazla kelimeden oluşuyorsa her kelimenin ilk harfi büyük yazılıyor. Bu bir kural değildir, ama her zaman adetlere uymak yerinde bir davranış olacaktır...
- Son olarak, sınıfımızı tanımladıktan sonra parantez işareti kullanmak zorunda olmadığımıza dikkat edin. En azından şimdilik... Bu parantez meselesine tekrar döneceğiz.
İlk adımı attığımıza göre ilerleyebiliriz:
class IlkSinif:
mesele = "Olmak ya da olmamak"
Böylece eksiksiz bir sınıf tanımlamış olduk. Aslında tabii ki normalde sınıflar bundan biraz daha karmaşıktır. Ancak yukarıdaki örnek, gerçek hayatta bu haliyle karşımıza çıkmayacak da olsa, hem yapı olarak kurallara uygun bir sınıftır, hem de bize sınıflara ilişkin pek çok önemli ipucu vermektedir. Sırasıyla bakalım:
- İlk satırda doğru bir şekilde sınıfımızı tanımladık.
- İkinci satırda ise “mesele” adlı bir değişken oluşturduk.
Böylece ilk sınıfımızı başarıyla tanımlamış olduk.
[değiştir] Sınıfları Çalıştırmak
Şimdi güzel güzel yazdığımız bu sınıfı nasıl çalıştıracağımıza bakalım:
Herhangi bir Python programını nasıl çalıştırıyorsak sınıfları da öyle çalıştırabiliriz. Yani pek çok farklı yöntem kullanabiliriz. Örneğin yazdığımız şey arayüzü olan bir Tkinter programıysa "python programadı.py" komutuyla bunu çalıştırabilir, yazdığımız arayüzü görebiliriz. Hatta gerekli ayarlamaları yaptıktan sonra programın simgesine çift tıklayarak veya GNU/Linux sistemlerinde konsol ekranında programın sadece adını yazarak çalıştırabiliriz programımızı. Eğer komut satırından çalışan bir uygulama yazdıysak, yine "python programadı.py" komutuyla programımızı çalıştırıp konsol üzerinden yönetebiliriz. Ancak bizim şimdilik yazdığımız kodun bir arayüzü yok. Üstelik bu sadece NTP'yi öğrenmek için yazdığımız, tam olmayan bir kod parçasından ibaret. Dolayısıyla sınıfımızı tecrübe etmek için biz şimdilik doğrudan Python komut satırı içinden çalışacağız.
Şu halde herkes kendi platformuna uygun şekilde Python komut satırını başlatsın! Python'u başlattıktan sonra bütün platformlarda şu komutu vererek bu kod parçasını çalıştırılabilir duruma getirebiliriz:
>>>from sinif import *
Burada sizin bu kodları “sinif.py” adlı bir dosyaya kaydettiğinizi varsaydım. Dolayısıyla bu şekilde dosyamızı bir modül olarak içe aktarabiliyoruz (import). Bu arada Python'un bu modülü düzgün olarak içe aktarabilmesi için komut satırını, bu modülün bulunduğu dizin içinde açmak gerekir. Python içe aktarılacak modülleri ararken ilk olarak o anda içinde bulunulan dizine bakacağı için modülümüzü rahatlıkla bulabilecektir.
GNU/Linux kullanıcıları komut satırıyla daha içli dışlı oldukları için etkileşimli kabuğu modülün bulunduğu dizinde nasıl açacaklarını zaten biliyorlardır... Ama biz yine de hızlıca üzerinden geçelim...(Modülün masaüstünde olduğunu varsayıyoruz):
ALT+F2 tuşlarına basıp açılan pencereye "konsole" (KDE) veya "gnome-terminal" (GNOME) yazıyoruz. Ardından konsol ekranında "cd Desktop" komutunu vererek masaüstüne erişiyoruz.
Windows kullanıcılarının komut satırına daha az aşina olduğunu varsayarak biraz daha detaylı anlatalım bu işlemi...
Windows kullanıcıları ise Python komut satırını modülün olduğu dizin içinde açmak için şu yolu izleyebilir (yine modülün masaüstünde olduğunu varsayarsak...):
Başlat > Çalıştır yolunu takip edip açılan kutuya "cmd" yazıyoruz (parantezler olmadan). Komut ekranı karşımıza gelecek. Muhtemelen içinde bulunduğunuz dizin "C:\Documents and Settings\İsminiz" olacaktır. Orada şu komutu vererek masaüstüne geçiyoruz:
cd Desktop
Şimdi de şu komutu vererek Python komut satırını başlatıyoruz:
C:/python25/python
Tabii kullandığınız Python sürümünün 2.5 olduğunu varsaydım. Sizde sürüm farklıysa komutu ona göre değiştirmelisiniz.
Eğer herhangi bir hata yapmadıysanız karşınıza şuna benzer bir ekran gelmeli:
C:\Documents and Settings\Isminiz>c:/python25/Python Python 2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>>
Şimdi bu ekrandaki ">>>" satırından hemen sonra şu komutu verebiliriz:
from sinif import *
Artık sınıfımızı çalıştırmamızın önünde hiç bir engel kalmadı sayılır. Bu noktada yapmamız gereken tek bir işlem var: Örnekleme
[değiştir] Örnekleme (Instantiation)
Şimdi şöyle bir şey yazıyoruz:
>>>deneme = IlkSinif()
Böylece oluşturduğumuz sınıfı bir değişkene atadık. NTP kavramlarıyla konuşacak olursak, “sınıfımızı örneklemiş olduk”.
Peki bu "örnekleme" denen şey de ne oluyor? Hemen bakalım:
İngilizce’de “instantiation” olarak ifade edilen “örnekleme” kavramı sayesinde sınıfımızı kullanırken belli bir kolaylık sağlamış oluyoruz. Gördüğünüz gibi, “örnekleme” (instantiation) aslında şekil olarak yalnızca bir değişken atama işleminden ibarettir. Nasıl daha önce gördüğümüz değişkenler uzun ifadeleri kısaca adlandırmamızı sağlıyorsa, burada da “örnekleme” işlemi hemen hemen aynı vazifeyi görüyor. Yani böylece ilk satırda tanımladığımız sınıfa daha kullanışlı bir isim vermiş oluyoruz. Dediğimiz gibi, bu işleme “örnekleme” (instantiation) adı veriliyor. Bu örneklemelerin her birine ise “örnek” (instance) deniyor. Yani, IlkSinif adlı sınıfa bir isim verme işlemine “örnekleme” denirken, bu işlem sonucu ortaya çıkan değişkene de, “örnek” (instance) diyoruz. Buna göre, burada “deneme” adlı değişken, “IlkSinif” adlı sınıfın bir örneğidir (“deneme” is an instance of the class “IlkSinif”). Daha soyut bir ifadeyle, örnekleme işlemi “Class” (sınıf) nesnesini etkinleştirmeye yarar. Yani sınıfın bütününü alır ve onu paketleyip, istediğimiz şekilde kullanabileceğimiz bir nesne haline getirir. Şöyle de diyebiliriz:
Biz bir sınıf tanımlıyoruz. Bu sınıfın içinde birtakım değişkenler, fonksiyonlar, vb. olacaktır. Hayli kaba bir benzetme olacak ama, biz bunları bir internet sayfasının içeriğine benzetebiliriz. İşte biz bu sınıfı "örneklediğimiz" zaman, sınıf içeriğini bir bakıma erişilebilir hale getirmiş oluyoruz. Tıpkı bir internet sayfasının, "www...." şeklinde gösterilen adresi gibi... Mesela www.python.quotaless.com adresi içindeki bütün bilgileri bir sınıf olarak düşünürsek, "www.python.quotaless.com" ifadesi bu sınıfın bir örneğidir... Durum tam olarak böyle olmasa bile, bu benzetme, "örnekleme" işlemine ilişkin en azından zihnimizde bir kıvılcım çakmasını sağlayabilir.
Daha yerinde bir benzetme şöyle olabilir: "İnsan"ı büyük bir sınıf olarak kabul edelim. İşte "siz" (yani Ahmet, Mehmet, vb...) bu büyük sınıfın bir örneği, yani ete kemiğe bürünmüş hali oluyorsunuz... Buna göre "insan" sınıfı insanın ne tür özellikleri olduğuna dair tanımlar (fonksiyonlar, veriler) içeriyor. "Mehmet" örneği (instance) ise bu tanımları, nitelikleri, özellikleri taşıyan bir "nesne" oluyor...
[değiştir] Çöp Toplama (Garbage Collection)
Peki biz bir sınıfı örneklemezsek ne olur? Eğer bir sınıfı örneklemezsek, o örneklenmeyen sınıf program tarafından otomatik olarak "çöp toplama" (garbage collection) adı verilen bir sürece tabi tutulacaktır. Burada bu sürecin ayrıntılarına girmeyeceğiz. Ama kısaca şöyle anlatabiliriz:
Python'da (ve bir çok programlama dilinde) yazdığımız programlar içindeki "işe yaramayan" veriler bellekten silinir. Böylece etkili bir hafıza yönetimi uygulanmış ve programların performansı artırılmış olur. Mesela:
a = 5 a = a + 6 print a 11
Burada "a" değişkeninin gösterdiği "5" verisi, daha sonra gelen "a = a + 6" ifadesi nedeniyle boşa düşmüş, ıskartaya çıkmış oluyor. Yani "a = a + 6" ifadesi nedeniyle, "a" değişkeni artık "5" verisini göstermiyor. Dolayısıyla "5" verisi o anda bellekte boşu boşuna yer kaplamış oluyor. Çünkü "a = a + 6" ifadesi yüzünden, "5" verisine gönderme yapan, onu gösteren, bu veriye bizim ulaşmamızı sağlayacak hiç bir işaret kalmamış oluyor ortada. İşte Python, bir veriye işaret eden hiç bir referans kalmadığı durumlarda, yani o veri artık işe yaramaz hale geldiğinde, otomatik olarak "çöp toplama" işlemini devreye sokar ve bu örnekte "5" verisini çöpe gönderir. Yani artık o veriyi bellekte tutmaktan vazgeçer. İşte eğer biz de yukarıda olduğu gibi sınıflarımızı "örneklemezsek", bu sınıflara hiçbir yerde işaret edilmediği, yani bu sınıfı gösteren hiçbir "referans" olmadığı için, sınıfımız oluşturulduğu anda çöp toplama işlemine tabi tutulacaktır. Dolayısıyla artık bellekte tutulmayacaktır.
"Çöp Toplama" işlemini de kısaca anlattığımıza göre artık kaldığımız yerden yolumuza devam edebiliriz...
Bu arada dikkat ettiyseniz sınıfımızı örneklerken parantez kullandık. Yani şöyle yaptık:
deneme = IlkSinif()
Eğer parantezleri kullanmazsak, yani "deneme = IlkSinif" gibi bir şey yazarsak, yaptığımız şey "örnekleme" olmaz. Böyle yaparak sınıfı sadece kopyalamış oluruz... Bizim yapmak istediğimiz bu değil. O yüzden, "parantezlere dikkat!" diyoruz...
Artık şu komut yardımıyla, sınıf örneğimizin “niteliklerine” ulaşabiliriz:
>>>deneme.mesele Olmak ya da olmamak
[değiştir] Niteliklere Değinme (Attribute References)
Biraz önce “nitelik” diye bir şeyden söz ettik. İngilizce’de “attribute” denen bu “nitelik” kavramı, Python’daki nesnelerin özelliklerine işaret eder. Python'un yazarı Guido Van Rossum bu kavram için şöyle diyor:
"I use the word attribute for any name following a dot" (Noktadan sonra gelen bütün isimler için ben "nitelik" kelimesini kullanıyorum)
kaynak: http://docs.python.org/tut/node11.html
Bu tanıma göre, örneğin,
>>>deneme.mesele
dediğimiz zaman, buradaki “mesele”; “deneme” adlı sınıf örneğinin (instance) bir niteliği (attribute) oluyor. Biraz karışık gibi mi? Hemen bir örnek yapalım o halde:
class Toplama:
a = 15
b = 20
c = a + b
1. İlk satırda “Toplama” adlı bir sınıf tanımladık. Bunu yapmak için “class” parçacığından yararlandık.
2. Sırasıyla; a, b ve c adlı üç adet değişken oluşturduk. c değişkeni a ve b değişkenlerinin toplamıdır.
Bu sınıftaki a, b ve c değişkenleri ise, “Toplama” sınıf örneğinin (örneği biraz sonra tanımlayacağız) birer niteliği oluyor. Bundan önceki örneğimizde ise "mesele" adlı değişken, "deneme" adlı sınıf örneğinin bir niteliği idi...
Bu sınıfı yazıp kaydettiğimiz dosyamızın adının “matematik.py” olduğunu varsayarsak;
>>>from matematik import *
komutunu verdikten sonra şunu yazıyoruz::
>>>sonuc = Toplama()
Böylece “Toplama” adlı sınıfımızı “örnekliyoruz”. Bu işleme “örnekleme” (instantiation) adı veriyoruz. “sonuc” kelimesine ise Python'cada “örnek” (instance) adı veriliyor. Yani “sonuc”, “Toplama” sınıfının bir örneğidir, diyoruz…
Artık,
sonuc.a
sonuc.b
sonuc.c
biçiminde, "sonuc" örneğinin niteliklerine tek tek erişebiliriz.
Peki kodları şöyle çalıştırırsak ne olur?
>>>import matematik
Eğer modülü bu şekilde içe aktarırsak (import), sınıf örneğinin niteliklerine ulaşmak için şu yapıyı kullanmamız gerekir:
matematik.sonuc.a
matematik.sonuc.b
matematik.sonuc.c
Yani her defasında dosya adını (ya da başka bir ifadeyle “modülün adını”) da belirtmemiz gerekir.
Bu iki kullanım arasında, özellikle sağladıkları güvenlik avantajları/dezavantajları açısından başka bazı temel farklılıklar da vardır, ama şimdilik konumuzu dağıtmamak için bunlara girmiyoruz… Ama temel olarak şunu bilmekte fayda var: Genellikle tercih edilmesi gereken yöntem "from modül import *" yerine "import modül" biçimini kullanmaktır. Eğer "from modül import *" yöntemini kullanarak içe aktardığınız modül içindeki isimler (değişkenler, nitelikler), bu modülü kullanacağınız dosya içinde de bulunuyorsa isim çakışmaları ortaya çıkabilir... Esasında, "from modül import *" yapısını sadece ne yaptığımızı çok iyi biliyorsak ve modülle ilgili belgelerde modülün bu şekilde içe aktarılması gerektiği bildiriliyorsa kullanmamız yerinde olacaktır. Mesela Tkinter ile programlama yaparken rahatlıkla "from Tkinter import *" yapısını kullanabiliriz, çünkü Tkinter bu kullanımda problem yaratmayacak şekilde tasarlanmıştır. Yukarıda bizim verdiğimiz örnekte de "from modül import *" yapısını rahatlıkla kullanıyoruz, çünkü şimdilik tek bir modül üzerinde çalışıyoruz. Dolayısıyla isim çakışması yaratacak başka bir modülümüz olmadığı için "ne yaptığımızı biliyoruz!"...
Yukarıda anlattığımız kod çalıştırma biçimleri tabii ki, bu kodları komut ekranından çalıştırdığınızı varsaymaktadır. Eğer siz bu kodları IDLE ile çalıştırmak isterseniz, bunları hazırladıktan sonra F5 tuşuna basmanız, veya “Run > Run Module” yolunu takip etmeniz yeterli olacaktır. F5’e bastığınızda veya “Run > Run Module” yolunu takip ettiğinizde IDLE sanki komut ekranında “from matematik import *” komutunu vermişsiniz gibi davranacaktır.
Veya GNU/Linux sistemlerinde sistem konsolunda
python -i sinif.py
komutunu vererek de bu kod parçalarını çalıştırılabilir duruma getirebiliriz. Bu komutu verdiğimizde "from sinif import *" komutu otomatik olarak verilip hemen ardından Python komut satırı açılacaktır. Bu komut verildiğinde ekranda göreceğiniz ">>>" işaretinden, Python'un sizden hareket beklediğini anlayabilirsiniz...
Şimdi isterseniz buraya kadar söylediklerimizi şöyle bir toparlayalım. Bunu da yukarıdaki örnek üzerinden yapalım:
class Toplama:
a = 15
b = 20
c = a + b
1. “Toplama” adlı bir sınıf tanımlıyoruz.
2. Sınıfımızın içine istediğimiz kod parçalarını ekliyoruz. Biz burada üç adet değişken ekledik. Bu değişkenlerin her birine, “nitelik” adını veriyoruz.
3. Bu kodları kullanabilmek için Python komut satırında şu komutu veriyoruz:
>>>from matematik import *
Burada modül adının (yani dosya adının) matematik olduğunu varsaydık.
4. Şimdi yapmamız gereken şey, Toplama adlı sınıfı “örneklemek” (instantiation). Yani bir nevi, sınıfın kendisini bir değişkene atamak. Bu değişkene biz Python’cada “örnek” (instance) adını veriyoruz. Yani, “sonuc" adlı değişken, “Toplama” adlı sınıfın bir örneğidir diyoruz (sonuc is an instance of Toplama)”.
>>>sonuc = Toplama()
5. Bu komutu verdikten sonra niteliklerimize erişebiliriz:
>>>sonuc.a
>>>sonuc.b
>>>sonuc.c
Dikkat ederseniz, niteliklerimize erişirken “örnek”ten (instance), yani “sonuc” adlı değişkenden yararlanıyoruz.
Şimdi bir an bu sınıfımızı örneklemediğimizi düşünelim. Dolayısıyla bu sınıfı şöyle kullanmamız gerekecek:
>>>Toplama().a >>>Toplama().b >>>Toplama().c
Ama daha önce de anlattığımız gibi, siz "Toplama().a" der demez sınıf çalıştırılacak ve çalıştırıldıktan hemen sonra ortada bu sınıfa işaret eden herhangi bir referans kalmadığı için Python tarafından "işe yaramaz" olarak algılanan sınıfımız çöp toplama işlemine tabi tutularak derhal belleği terketmesi sağlanacaktır. Bu yüzden bunu her çalıştırdığınızda yeniden belleğe yüklemiş olacaksınız sınıfı. Bu da bir hayli verimsiz bir çalışma şeklidir.
Böylelikle zor kısmı geride bırakmış olduk. Artık önümüze bakabiliriz. Zira en temel bazı kavramları gözden geçirdiğimiz ve temelimizi oluşturduğumuz için, daha karışık şeyleri anlamak kolaylaşacaktır.
[değiştir] __init__ Nedir?
Eğer daha önce etrafta sınıfları içeren kodlar görmüşseniz, bu __init__ fonksiyonuna en azından bir göz aşinalığınız vardır. Genellikle şu şekilde kullanıldığını görürüz bunun:
def __init__(self):
Biz şimdilik bu yapıdaki __init__ kısmıyla ilgileneceğiz. “self”in ne olduğunu şimdilik bir kenara bırakıp, onu olduğu gibi kabul edelim. İşe hemen bir örnekle başlayalım. İsterseniz kendimizce ufacık bir oyun tasarlayalım:
#!/usr/bin/env python
#-*- coding:utf8 -*-
class Oyun:
def __init__(self):
enerji = 50
para = 100
fabrika = 4
isci = 10
print "enerji:", enerji
print "para:", para
print "fabrika:", fabrika
print "işçi:", isci
macera = Oyun()
Gayet güzel. Dikkat ederseniz "örnekleme" (instantiation) işlemini doğrudan dosya içinde hallettik. Komut satırına bırakmadık bu işi.
Şimdi bu kodları çalıştıracağız. Bir kaç seçeneğimiz var:
1. Üzerinde çalıştığımız platforma göre Python komut satırını, yani etkileşimli kabuğu açıyoruz. Orada şu komutu veriyoruz:
from deneme import *
Burada dosya adının "deneme.py" olduğunu varsaydık. Eğer örnekleme işlemini dosya içinden halletmemiş olsaydık, "from deneme import *" komutunu vermeden önce "macera = Oyun()" satırı yardımıyla ilk olarak sınıfımızı örneklendirmemiz gerekecekti.
2. GNU/Linux sistemlerinde başka bir seçenek olarak, ALT+F2 tuşlarına basıyoruz ve açılan pencerede "konsole" (KDE) veya "gnome-terminal" (GNOME) yazıp enter'e bastıktan sonra açtığımız komut satırında şu komutu veriyoruz:
python -i deneme.py
3. Eğer Windows'ta IDLE üzerinde çalışıyorsak, F5 tuşuna basarak veya "Run>Run Module" yolunu takip ederek kodlarımızı çalıştırıyoruz
Bu kodları yukarıdaki seçeneklerden herhangi biriyle çalıştırdığımızda, __init__ fonksiyonu içinde tanımlanmış olan bütün değişkenlerin, yani “niteliklerin”, ilk çalışma esnasında ekrana yazdırıldığını görüyoruz. İşte bu niteliklerin başlangıç değeri olarak belirlenebilmesi hep __init__ fonksiyonu sayesinde olmaktadır. Dolayısıyla şöyle bir şey diyebiliriz:
Python’da bir programın ilk kez çalıştırıldığı anda işlemesini istediğimiz şeyleri bu __init__ fonksiyonu içine yazıyoruz. Mesela yukarıdaki ufak oyun çalışmasında, oyuna başlandığı anda bir oyuncunun sahip olacağı özellikleri __init__ fonksiyonu içinde tanımladık. Buna göre bu oyunda bir oyuncu oyuna başladığında;
enerjisi 50, parası 100 fabrika sayısı 4, işçi sayısı ise 10
olacaktır.
Yalnız hemen uyaralım: Yukarıdaki örnek aslında pek de düzgün sayılmaz. Çok önemli eksiklikleri var bu kodun. Ama şimdi konumuz bu değil... Olayın iç yüzünü kavrayabilmek için öyle bir örnek vermemiz gerekiyordu. Bunu biraz sonra açıklayacağız. Biz okumaya devam edelim...
Bir de Tkinter ile bir örnek yapalım. Zira sınıflı yapıların en çok ve en verimli kullanıldığı yer arayüz programlama çalışmalarıdır:
from Tkinter import *
class Arayuz:
def __init__(self):
pencere = Tk()
dugme = Button(text="tamam")
dugme.pack()
uygulama = Arayuz()
Bu kodları da yukarıda saydığımız yöntemlerden herhangi biri ile çalıştırıyoruz. Tabii ki bu kod da eksiksiz değildir. Ancak şimdilik amacımıza hizmet edebilmesi için kodlarımızı bu şekilde yazmamız gerekiyordu. Ama göreceğiniz gibi yine de çalışıyor bu kodlar... Dikkat ederseniz burada da örnekleme işlemini dosya içinden hallettik. Eğer örnekleme satırını dosya içine yazmazsak, Tkinter penceresinin açılması için komut satırında "uygulama = Arayuz()" gibi bir satır yazmamız gerekir.
Buradaki __init__ fonksiyonu sayesinde “Arayuz” adlı sınıf her çağrıldığında bir adet Tkinter penceresi ve bunun içinde bir adet düğme otomatik olarak oluşacaktır. Zaten bu __init__ fonksiyonuna da İngilizce’de çoğu zaman “constructor” (oluşturan, inşa eden, meydana getiren) adı verilir. Gerçi __init__ fonksiyonuna "constructor" demek pek doğru bir ifade sayılmaz, ama biz bunu şimdi bir kenara bırakalım. Sadece aklımızda olsun, __init__ fonksiyonu gerçek anlamda bir "constructor" değildir, ama ona çok benzer...
Şöyle bir yanlış anlaşılma olmamasına dikkat edin:
“__init__” fonksiyonunun, “varsayılan değerleri belirleme”, yani “inşa etme” özelliği konumundan kaynaklanmıyor. Yani bu __init__ fonksiyonu, işlevini sırf ilk sırada yer aldığı için yerine getirmiyor. Bunu test etmek için, isterseniz yukarıdaki kodları “__init__” fonksiyonunun adını değiştirerek çalıştırmayı deneyin. Aynı işlevi elde edemezsiniz… Mesela __init__ yerine __simit__ deyin. Çalışmaz…
__init__ konusuna biraz olsun ışık tuttuğumuza göre artık en önemli bileşenlerden ikincisine gelebiliriz: self
[değiştir] self Nedir?
Bu küçücük kelime Python’da sınıfların en can alıcı noktasını oluşturur. Esasında çok basit bir işlevi olsa da, bu işlevi kavrayamazsak neredeyse bütün bir sınıf konusunu kavramak imkansız hale gelecektir. Self’i anlamaya doğru ilk adımı atmak için yukarıda kullandığımız kodlardan faydalanarak bir örnek yapmaya çalışalım. Kodumuz şöyleydi:
class Oyun:
def __init__(self):
enerji = 50
para = 100
fabrika = 4
isci = 10
print "enerji:", enerji
print "para:", para
print "fabrika:", fabrika
print "işçi:", isci
macera = Oyun()
Diyelim ki biz burada “enerji, para, fabrika, işçi” değişkenlerini ayrı bir fonksiyon içinde kullanmak istiyoruz. Yani mesela “göster” adlı ayrı bir fonksiyonumuz olsun ve biz bu değişkenleri ekrana yazdırmak istediğimizde bu “göster” fonksiyonundan yararlanalım. Kodların şu anki halinde olduğu gibi, bu kodlar tanımlansın, ama doğrudan ekrana dökülmesin. Şöyle bir şey yazmayı deneyelim. Bakalım sonuç ne olacak?
class Oyun:
def __init__(self):
enerji = 50,
para = 100
fabrika = 4
isci = 10
def goster():
print "enerji:", enerji
print "para:", para
print "fabrika:", fabrika
print "işçi:", isci
macera = Oyun()
Öncelikle bu kodların sahip olduğu “niteliklere” bir bakalım:
enerji, para, fabrika, işci ve goster()
Burada “örneğimiz” (instance) “macera” adlı değişken. Dolayısıyla bu niteliklere şu şekilde ulaşabiliriz:
macera.enerji macera.para macera.fabrika macera.işci macera.goster()
Hemen deneyelim. Ama o da ne? Mesela “macera.goster()” dediğimizde şöyle bir hata alıyoruz:
Traceback (most recent call last): File "<pyshell#0>", line 1, in <module> macera.goster() TypeError: goster() takes no arguments (1 given)
Belli ki bir hata var kodlarımızda. goster() fonksiyonuna bir “self” ekleyerek tekrar deneyelim. Belki düzelir…
class Oyun:
def __init__(self):
enerji = 50
para = 100
fabrika = 4
isci = 10
def goster(self):
print "enerji:", enerji
print "para:", para
print "fabrika:", fabrika
print "işçi:", isci
macera = Oyun()
Tekrar deniyoruz:
macera.goster()
Olmadı… Bu sefer de şöyle bir hata aldık:
enerji: Traceback (most recent call last): File "<pyshell#0>", line 1, in <module> macera.goster() File "xxxxxxxxxxxxxxxxxxx", line 9, in goster print "enerji:", enerji NameError: global name 'enerji' is not defined
Hmm… Sorunun ne olduğu az çok ortaya çıktı. Hatırlarsanız buna benzer hata mesajlarını Fonksiyon tanımlarken “global” değişkeni yazmadığımız zamanlarda da alıyorduk… İşte “self” burada devreye giriyor. Yani bir bakıma, fonksiyonlardaki “global” ifadesinin yerini tutuyor. Daha doğru bir ifadeyle, burada "macera" adlı sınıf örneğini temsil ediyor. Artık kodlarımızı düzeltebiliriz:
class Oyun:
def __init__(self):
self.enerji = 50
self.para = 100
self.fabrika = 4
self.isci = 10
def goster(self):
print "enerji:", self.enerji
print "para:", self.para
print "fabrika:", self.fabrika
print "işçi:", self.isci
macera = Oyun()
Gördüğünüz gibi, kodlar içinde yazdığımız değişkenlerin, fonksiyon dışından da çağrılabilmesi için, yani bir bakıma "global" bir nitelik kazanması için “self” olarak tanımlanmaları gerekiyor. Yani mesela, “enerji” yerine “self.enerji” diyerek, bu “enerji” adlı değişkenin yalnızca içinde bulunduğu fonksiyonda değil, o fonksiyonun dışında da kullanılabilmesini sağlıyoruz. İyice somutlaştırmak gerekirse, “__init__” fonksiyonu içinde tanımladığımız “enerji” adlı değişken, bu haliyle “goster” adlı fonksiyonun içinde kullanılamaz. Daha da önemlisi bu kodları bu haliyle tam olarak çalıştıramayız da. Mesela şu temel komutları işletemeyiz:
macera.enerji macera.para macera.isci macera.fabrika
Eğer biz “enerji” adlı değişkeni “goster” fonksiyonu içinde kullanmak istersek değişkeni sadece “enerji” değil, “self.enerji” olarak tanımlamamız gerekir. Ayrıca bunu “goster” adlı fonksiyon içinde kullanırken de sadece “enerji” olarak değil, “self.enerji” olarak yazmamız gerekir. Üstelik mesela "enerji" adlı değişkeni herhangi bir yerden çağırmak istediğimiz zaman da bunu önceden "self" olarak tanımlamış olmamız gerekir.
Şimdi tekrar deneyelim:
macera.goster enerji: 50 para: 100 fabrika: 4 işçi: 10
macera.enerji50
macera.para 100
macera.fabrika 410
macera.isci
Sınıfın niteliklerine tek tek nasıl erişebildiğimizi görüyorsunuz… Bu arada, isterseniz "self"i, "macera" örneğinin yerini tutan bir kelime olarak da kurabilirsiniz zihninizde. Yani kodları çalıştırırken "macera.enerji" diyebilmek için, en başta bunu "self.enerji" olarak tanımlamamız gerekiyor... Bu düşünme tarzı işimizi biraz daha kolaylaştırabilir.
Bir de Tkinter’li örneğimize bakalım:
from Tkinter import *
class Arayuz:
def __init__(self):
pencere = Tk()
dugme = Button(text="tamam")
dugme.pack()
uygulama = Arayuz()
Burada tanımladığımız düğmenin bir iş yapmasını sağlayalım. Mesela düğmeye basılınca komut ekranında bir yazı çıksın. Önce şöyle deneyelim:
from Tkinter import *
class Arayuz:
def __init__(self):
pencere = Tk()
dugme = Button(text="tamam",command=yaz)
dugme.pack()
def yaz():
print "Hadi eyvallah!"
uygulama = Arayuz()
Tabii ki bu kodları çalıştırdığımızda şöyle bir hata mesajı alırız:
Traceback (most recent call last): File "xxxxxxxxxxxxxxxxxxxx", line 13, in <module> uygulama = Arayuz() File "xxxxxxxxxxxxxxxxxxxxxxxx", line 7, in __init__ dugme = Button(text="tamam",command=yaz) NameError: global name 'yaz' is not defined
Bunun sebebini bir önceki örnekte öğrenmiştik. Kodlarımızı şu şekilde yazmamız gerekiyor:
from Tkinter import *
class Arayuz:
def __init__(self):
pencere = Tk()
dugme = Button(text="tamam",command=self.yaz)
dugme.pack()
def yaz(self):
print "Hadi eyvallah!"
uygulama = Arayuz()
Gördüğünüz gibi, eğer programın farklı noktalarında kullanacağımız değişkenler veya fonksiyonlar varsa, bunları “self” öneki ile birlikte tanımlıyoruz. “def self.yaz” şeklinde bir fonksiyon tanımlama yöntemi olmadığına göre bu işlemi “def yaz(self)” şeklinde yapmamız gerekiyor. Bu son örnek aslında yine de tam anlamıyla kusursuz bir örnek değildir. Ama şimdilik elimizden bu kadarı geliyor. Daha çok bilgimiz olduğunda bu kodları daha düzgün yazmayı da öğreneceğiz.
Bu iki örnek içinde, “self”lerle oynayarak olayın iç yüzünü kavramaya çalışın. Mesela yaz() fonksiyonundaki self parametresini silince ne tür bir hata mesajı alıyorsunuz, “command=self.yaz” içindeki “self” ifadesini silince ne tür bir hata mesajı alıyorsunuz? Bunları iyice inceleyip, “self”in nerede ne işe yaradığını kavramaya çalışın.
Bu noktada küçük bir sır verelim. Siz bu kelimeyi bütün sınıflı kodlamalarda bu şekilde görüyor olsanız da aslında illa ki “self” kelimesini kullanacaksınız diye bir kaide yoktur. Self yerine başka kelimeler de kullanabilirsiniz. Mesela yukarıdaki örneği şöyle de yazabilirsiniz:
from Tkinter import * class Arayuz: def __init__(armut): pencere = Tk() dugme = Button(text="tamam",command=armut.yaz) dugme.pack() def yaz(armut): print "Hadi eyvallah!" uygulama = Arayuz()
Ama siz böyle yapmayın. “self” kelimesinin kullanımı o kadar yaygınlaşmış ve yerleşmiştir ki, sizin bunu kendi kodlarınızda dahi olsa değiştirmeye kalkmanız pek hoş karşılanmayacaktır. Ayrıca sizin kodlarınızı okuyan başkaları, ne yapmaya çalıştığınızı anlamakta bir an da olsa tereddüt edecektir. Hatta birkaç yıl sonra dönüp siz dahi aynı kodlara baktığınızda, “Ben burada ne yapmaya çalışmışım,” diyebilirsiniz… O yüzden, “self” iyidir, “self” kullanın!...
Sizi “self” kullanmaya ikna ettiğimizi kabul edersek, artık yolumuza devam edebiliriz.
Hatırlarsanız yukarıda ufacık bir oyun çalışması yapmaya başlamıştık. Gelin isterseniz oyunumuzu biraz ayrıntılandıralım. Elimizde şimdilik şunlar vardı:
class Oyun:
def __init__(self):
self.enerji = 50
self.para = 100
self.fabrika = 4
self.isci = 10
def goster(self):
print "enerji:", self.enerji
print "para:", self.para
print "fabrika:", self.fabrika
print "işçi:", self.isci
macera = Oyun()
Buradaki kodlar yardımıyla bir oyuncu oluşturduk. Bu oyuncunun oyuna başladığında sahip olacağı enerji, para, fabrika ve işçi bilgilerini de girdik. Kodlarımız arasındaki “goster()” fonksiyonu yardımıyla da her an bu bilgileri görüntüleyebiliyoruz.
Şimdi isterseniz oyunumuza biraz hareket getirelim. Mesela kodlara yeni bir fonksiyon ekleyerek oyuncumuza yeni fabrikalar kurma olanağı tanıyalım:
class Oyun:
def __init__(self):
self.enerji = 50
self.para = 100
self.fabrika = 4
self.isci = 10
def goster(self):
print "enerji:", self.enerji
print "para:", self.para
print "fabrika:", self.fabrika
print "işçi:", self.isci
def fabrikakur(self,miktar):
if self.enerji > 3 and self.para > 10:
self.fabrika = miktar + self.fabrika
self.enerji = self.enerji - 3
self.para = self.para – 10
print miktar, "adet fabrika kurdunuz! Tebrikler!"
else:
print "Yeni fabrika kuramazsınız. Çünkü yeterli enerjiniz/paranız yok!"
macera = Oyun()
Burada “fabrikakur()” fonksiyonuyla ne yapmaya çalıştığımız aslında çok açık. Hemen bunun nasıl kullanılacağını görelim:
macera.fabrikakur(5)
Bu komutu verdiğimizde, “5 adet fabrika kurdunuz! Tebrikler!” şeklinde bir kutlama mesajı gösterecektir bize programımız… Kodlarımız içindeki "def fabrikakur(self,miktar)" ifadesinde gördüğümüz "miktar" kelimesi, kodları çalıştırırken vereceğimiz parametreyi temsil ediyor. Yani burada "5" sayısını temsil ediyor. Eğer "macera.fabrikakur()" fonksiyonunu kullanırken herhangi bir sayı belirtmezseniz, hata alırsınız. Çünkü kodlarımızı tanımlarken fonksiyon içinde "miktar" adlı bir ifade kullanarak, kullanıcıdan fonksiyona bir parametre vermesini beklediğimizi belirttik. Dolayısıyla Python kullanıcıdan parantez içinde bir parametre girmesini bekleyecektir. Eğer fonksiyon parametresiz çalıştırılırsa da, Python'un beklentisi karşılanmadığı için, hata verecektir. Burada dikkat edeceğimiz nokta, kodlar içinde bir fonksiyon tanımlarken ilk parametrenin her zaman "self" olması gerektiğidir. Yani "def fabrikakur(miktar)" değil, "def fabrikakur(self,miktar)" dememiz gerekiyor.
Şimdi de şu komutu verelim:
macera.goster()
Bu komut şu çıktıyı verecektir:
enerji: 47 para: 90 fabrika: 9 işçi: 10
Gördüğünüz gibi oyuncumuz 5 adet fabrika kazanmış, ama bu işlem enerjisinde ve parasında bir miktar kayba neden olmuş (fabrika kurmayı bedava mı sandınız!).
Yazdığımız kodlara dikkatlice bakarsanız, oradaki "if" deyimi sayesinde oyuncunun enerjisi 3’ün altına, parası da 10’un altına düşerse şöyle bir mesaj verilecektir:
Yeni fabrika kuramazsınız. Çünkü yeterli enerjiniz/paranız yok!
Art arda fabrikalar kurarak bunu kendiniz de test edebilirsiniz.
[değiştir] Miras Alma (Inheritance)
Şimdiye kadar bir oyuncu oluşturduk ve bu oyuncuya oyuna başladığı anda sahip olacağı bazı özellikler verdik. Oluşturduğumuz oyuncu isterse oyun içinde fabrika da kurabiliyor. Ama böyle, “kendin çal, kendin oyna” tarzı bir durumun sıkıcı olacağı belli. O yüzden gelin oyuna biraz hareket katalım! Mesela oyunumuzda bir adet oyuncu dışında bir adet de düşman olsun. O halde hemen bir adet düşman oluşturalım:
class Dusman:
“Düşman”ımızın gövdesini oluşturduk. Şimdi sıra geldi onun kolunu bacağını oluşturmaya, ona bir kişilik kazandırmaya…
Hatırlarsanız, oyunun başında oluşturduğumuz oyuncunun bazı özellikleri vardı. (enerji, para, fabrika, işçi gibi…) İsterseniz düşmanımızın da buna benzer özellikleri olsun. Mesela düşmanımız da oyuncunun sahip olduğu özelliklerin aynısıyla oyuna başlasın. Yani onun da;
enerjisi 50, parası 100 fabrika sayısı 4, işçi sayısı ise 10
olsun. Şimdi hatırlarsanız oyuncu için bunu şöyle yapmıştık:
class Oyun:
def __init__(self):
enerji = 50
para = 100
fabrika = 4
isci = 10
Şimdi aynı şeyi “Dusman” sınıfı için de yapacağız. Peki bu özellikleri yeniden tek tek “düşman” için de yazacak mıyız? Tabii ki hayır. O halde nasıl yapacağız bunu? İşte burada imdadımıza Python sınıflarının “miras alma” özelliği yetişiyor. Yabancılar bu kavrama “inheritance” adını veriyorlar. Yani, nasıl Mısır’daki dedenizden size miras kaldığında dedenizin size bıraktığı mirasın nimetlerinden her yönüyle yararlanabiliyorsanız, bir sınıf başka bir sınıftan miras aldığında da aynı şekilde miras alan sınıf miras aldığı sınıfın özelliklerini kullanabiliyor. Az laf, çok iş. Hemen bir örnek yapalım. Yukarıda “Dusman” adlı sınıfımızı oluşturmuştuk:
class Dusman:
Dusman sınıfı henüz bu haliyle hiçbir şey miras almış değil. Hemen miras aldıralım. Bunun için sınıfımızı şöyle tanımlamamız gerekiyor:
class Dusman(Oyun):
Böylelikle daha en başta tanımladığımız “Oyun” adlı sınıfı, bu yeni oluşturduğumuz “Dusman” adlı sınıfa miras verdik. Dusman sınıfının durumunu Python’cada şöyle ifade edebiliriz:
“Dusman sınıfı Oyun sınıfını miras aldı” (Dusman inherits from Oyun)
Bu haliyle kodlarımız henüz eksik. Şimdilik şöyle bir şey yazıp sınıfımızı kitabına uyduralım:
class Dusman(Oyun):
pass
dsman = Dusman()
Yukarıda “pass” ifadesini neden kullandığımızı biliyorsunuz. Sınıfı tanımladıktan sonra iki nokta üst üstenin ardından aşağıya bir kod bloğu yazmamız gerekiyor. Ama şu anda oraya yazacak bir kodumuz yok. O yüzden idareten oraya bir pass ifadesi yerleştirerek gerekli kod bloğunu geçiştirmiş oluyoruz. O kısmı boş bırakamayız. Yoksa sınıfımız kullanılamaz durumda olur. Daha sonra oraya yazacağımız kod bloklarını hazırladıktan sonra oradaki “pass” ifadesini sileceğiz.
Şimdi bakalım bu sınıfla neler yapabiliyoruz?
Bu kodları, yazının başında anlattığımız şekilde çalıştıralım. Dediğimiz gibi, “Dusman” adlı sınıfımız daha önce tanımladığımız “Oyun” adlı sınıfı miras alıyor. Dolayısıyla “Dusman” adlı sınıf “Oyun” adlı sınıfın bütün özelliklerine sahip. Bunu hemen test edelim:
dsman.goster() enerji: 50 para: 100 fabrika: 4 işçi: 10
Gördüğünüz gibi, Oyun sınıfının bir fonksiyonu olan “goster”i “Dusman” sınıfı içinden de çalıştırabildik. Üstelik Dusman içinde bu değişkenleri tekrar tanımlamak zorunda kalmadan… İstersek bu değişkenlere teker teker de ulaşabiliriz:
dsman.enerji 50 dsman.isci 10
Dusman sınıfı aynı zamanda Oyun sınıfının “fabrikakur” adlı fonksiyonuna da erişebiliyor:
dsman.fabrikakur(4) 4 adet fabrika kurdunuz! Tebrikler!
Gördüğünüz gibi düşmanımız kendisine 4 adet fabrika kurdu!.. Düşmanımızın durumuna bakalım:
dsman.goster() enerji: 47 para: 90 fabrika: 8 işçi: 10
Evet, düşmanımızın fabrika sayısı artmış, enerjisi ve parası azalmış. Bir de kendi durumumuzu kontrol edelim:
macera.goster() enerji: 50 para: 100 fabrika: 4 işçi: 10
Dikkat ederseniz, Oyun ve Dusman sınıfları aynı değişkenleri kullandıkları halde birindeki değişiklik öbürünü etkilemiyor. Yani düşmanımızın yeni bir fabrika kurması bizim değerlerimizi değişikliğe uğratmıyor.
Şimdi şöyle bir şey yapalım:
Düşmanımızın, oyuncunun özelliklerine ek olarak bir de “ego” adlı bir niteliği olsun. Mesela düşmanımız bize her zarar verdiğinde egosu büyüsün!...
Önce şöyle deneyelim:
class Dusman(Oyun):
def __init__(self):
self.ego = 0
Bu kodları çalıştırdığımızda hata alırız. Çünkü burada yeni bir “__init__” fonksiyonu tanımladığımız için, bu yeni fonksiyon kendini Oyun sınıfının __init__ fonksiyonunun üzerine yazıyor. Dolayısıyla Oyun sınıfından miras aldığımız bütün nitelikleri kaybediyoruz. Bunu önlemek için şöyle bir şey yapmamız gerekir:
class Dusman(Oyun):
def __init__(self):
Oyun.__init__(self)
self.ego = 0
Burada “Oyun.__init__(self)” ifadesiyle “Oyun” adlı sınıfın “__init__” fonksiyonu içinde yer alan bütün nitelikleri, “Dusman” adlı sınıfın __init__ fonksiyonu içine kopyalıyoruz. Böylece “self.ego” değişkenini tanımlarken, “enerji, para, vb.” niteliklerin kaybolmasını engelliyoruz.
Aslında bu haliyle kodlarımız düzgün şekilde çalışır. Kodlarımızı çalıştırdığımızda biz ekranda göremesek de aslında “ego” adlı niteliğe sahiptir düşmanımız. Ekranda bunu göremememizin nedeni tabii ki kodlarımızda henüz bu niteliği ekrana yazdıracak bir “print” deyiminin yer almaması… İsterseniz bu özelliği daha önce de yaptığımız gibi ayrı bir fonksiyon ile halledelim:
<pre> class Dusman(Oyun): def __init__(self): Oyun.__init__(self) self.ego = 0
def goster(self): Oyun.goster(self) print "ego:", self.ego
dsman = Dusman() </pre> Tıpkı “__init__” fonksiyonunda olduğu gibi, burada da “Oyun.goster(self)” ifadesi yardımıyla “Oyun” sınıfının “goster()” fonksiyonu içindeki değişkenleri “Dusman” sınıfının “goster()” fonksiyonu içine kopyaladık. Böylece “ego” değişkenini yazdırırken, öteki değişkenlerin de yazdırılmasını sağladık.
Şimdi artık düşmanımızın bütün niteliklerini istediğimiz şekilde oluşturmuş olduk. Hemen deneyelim:
dsman.goster() enerji: 50 para: 100 fabrika: 4 işçi: 10 ego: 0
Gördüğünüz gibi düşmanımızın özellikleri arasında oyuncumuza ilave olarak bir de “ego” adlı bir nitelik var. Bunun başlangıç değerini “0” olarak ayarladık. Daha sonra yazacağımız fonksiyonda düşmanımız bize zarar verdikçe egosu büyüyecek…. Şimdi gelin bu fonksiyonu yazalım:
class Dusman(Oyun):
def __init__(self):
Oyun.__init__(self)
self.ego = 0
def goster(self):
Oyun.goster(self)
print "ego:", self.ego
def fabrikayik(self,miktar):
macera.fabrika = macera.fabrika – miktar
self.ego = self.ego + 2
print "Tebrikler. Oyuncunun", miktar, "adet fabrikasını yıktınız!"
print "Üstelik egonuz da tavana vurdu!"
dsman = Dusman()
Dikkat ederseniz, "fabrikayik" fonksiyonu içindeki değişkeni "macera.fabrika" şeklinde yazdık. Yani bir önceki "Oyun" adlı sınıfın "örneğini" (instance) kullandık. "Dusman" sınıfının değil... Neden? Çok basit. Çünkü kendi fabrikalarımızı değil oyuncunun fabrikalarını yıkmak istiyoruz!..
Burada, şu kodu çalıştırarak oyuncumuzun kurduğu fabrikaları yıkabiliriz:
dsman.fabrikayik(2)
Biz burada “2” adet fabrika yıkmayı tercih ettik…
Kodlarımızın en son halini topluca görelim isterseniz:
class Oyun:
def __init__(self):
self.enerji = 50
self.para = 100
self.fabrika = 4
self.isci = 10
def goster(self):
print "enerji:", self.enerji
print "para:", self.para
print "fabrika:", self.fabrika
print "işçi:", self.isci
def fabrikakur(self,miktar):
if self.enerji > 3 and self.para > 10:
self.fabrika = miktar + self.fabrika
self.enerji = self.enerji - 3
self.para = self.para – 10
print miktar, "adet fabrika kurdunuz! Tebrikler!"
else:
print "Yeni fabrika kuramazsınız. Çünkü yeterli enerjiniz/paranız yok!"
macera = Oyun()
class Dusman(Oyun):
def __init__(self):
Oyun.__init__(self)
self.ego = 0
def goster(self):
Oyun.goster(self)
print “ego:”, self.ego
def fabrikayik(self,miktar):
macera.fabrika = macera.fabrika – miktar
self.ego = self.ego + 2
print “Tebrikler. Oyuncunun”, miktar, “adet fabrikasını yıktınız!”
print “Üstelik egonuz da tavana vurdu!”
dsman = Dusman()
En son oluşturduğumuz fonksiyonda nerede “Oyun” sınıfını doğrudan adıyla kullandığımıza ve nerede bu sınıfın “örneğinden” (instance) yararlandığımıza dikkat edin. Dikkat ederseniz, fonksiyon başlıklarını çağırırken doğrudan sınıfın kendi adını kullanıyoruz (mesela “Oyun.__init__(self)”). Bir fonksiyon içindeki değişkenleri çağırırken ise (mesela “macera.fabrika”), “örneği” (instance) kullanıyoruz. Eğer bir fonksiyon içindeki değişkenleri çağırırken de sınıf isminin kendisini kullanmak isterseniz, ifadeyi “Oyun().__init__(self)” şeklinde yazmanız gerekir. Ama siz böyle yapmayın... Yani değişkenleri çağırırken örneği kullanın.
Artık kodlarımız didiklenmek üzere sizi bekliyor. Burada yapılan şeyleri iyice anlayabilmek için kafanıza göre kodları değiştirin. Neyi nasıl değiştirdiğinizde ne gibi bir sonuç elde ettiğinizi dikkatli bir şekilde takip ederek, bu konunun zihninizde iyice yer etmesini sağlayın.
Aslında yukarıdaki kodları daha düzenli bir şekilde de yazmamız mümkün. Örneğin, “enerji, para, fabrika” gibi nitelikleri ayrı bir sınıf halinde düzenleyip, öteki sınıfların doğrudan bu sınıftan miras almasını sağlayabiliriz. Böylece sınıfımız daha derli toplu bir görünüm kazanmış olur. Aşağıdaki kodlar içinde, isimlendirmeleri de biraz değiştirerek standartlaştırdığımıza dikkat edin:
class Oyun:
def __init__(self):
self.enerji = 50
self.para = 100
self.fabrika = 4
self.isci = 10
def goster(self):
print "enerji:", self.enerji
print "para:", self.para
print "fabrika:", self.fabrika
print "işçi:", self.isci
oyun = Oyun()
class Oyuncu(Oyun):
def __init__(self):
Oyun.__init__(self)
def fabrikakur(self,miktar):
if self.enerji > 3 and self.para > 10:
self.fabrika = miktar + self.fabrika
self.enerji = self.enerji - 3
self.para = self.para - 10
print miktar, "adet fabrika kurdunuz! Tebrikler!"
else:
print "Yeni fabrika kuramazsınız. Çünkü yeterli enerjiniz/paranız yok!"
oyuncu = Oyuncu()
class Dusman(Oyun):
def __init__(self):
Oyun.__init__(self)
self.ego = 0
def goster(self):
Oyun.goster(self)
print "ego:", self.ego
def fabrikayik(self,miktar):
oyuncu.fabrika = oyuncu.fabrika - miktar
self.ego = self.ego + 2
print "Tebrikler. Oyuncunun", miktar, "adet fabrikasını yıktınız!"
print "Üstelik egonuz da tavana vurdu!"
dusman = Dusman()
Bu kodlar hakkında son bir noktaya daha değinelim. Hatırlarsanız oyuna başlarken oluşturulan niteliklerde değişiklik yapabiliyorduk. Mesela yukarıda “Dusman” sınıfı için “ego” adlı yeni bir nitelik tanımlamıştık. Bu nitelik sadece “Dusman” tarafından kullanılabiliyordu, Oyuncu tarafından değil. Aynı şekilde, yeni bir nitelik belirlemek yerine, istersek varolan bir niteliği iptal de edebiliriz. Diyelim ki Oyuncu’nun oyuna başlarken “fabrika”ları olsun istiyoruz, ama Dusman’ın oyun başlangıcında fabrikası olsun istemiyoruz. Bunu şöyle yapabiliriz:
class Dusman(Oyun):
def __init__(self):
Oyun.__init__(self)
del self.fabrika
self.ego = 0
Gördüğünüz gibi “Dusman” sınıfı için “__init__” fonksiyonunu tanımlarken “fabrika” niteliğini “del” komutuyla siliyoruz. Bu silme işlemi sadece “Dusman” sınıfı için geçerli oluyor. Bu işlem öteki sınıfları etkilemiyor. Bunu şöyle de ifade edebiliriz;
“del komutu yardımıyla fabrika adlı değişkene Dusman adlı bölgeden erişilmesini engelliyoruz.”
Dolayısıyla bu değişiklik sadece o “bölgeyi” etkiliyor. Öteki sınıflar ve daha sonra oluşturulacak yeni sınıflar bu işlemden etkilenmez. Yani aslında “del” komutuyla herhangi bir şeyi sildiğimiz yok! Sadece “erişimi engelliyoruz”.
Küçük bir not: Burada “bölge” olarak bahsettiğimiz şey aslında Python’cada “isim alanı” (namespace) olarak adlandırılıyor.
Şimdi bir örnek de Tkinter ile yapalım. Yukarıda verdiğimiz örneği hatırlıyorsunuz:
from Tkinter import *
class Arayuz:
def __init__(self):
pencere = Tk()
dugme = Button(text="tamam",command=self.yaz)
dugme.pack()
def yaz(self):
print "Hadi eyvallah!"
uygulama = Arayuz()
Bu örnek gayet düzgün çalışsa da bu sınıfı daha düzgün ve düzenli bir hale getirmemiz mümkün:
#!/usr/bin/env python
#-*-coding:utf-8-*-
from Tkinter import *
class Arayuz(Frame):
def __init__(self):
Frame.__init__(self)
self.pack()
self.pencerearaclari()
def pencerearaclari(self):
self.dugme = Button(self,text="tamam",command=self.yaz)
self.dugme.pack()
def yaz(self):
print "Hadi eyvallah!"
uygulama = Arayuz()
uygulama.mainloop()
Burada dikkat ederseniz, Tkinter’in “Frame” adlı sınıfını miras aldık. Buradan anlayacağımız gibi, “miras alma” (inheritance) özelliğini kullanmak için miras alacağımız sınıfın o anda kullandığımız modül içinde olması şart değil. Burada olduğu gibi, başka modüllerin içindeki sınıfları da miras alabiliyoruz. Yukarıdaki kodları dikkatlice inceleyin. Başta biraz karışık gibi görünse de aslında daha önce verdiğimiz basit örneklerden hiç bir farkı yoktur.
[değiştir] Eski ve Yeni Sınıflar
Şimdiye kadar verdiğimiz sınıf örneklerinde önemli bir konudan hiç bahsetmedik. Python'da iki tip sınıf vardır: Eski tip sınıflar ve yeni tip sınıflar. Ancak korkmanızı gerektirecek kadar fark yoktur bu iki sınıf tipi arasında. Ayrıca hangi sınıf tipini kullanırsanız kullanın sorun yaşamazsınız. Ama tabii ki kendimizi yeni tipe alıştırmakta fayda var, çünkü muhtemelen Python'un sonraki sürümlerinden birinde (büyük ihtimalle Python 3.0'da) eski tip sınıflar kullanımdan kaldırılacaktır. Eski tip sınıflar ile yeni tip sınıflar arasındaki en büyük fark şudur:
Eski tip sınıflar şöyle tanımlanır:
class Deneme:
Yeni tip sınıflar ise şöyle tanımlanır:
class Deneme(object)
Gördüğünüz gibi, eski tip sınıflarda başka bir sınıfı miras alma zorunluluğu yoktur. O yüzden sınıfları istersek parantezsiz olarak tanımlayabiliyoruz. Yeni tip sınıflarda ise her sınıf mutlaka başka bir sınıfı miras almalıdır. Eğer kodlarınız içinde gerçekten miras almanız gereken başka bir sınıf yoksa, öntanımlı olarak "object" adlı sınıfı miras almanız gerekiyor. Dolayısıyla politikamız şu olacak:
"Ya bir sınıfı miras al, ya da miras alman gereken herhangi bir sınıf yoksa, "object" adlı sınıfı miras al..."
Dediğimiz gibi, eski ve yeni sınıflar arasındaki en temel fark budur.
Aslında daha en başta hiç eski tip sınıfları anlatmadan doğrudan yeni tip sınıfları anlatmakla işe başlayabilirdik. Ama bu pek doğru bir yöntem olmazdı. Çünkü her ne kadar eski tip sınıflar sonraki bir Python sürümünde tedavülden kaldırılacaksa da, etrafta eski sınıflarla yazılmış bolca kod göreceksiniz. Dolayısıyla sadece yeni tip sınıfları öğrenmek mevcut tabloyu eksik algılamak olacaktır...
Yukarıda hatırlarsanız "pass" ifadesini kullanmıştık. Sınıfların yapısı gereği bir kod bloğu belirtmemiz gerektiğinde, ama o anda yazacak bir şeyimiz olmadığında sırf bir "yer tutucu" vazifesi görsün diye o "pass" ifadesini kullanmıştık. Yine bu "pass" ifadesini kullanarak başka bir şey daha yapabiliriz. Şu örneğe bakalım:
class BosSinif(object):
pass
Böylece içi boş da olsa kurallara uygun bir sınıf tanımlamış olduk. Ayrıca dikkat ederseniz, sınıfımızı tanımlarken "yeni sınıf" yapısını kullandık. Özel olarak miras alacağımız bir sınıf olmadığı için doğrudan "object" adlı sınıfı miras aldık. Yine dikkat ederseniz sınıfımız için bir "örnek" (instance) de belirtmedik. Hem sınıfın içini doldurma işini, hem de örnek belirleme işini komut satırından halledeceğiz. Önce sınıfımızı örnekliyoruz:
>>>sinifimiz = BosSinif()
Gördüğünüz gibi "BosSınıf()" şeklinde, parametresiz olarak örnekliyoruz sınıfımızı. Zaten parantez içinde bir parametre belirtirseniz hata mesajı alırsınız...
Şimdi boş olan sınıfımıza "nitelikler" ekliyoruz:
>>>sinifimiz.sayi1 = 45 >>>sinifimiz.sayi2 = 55 >>>sinifimiz.sonuc = sinifimiz.sayi1 * sinifimiz.sayi2 >>>sinifimiz.sonuc >>>2475
İstersek sınıfımızın son halini, Python sınıflarının "__dict__" metodu yardımıyla görebiliriz:
>>>sinifimiz.__dict__
{'sayi2': 55, 'sayi1': 45, 'sonuc': 2475}
Gördüğünüz gibi sınıfın içeriği aslında bir sözlükten ibaret... Dolayısıyla sözlüklere ait şu işlemler sınıfımız için de geçerlidir:
>>>sinifimiz.__dict__.keys() ['sayi2', 'sayi1', 'sonuc']
>>>sinifimiz.__dict__.values() [55, 45, 2475]
Buradan öğrendiğimiz başka bir şey de, sınıfların içeriğinin dinamik olarak değiştirilebileceğidir. Yani bir sınıfı her şeyiyle tanımladıktan sonra, istersek o sınıfın niteliklerini etkileşimli olarak değiştirebiliyoruz.
[değiştir] Sonuç
Böylece "Nesne Tabanlı Programlama" konusunun sonuna gelmiş oluyoruz. Aslında daha doğru bir ifadeyle, Nesne Tabanlı Programlama'ya hızlı bir giriş yapmış oluyoruz. Çünkü NTP şu birkaç sayfada anlatılanlardan ibaret değildir. Bu yazımızda bizim yapmaya çalıştığımız şey, okuyucuya NTP hakkında bir fikir vermektir. Eğer okuyucu bu yazı sayesinde NTP hakkında hiç değilse birazcık fikir sahibi olmuşsa kendimizi başarılı sayacağız. Bu yazıdaki amaç NTP gibi çetrefilli bir konuyu okuyucunun gözünde bir nebze de olsa sevimli kılabilmek, konuyu kolay hazmedilir bir hale getirmektir. Okuyucu bu yazıdan sonra NTP'ye ilişkin başka kaynakları daha bir kendine güvenle inceleme imkanına kavuşacak ve okuduğunu daha kolay anlayacaktır. Bir sonraki bölümde de NTP konusunu işlemeye devam edeceğiz. Ama bu kez biraz daha ayrıntıya girerek...
