Nesne Tabanlı Programlama (Devamı)

Bu bölümde de, temellerini geçen derslerimizde attığımız nesne tabanlı programlama konusunu incelemeye devam edeceğiz. Bu bölümde uygulamaya yönelik bazı örnekler yapmanın yanı sıra, nesne tabanlı programlamaya ilişkin bazı teorik bilgiler de vereceğiz.

Nesneler

Geçen bölümlerden birinde sınıfları tanımlarken, bunların, nesne üretmemizi sağlayan bir veri tipi olduğuna dair muğlak bir laf etmiştik. İşte bu başlık altında, o tanım içinde geçen ve nesne tabanlı programlamanın temelini oluşturan ‘nesne’ kavramı üzerine eğileceğiz.

Nesne Nedir?

Programlamaya ilişkin kavramlar içinde, özellikle programlamaya yeni başlayanların kafasını en fazla karıştıran kavram nedir diye sorsak, herhalde alacağımız cevap ‘nesne’ olur. Hakikaten, sağda solda sürekli duyduğumuz bu ‘nesne’ denen şey, öteden beri yazılım geliştirici adaylarının zihnini karıştırır durur.

Aslında ‘nesne’ (object) dedikleri, ilk bakışta uyandırdığı izlenimin aksine, anlaması zor, gizemli bir kavram değildir. Dolayısıyla, nesne kavramına ilişkin olarak öğrenmemiz gereken ilk şey, bunun abartılacak veya korkulacak bir şey olmadığıdır. Peki ama tam olarak nedir bu nesne dedikleri?

Kabaca, Python’da belli birtakım metotlara ve/veya niteliklere sahip olan öğelere nesne adı verilir. Yani ‘nesne’ kelimesi, içinde birtakım metot ve/veya nitelikler barındıran öğeleri tanımlamak için kullanılan bir tabirden, basit bir isimlendirmeden ibarettir.

Peki bir nesne oluşturmak için acaba ne yapmamız gerekiyor?

Hatırlarsanız, geçen bölümde, sınıfların nesne üretmemizi sağlayan veri tipleri olduğunu söylemiştik. O halde gelin minik bir nesne üretelim:

class Sınıf():
    pass

sınıf = Sınıf()

İşte bu kodlardaki sınıf = Sınıf() komutu ile bir nesne üretmiş olduk. Nesnemizin adı da ‘sınıf’. Teknik olarak ifade edersek, sınıf örneği, Sınıf() adlı sınıfın bütün nitelik ve metotlarını bünyesinde barındıran bir nesnedir. Mesela yukarıdaki kodların sınıf.py adlı bir dosyada bulunduğunu varsayarak şöyle bir deneme yapalım:

>>> import sınıf
>>> snf = sınıf.Sınıf()

Bu şekilde, kodları içeren modülü içe aktarmış ve modül içindeki Sınıf() adlı sınıfı snf adı ile örneklemiş olduk. Yani yukarıdaki kodlar yardımıyla snf adlı bir nesne oluşturduk. Bakalım bu nesne hangi nitelik ve/veya metotlara sahipmiş:

>>> dir(snf)

['__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__']

Gördüğünüz gibi, biz boş bir sınıf tanımlamış olsak da, snf nesnesi öntanımlı olarak yine de bazı nitelik ve metotlara sahip. İşte Python’da, yukarıdaki gibi birtakım nitelik ve metotlara sahip olan bu tür öğelere ‘nesne’ adı veriyoruz.

Bir de isterseniz yukarıdaki gibi boş bir sınıf tanımlamak yerine, sınıfımız içinde kendimiz birtakım nitelik ve metotlar tanımlamayı da deneyelim:

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

    def __init__(self):
        self.örnek_niteliği = 'örnek niteliği'

    def örnek_metodu(self):
        print('örnek metodu')

    @classmethod
    def sınıf_metodu(cls):
        print('sınıf metodu')

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

Şimdi nesne içeriğini tekrar kontrol edelim:

>>> import sınıf
>>> snf = sınıf.Sınıf()
>>> dir(snf)

['__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__',
 'statik_metot', 'sınıf_metodu', 'sınıf_niteliği',
 'örnek_metodu', 'örnek_niteliği']

Gördüğünüz gibi, kendi tanımladığımız nitelik ve metotlar da snf adlı nesne içine eklenmiş…

İşte snf adlı sınıf örneğinin, yukarıda gösterilen birtakım durum ve davranışlara sahip olmasından yola çıkarak, snf örneğinin bir nesne olduğunu söylüyoruz.

Yukarıdaki açıklamaların, ‘nesne’ kavramı hakkında en azından bir fikir sahibi olmanızı sağladığını zannediyorum. Gördüğünüz gibi, nesne denen şey aslında basit bir isimlendirmeden ibarettir: Python’da belli bir durumu/niteliği/metodu/davranışı olan elemanlara/öğelere nesne (object) adı veriyoruz. Peki o zaman, nesne denen şey basit bir adlandırmadan ibaretse nesne tabanlı programlamanın etrafında koparılan bunca yaygaranın sebebi nedir?

Nesne tabanlı programlamayı bu kadar özel ve önemli kılan şeyin ne olduğunu anlamak için gelin nesnelere biraz daha yakından bakalım.

Basit Bir Oyun

Gelin isterseniz nesne denen kavramı daha iyi anlayabilmek, bir nesneyi nesne yapan metot ve nitelikler arasındaki ilişkiyi daha net bir şekilde kavrayabilmek için, komut satırı üzerinde çalışan çok basit bir oyun tasarlayalım. Bu şekilde hem eski bilgilerimizi tekrar etmiş oluruz, hem teorik bilgilerimizi uygulama sahasına dökmüş oluruz, hem de yeni şeyler öğrenmiş oluruz.

Oyunumuzun kodları şöyle:

import time
import random
import sys

class Oyuncu():
    def __init__(self, isim, can=5, enerji=100):
        self.isim = isim
        self.darbe = 0
        self.can = can
        self.enerji = enerji

    def mevcut_durumu_görüntüle(self):
        print('darbe: ', self.darbe)
        print('can: ', self.can)
        print('enerji: ', self.enerji)

    def saldır(self, rakip):
        print('Bir saldırı gerçekleştirdiniz.')
        print('Saldırı sürüyor. Bekleyiniz.')

        for i in range(10):
            time.sleep(.3)
            print('.', end='', flush=True)

        sonuç = self.saldırı_sonucunu_hesapla()

        if sonuç == 0:
            print('\nSONUÇ: kazanan taraf yok')

        if sonuç == 1:
            print('\nSONUÇ: rakibinizi darbelediniz')
            self.darbele(rakip)

        if sonuç == 2:
            print('\nSONUÇ: rakibinizden darbe aldınız')
            rakip.darbele(self)

    def saldırı_sonucunu_hesapla(self):
         return random.randint(0, 2)

    def kaç(self):
        print('Kaçılıyor...')
        for i in range(10):
            time.sleep(.3)
            print('\n', flush=True)

        print('Rakibiniz sizi yakaladı')

    def darbele(self, darbelenen):
        darbelenen.darbe += 1
        darbelenen.enerji -= 1
        if (darbelenen.darbe % 5) == 0:
            darbelenen.can -= 1
        if darbelenen.can < 1:
            darbelenen.enerji = 0
            print('Oyunu {} kazandı!'.format(self.isim))
            self.oyundan_çık()

    def oyundan_çık(self):
        print('Çıkılıyor...')
        sys.exit()

##################################

# Oyuncular
siz = Oyuncu('Ahmet')
rakip = Oyuncu('Mehmet')

# Oyun başlangıcı
while True:
    print('Şu anda rakibinizle karşı karşıyasınız.',
          'Yapmak istediğiniz hamle: ',
          'Saldır:  s',
          'Kaç:     k',
          'Çık:     q', sep='\n')

    hamle = input('\n> ')
    if hamle == 's':
        siz.saldır(rakip)

        print('Rakibinizin durumu')
        rakip.mevcut_durumu_görüntüle()

        print('Sizin durumunuz')
        siz.mevcut_durumu_görüntüle()

    if hamle == 'k':
        siz.kaç()

    if hamle == 'q':
        siz.oyundan_çık()

Komut satırı üzerinde çalışan basit bir oyundur bu. Dilerseniz bu kodları incelemeye başlamadan önce, bir dosyaya kaydedip çalıştırın. Karşınıza şöyle bir ekran gelecek:

Şu anda rakibinizle karşı karşıyasınız.
Yapmak istediğiniz hamle:
Saldır:  s
Kaç:     k
Çık:     q

>

Programımız bize burada üç farklı seçenek sunuyor. Eğer rakibimize saldırmak istiyorsak klavyedeki ‘s’ tuşuna; rakibimizden kaçmak istiyorsak klavyedeki ‘k’ tuşuna; yok eğer oyundan çıkmak istiyorsak da klavyedeki ‘q’ tuşuna basacağız. Tercihinizi belirleyip neler olduğunu inceleyin ve oyunu iyice tanımaya çalışın.

Oyunu iyice anlayıp tanıdıktan sonra oyun kodlarını incelemeye geçebiliriz.

Yukarıda ilk olarak Oyuncu adlı bir sınıf tanımladık:

class Oyuncu():
    def __init__(self, isim, can=5, enerji=100):
        self.isim = isim
        self.darbe = 0
        self.can = can
        self.enerji = enerji

class kelimesinin sınıf tanımlamamızı sağlayan bir araç, Oyuncu kelimesinin ise tanımladığımız sınıfın adı olduğunu biliyoruz. Bu satırın hemen ardından gelen __init__() fonksiyonu, sınıfımız örneklendiğinde neler olacağını tanımladığımız yerdir. Bu sınıfın, örnekleme sırasında hangi parametreleri alacağını da __init__() fonksiyonu içinde belirliyoruz. Parametre listesinde gördüğümüz ilk öğe, yani self, sınıfın o anki örneğini temsil ediyor. Python’ın söz dizimi kuralları gereğince bu kelimeyi oraya yazmamız gerektiğini biliyoruz.

Yukarıdaki fonksiyon, self dışında toplam üç parametre alıyor: isim, can ve enerji. Bunlardan ilki, yani isim parametresinin öntanımlı bir değeri yok. Dolayısıyla sınıfı çağırırken (yani örneklerken) bu parametrenin değerini belirtmemiz gerekecek. Öteki iki parametre olan can ve enerji ise birtakım öntanımlı değerlere sahip. Dolayısıyla sınıfı örneklendirirken bu parametrelere farklı bir değer atamadığımız sürece, bu parametreler, listede belirtilen değerleri taşıyacak.

Parametre olarak belirlediğimiz değerleri sınıf içinde kullanabilmek için, bunları __init__() fonksiyonunun gövdesinde birer örnek niteliğine dönüştürüyoruz:

self.isim = isim
self.darbe = 0
self.can = can
self.enerji = enerji

Burada ilave olarak bir de değeri 0 olan self.darbe adlı bir değişken tanımladık. Bu da sınıfımızın örnek niteliklerinden biri olup, ilgili oyuncu (yani sınıfın o anki örneği) darbe aldıkça bunun değeri yükselecektir.

Gelin isterseniz bu aşamada sınıfımızı örnekleyerek neler olup bittiğini daha net anlamaya çalışalım:

class Oyuncu():
    def __init__(self, isim, can=5, enerji=100):
        self.isim = isim
        self.darbe = 0
        self.can = can
        self.enerji = enerji

#Sınıfımızı örnekliyoruz
oyuncu = Oyuncu('Ahmet')

Burada oyuncu = Oyuncu('Ahmet') komutunu verdiğimiz anda __init__() fonksiyonu çalışmaya başlıyor ve oyuncu adlı nesne için sırasıyla şu değişkenleri oluşturuyor:

isim = 'Ahmet'
darbe = 0
can = 5
enerji = 100

Bu örnek niteliklerine nasıl ulaşabileceğinizi biliyorsunuz:

print('İsim: ', oyuncu.isim)
print('Darbe: ', oyuncu.darbe)
print('Can: ', oyuncu.can)
print('Enerji: ', oyuncu.enerji)

Başta da söylediğimiz gibi, Oyuncu() sınıfını örnekleyerek meydana getireceğiniz bütün sınıf örnekleri, yani nesneler, __init__() fonksiyonu içinde tanımladığınız örnek niteliklerini taşıyacaktır:

class Oyuncu():
    def __init__(self, isim, can=5, enerji=100):
        self.isim = isim
        self.darbe = 0
        self.can = can
        self.enerji = enerji

oyuncu1 = Oyuncu('Ahmet')
oyuncu2 = Oyuncu('Mehmet')
oyuncu3 = Oyuncu('Veli')
oyuncu4 = Oyuncu('Ayşe')

Burada oyuncu1, oyuncu2, oyuncu3 ve oyuncu4 olmak üzere dört farklı nesne oluşturduk. Bu nesnelerin hangi niteliklere sahip olacağını ise Oyuncu() sınıfının tanımı içinde belirttik. Yani sınıfımız tıpkı bir fabrika gibi çalışarak, bizim için, aynı nitelikleri taşıyan dört farklı nesne üretti.

İşte nesne tabanlı programlamanın özünü oluşturan ‘nesne’ budur. Bir nesnenin hangi niteliklere sahip olacağını belirleyen veri tipine sınıf (class) derken, o sınıfın ortaya çıkardığı ürüne ise nesne (object) adını veriyoruz. Bunu şuna benzetebilirsiniz: Eğer ‘İnsan’ bir sınıfsa, ‘Mahmut’ bu sınıfın bir örneğidir. Dolayısıyla Mahmut, İnsan sınıfından üretilmiş bir nesnedir. Aynı şekilde eğer ‘Köpek’ bir sınıfsa, ‘Karabaş’ da bu sınıfın bir örneğidir. Yani Karabaş, Köpek sınıfından üretilmiş bir nesnedir. Mahmut’un hangi özelliklere sahip olacağını İnsan sınıfının nasıl tanımlandığı, Karabaş’ın hangi özelliklere sahip olacağını ise Köpek sınıfının nasıl tanımlandığı belirler. İşte aynı bunun gibi, Oyuncu() sınıfından üretilen nesnelerin hangi özelliklere sahip olacağını da Oyuncu() sınıfının nasıl tanımlandığı belirler.

Kodlarımızı incelemeye devam edelim…

def mevcut_durumu_görüntüle(self):
    print('darbe: ', self.darbe)
    print('can: ', self.can)
    print('enerji: ', self.enerji)

Burada mevcut_durumu_görüntüle() adlı bir örnek metodu tanımladık. Örnek metotlarının ilk parametresinin her zaman self olması gerektiğini biliyoruz.

Tanımladığımız örnek metodunun görevi, Oyuncu() sınıfından oluşturduğumuz nesnelerin (yani örneklerin) o anki darbe, can ve enerji durumlarını görüntülemek. Birer örnek niteliği olan darbe, can ve enerji değişkenlerine self aracılığıyla eriştiğimize özellikle dikkat ediyoruz.

Gelelim sınıfımızın önemli örnek metotlarından biri olan saldır() fonksiyonunu incelemeye:

def saldır(self, rakip):
    print('Bir saldırı gerçekleştirdiniz.')
    print('Saldırı sürüyor. Bekleyiniz.')

    for i in range(10):
        time.sleep(.3)
        print('.', end='', flush=True)

    sonuç = self.saldırı_sonucunu_hesapla()

    if sonuç == 0:
        print('\nSONUÇ: kazanan taraf yok')

    if sonuç == 1:
        print('\nSONUÇ: rakibinizi darbelediniz')
        self.darbele(rakip)

    if sonuç == 2:
        print('\nSONUÇ: rakibinizden darbe aldınız')
        rakip.darbele(self)

Bu fonksiyon, self dışında tek bir parametre alıyor. Fonksiyonu çalıştırırken kullanacağımız rakip parametresi, saldırının kime karşı (yani sınıf örneklerinden hangisine karşı) düzenleneceğini belirleyecek.

Fonksiyon gövdesinde ilk olarak şöyle bir kısım görüyoruz:

print('Bir saldırı gerçekleştirdiniz.')
print('Saldırı sürüyor. Bekleyiniz.')

for i in range(10):
    time.sleep(.3)
    print('.', end='', flush=True)

Burada saldırının gerçekleştiğine dair kullanıcıyı bilgilendirdikten sonra şöyle bir kod parçası yazdık:

for i in range(10):
    time.sleep(.3)
    print('.', end='', flush=True)

Bu kodlarda time adlı bir standart kütüphane modülünün sleep() adlı bir metodundan yararlandığımızı görüyorsunuz. Elbette bu modülü kullanabilmek için öncelikle bu modülü içe aktarmış olmamız gerekiyor. Bu işlemi dosyanın en başında import time satırı yardımıyla gerçekleştirdiğimizi görebilirsiniz.

Yukarıdaki satırlar, 300’er milisaniye aralıklarla, yan yana nokta işaretleri yerleştirecektir. Dilerseniz etkileşimli kabukta bu kodları şu şekilde test edebilirsiniz:

>>> import time
>>> for i in range(10):
...     time.sleep(.3)
...     print('.', end='', flush=True)

print() fonksiyonu içinde kullandığımız end ve flush parametrelerinin ne olduğunu ve ne işe yaradığını ilk derslerimizden hatırlıyor olmalısınız. Eğer hatırlamıyorsanız, bu parametreleri tek tek kodlardan çıkarıp, bu kodları bir de öyle çalıştırın. Sonucun ne olduğunu takip ederek, end ve flush parametrelerinin görevini daha iyi anlayabilirsiniz.

Bu kodların ardından şöyle bir satır yazdık:

sonuç = self.saldırı_sonucunu_hesapla()

Burada, saldırı_sonucunu_hesapla() adlı bir örnek metodunu çağırdığımızı görüyorsunuz:

def saldırı_sonucunu_hesapla(self):
     return random.randint(0, 2)

Biraz önce time adlı bir standart kütüphane modülünü kullanmıştık. Şimdi ise random adlı başka bir standart kütüphane modülünü kullanıyoruz. Elbette bu modülü de kullanabilmek için öncelikle bu modülü import random komutuyla içe aktarmış olmamız gerekiyor. Bu zorunluluğu da, tıpkı time modülünde olduğu gibi, dosyanın en başında yerine getirmiştik.

Yukarıda random modülünü, 0 ile 2 arası rastgele sayılar üretmek için kullandık. random.randint(0, 2) komutu her çalıştığında 0, 1 ve 2 sayılarından birini rastgele üretecektir. Buradan elde ettiğimiz sonucu sonuç adlı bir değişkene atayarak saldır() fonksiyonu içinde şu şekilde kullanıyoruz:

sonuç = self.saldırı_sonucunu_hesapla()

if sonuç == 0:
    print('\nSONUÇ: kazanan taraf yok')

if sonuç == 1:
    print('\nSONUÇ: rakibinizi darbelediniz')
    self.darbele(rakip)

if sonuç == 2:
    print('\nSONUÇ: rakibinizden darbe aldınız')
    rakip.darbele(self)

Eğer randint() metodu 0 sayısını üretirse, rakibimize karşı gerçekleştirdiğimiz saldırının sonuçsuz kaldığına hükmediyoruz:

if sonuç == 0:
    print('\nSONUÇ: kazanan taraf yok')

Eğer randint() metodu 1 sayısını üretirse, rakibimizi başarıyla darbelediğimize, 2 sayısını üretirse de rakibimiz tarafından darbelendiğimize hükmediyoruz:

if sonuç == 1:
    print('\nSONUÇ: rakibinizi darbelediniz')
    self.darbele(rakip)

if sonuç == 2:
    print('\nSONUÇ: rakibinizden darbe aldınız')
    rakip.darbele(self)

Saldırı sonucunda rakibimizi darbelediğimizde ve rakibimizden darbe yediğimizde darbele() adlı bir başka örnek metodunu çağırdığımızı da gözden kaçırmayın.

Bu arada, örnek metotlarına da self öneki ile eriştiğimize dikkatinizi çekmek isterim. Ayrıca her ne kadar örnek metotlarını tanımlarken parantez listesi içinde self kelimesini belirtsek de, bu metotları çağırırken bunları argüman olarak kullanmadığımıza da özellikle dikkat etmelisiniz. Yani biz bu metotları şöyle tanımlıyoruz:

def saldırı_sonucunu_hesapla(self):
     return random.randint(0, 2)

Burada parametre listesinde self’i görüyoruz. Ama bu fonksiyonları çağırırken parantez içinde bu self’i kullanmıyoruz:

self.saldırı_sonucunu_hesapla()

self’i parantez içinde bir argüman olarak kullanmak yerine, bu kelimeyi fonksiyon adının başına bir önek olarak takıyoruz.

Ne diyorduk? Evet, saldır() fonksiyonu içinde darbele() adlı bir fonksiyona atıfta bulunduk. Yani saldırı sonucunda rakibimizi darbelediğimizde ve rakibimizden darbe yediğimizde darbele() adlı bir başka örnek metodunu çağırdık:

def darbele(self, darbelenen):
    darbelenen.darbe += 1
    darbelenen.enerji -= 1
    if (darbelenen.darbe % 5) == 0:
        darbelenen.can -= 1
    if darbelenen.can < 1:
        darbelenen.enerji = 0
        print('Oyunu {} kazandı!'.format(self.isim))
        self.oyundan_çık()

Bu fonksiyon içinde, herhangi bir darbe alma durumunda oyuncunun darbe, can ve enerji miktarlarında meydana gelecek değişiklikleri tanımlıyoruz.

Buna göre herhangi bir darbe alma durumunda aşağıdaki işlemler gerçekleştirilecek:

Darbelenen oyuncunun darbe değeri 1 birim artacak:

darbelenen.darbe += 1

enerji değeri 1 birim azalacak:

darbelenen.enerji -= 1

Darbelenen oyuncu her 5 darbede 1 can kaybedecek:

if (darbelenen.darbe % 5) == 0:
    darbelenen.can -= 1

Burada her 5 darbede 1 can kaybetme kriterini nasıl belirlediğimize dikkat edin. Bildiğiniz gibi, oyuncu darbe yedikçe darbe değişkeninin değeri artıyor. Bu değer 5 sayısına ulaştığında, 5 % 5 işleminin sonucu 0 olacaktır. Yani bu sayı 5’e bölündüğünde bölme işleminden kalan değer 0 olacaktır. 5’in tüm katları için (5, 10, 15, 20 gibi…) bu durum geçerlidir. Eğer darbe değişkenin ulaştığı değer 5’in katı değilse, bu sayı 5’e tam bölünmediği için, bölmeden kalan değer 0 dışında bir sayı olur. Dolayısıyla darbe değerinin ulaştığı sayının 5’e bölünmesinden kalan değerin 0 olup olmadığını kontrol ederek oyuncunun 5 darbede 1 can kaybetmesini sağlayabiliyoruz.

Oyuncunun can değeri 1’in altına düştüğünde ise enerji değeri 0’a inecek ve oyunu kimin kazandığı ilan edildikten sonra oyun kapatılacak:

if darbelenen.can < 1:
    darbelenen.enerji = 0
    print('Oyunu {} kazandı!'.format(self.isim))
    self.oyundan_çık()

Burada oyundan_çık() adlı bir örnek metoduna daha atıfta bulunduk:

def oyundan_çık(self):
    print('Çıkılıyor...')
    sys.exit()

Gayet basit bir fonksiyon. Herhangi bir şekilde oyundan çıkmak gerektiğinde sys modülünün exit() fonksiyonunu kullanarak oyunu terk ediyoruz.

İlerlemeden önce, darbele() fonksiyonunu kullandığımız kısma tekrar bakalım:

sonuç = self.saldırı_sonucunu_hesapla()

if sonuç == 0:
    print('\nSONUÇ: kazanan taraf yok')

if sonuç == 1:
    print('\nSONUÇ: rakibinizi darbelediniz')
    self.darbele(rakip)

if sonuç == 2:
    print('\nSONUÇ: rakibinizden darbe aldınız')
    rakip.darbele(self)

Bildiğiniz gibi, darbele() fonksiyonu, self dışında 1 adet parametre daha alıyor. Bu parametre, darbeyi hangi oyuncunun alacağını gösteriyor. İşte bunu tespit etmek için darbelenen adlı bir parametre belirledik. Gördüğünüz gibi, darbele() fonksiyonu saldır() adlı başka bir fonksiyonun içinden çağrılıyor. saldır() fonksiyonu da rakip adlı bir parametre alıyor. İşte darbe alan oyuncunun can ve enerji değerlerini yenilemek istediğimizde bu parametreyi, darbele() fonksiyonuna gönderiyoruz:

self.darbele(rakip)

Burada darbelenen oyuncu karşı taraf. Yani rakibimiz bizden (self’den) darbe yemiş. Eğer darbelenen kişi kendimizsek, rakip oyuncusunun bizi darbelemesini istiyoruz:

rakip.darbele(self)

Pek çok kez söylediğimiz gibi, self kelimesi mevcut sınıf örneğini temsil eder. Dolayısıyla kendimize atıfta bulunmak istediğimiz durumlarda, yukarıda olduğu gibi self’i kullanabiliriz.

Eğer arzu ederseniz, darbele() fonksiyonunu şöyle de yazabilirsiniz:

def darbele(self):
    self.darbe += 1
    self.enerji -= 1
    if (self.darbe % 5) == 0:
        self.can -= 1
    if self.can < 1:
        self.enerji = 0
        print('Oyunu {} kazandı!'.format(self.isim))
        self.oyundan_çık()

Burada darbelenen parametresini iptal ettik. Kimin durumunun yenileceğini self’in kim olduğu belirleyecek:

if sonuç == 1:
    print('\nSONUÇ: rakibinizi darbelediniz')
    rakip.darbele()

if sonuç == 2:
    print('\nSONUÇ: rakibinizden darbe aldınız')
    self.darbele()

Gördüğünüz gibi, eğer rakibi darbeleyip onun can ve enerji durumunu yenilemek istiyorsak, ilgili fonksiyonu rakip.darbele() şeklinde çağırıyoruz. Kendimizin durumunu yenilemek istediğimizde ise self.darbele() komutunu kullanıyoruz. Tabii darbele fonksiyonunu bu şekilde tanımlayacaksak ismini darbe_al olarak belirlemek daha anlamlı olur.

Sınıfımızı tanımladığımıza göre artık bu sınıfı nasıl kullanacağımızı incelemeye geçebiliriz:

siz = Oyuncu('Ahmet')
rakip = Oyuncu('Mehmet')

Burada öncelikle Oyuncu() sınıfı için iki farklı nesne/örnek oluşturuyoruz:

siz = Oyuncu('Ahmet')
rakip = Oyuncu('Mehmet')

Bu iki nesne, Oyuncu() sınıfının bütün niteliklerini taşıyor. Nesneleri oluştururken, zorunlu argüman olan isim değerini mutlaka belirtmemiz gerektiğini unutmuyoruz.

Daha sonra bir while döngüsü içinde, oyunumuzun kullanıcı tarafından görüntülenecek kısmını kodluyoruz:

while True:
    print('Şu anda rakibinizle karşı karşıyasınız.',
          'Yapmak istediğiniz hamle: ',
          'Saldır:  s',
          'Kaç:     k',
          'Çık:     q', sep='\n')

    hamle = input('\n> ')
    if hamle == 's':
        siz.saldır(rakip)

        print('Rakibinizin durumu')
        rakip.mevcut_durumu_görüntüle()

        print('Sizin durumunuz')
        siz.mevcut_durumu_görüntüle()

    if hamle == 'k':
        siz.kaç()

    if hamle == 'q':
        siz.oyundan_çık()

Oyunun nasıl oynanacağı konusunda kullanıcılarımızı bilgilendiriyoruz:

print('Şu anda rakibinizle karşı karşıyasınız.',
      'Yapmak istediğiniz hamle: ',
      'Saldır:  s',
      'Kaç:     k',
      'Çık:     q', sep='\n')

Kullanıcılarımızın klavyede hangi tuşa bastığını şu şekilde alıyoruz:

hamle = input('\n> ')

Eğer kullanıcı ‘s’ tuşuna basarsa rakibimize saldırıyoruz:

if hamle == 's':
    siz.saldır(rakip)

Saldırının ardından hem kendi durumumuzu hem de rakibimizin durumunu görüntülüyoruz:

print('Rakibinizin durumu')
rakip.mevcut_durumu_görüntüle()

print('Sizin durumunuz')
siz.mevcut_durumu_görüntüle()

Eğer kullanıcı ‘k’ tuşuna basarsa:

if hamle == 'k':
    siz.kaç()

sınıf içinde tanımladığımız kaç() metodunu çalıştırıyoruz:

def kaç(self):
    print('Kaçılıyor...')
    for i in range(10):
        time.sleep(.3)
        print('\n', flush=True)

    print('Rakibiniz sizi yakaladı')

Burada 300’er milisaniyelik aralıklarla ‘\n’ kaçış dizisini kullanarak bir alt satıra geçiyoruz.

Kullanıcının ‘q’ tuşuna basması halinde ise oyundan derhal çıkıyoruz:

if hamle == 'q':
    siz.oyundan_çık()

Bu örnek kodlar bize sınıflar ve nesneler hakkında epey bilgi verdi. Ayrıca bu kodlar sayesinde önceki bilgilerimizi de pekiştirmiş olduk.

Her Şey Bir Nesnedir

Belki sağda solda şu sözü duymuşsunuzdur: Python’da her şey bir nesnedir. Gerçekten de (if, def, and, or gibi deyim ve işleçler hariç) Python’da her şey bir nesnedir. Peki her şeyin nesne olması tam olarak ne anlama geliyor?

Hatırlarsanız nesnenin ne olduğunu tanımlarken, belli bir durumda bulunan ve belli birtakım davranışları olan öğelere nesne adı verildiğini söylemiştik. İşte Python’daki her şey, bu tanım doğrultusunda bir nesnedir.

Mesela, aşağıdaki komutu verdiğimiz anda bir nesne oluşturmuş oluyoruz:

>>> 'istihza'

‘istihza’ karakter dizisi, str adlı sınıfın…

>>> type('istihza')

<class 'str'>

…bütün özelliklerini taşıyan bir nesnedir:

>>> dir('istihza')

['__add__', '__class__', '__contains__', '__delattr__',
 '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
 '__getattribute__', '__getitem__', '__getnewargs__',
 '__gt__', '__hash__', '__init__', '__iter__', '__le__',
 '__len__', '__lt__', '__mod__', '__mul__', '__ne__',
 '__new__', '__reduce__', '__reduce_ex__', '__repr__',
 '__rmod__', '__rmul__', '__setattr__', '__sizeof__',
 '__str__', '__subclasshook__', 'capitalize', 'casefold',
 'center', 'count', 'encode', 'endswith', 'expandtabs',
 'find', 'format', 'format_map', 'index', 'isalnum',
 'isalpha', 'isdecimal', 'isdigit', 'isidentifier',
 'islower', 'isnumeric', 'isprintable', 'isspace',
 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip',
 'maketrans', 'partition', 'replace', 'rfind', 'rindex',
 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split',
 'splitlines', 'startswith', 'strip', 'swapcase',
 'title', 'translate', 'upper', 'zfill']

Aynı şekilde, ['elma', 'armut'] listesi de, list adlı sınıfın…

>>> type(['elma', 'armut'])

<class 'list'>

…bütün özelliklerini taşıyan bir nesnedir:

>>> dir(['elma', 'armut'])

['__add__', '__class__', '__contains__', '__delattr__',
 '__delitem__', '__dir__', '__doc__', '__eq__',
 '__format__', '__ge__', '__getattribute__', '__getitem__',
 '__gt__', '__hash__', '__iadd__', '__imul__', '__init__',
 '__iter__', '__le__', '__len__', '__lt__', '__mul__',
 '__ne__', '__new__', '__reduce__', '__reduce_ex__',
 '__repr__', '__reversed__', '__rmul__', '__setattr__',
 '__setitem__', '__sizeof__', '__str__', '__subclasshook__',
 'append', 'clear', 'copy', 'count', 'extend', 'index',
 'insert', 'pop', 'remove', 'reverse', 'sort']

Hatta mesela 1 gibi alelade bir sayı bile, dış dünyayla iletişim kurmasını ve dış dünyanın kendisiyle iletişim kurabilmesini sağlayan pek çok nitelik ve metoda sahip bir nesnedir:

>>> dir(1)

['__abs__', '__add__', '__and__', '__bool__', '__ceil__',
 '__class__', '__delattr__', '__dir__', '__divmod__',
 '__doc__', '__eq__', '__float__', '__floor__',
 '__floordiv__', '__format__', '__ge__', '__getattribute__',
 '__getnewargs__', '__gt__', '__hash__', '__index__',
 '__init__', '__int__', '__invert__', '__le__', '__lshift__',
 '__lt__', '__mod__', '__mul__', '__ne__', '__neg__',
 '__new__', '__or__', '__pos__', '__pow__', '__radd__',
 '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__',
 '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__',
 '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__',
 '__rshift__', '__rsub__', '__rtruediv__', '__rxor__',
 '__setattr__', '__sizeof__', '__str__', '__sub__',
 '__subclasshook__', '__truediv__', '__trunc__', '__xor__',
 'bit_length', 'conjugate', 'denominator', 'from_bytes',
 'imag', 'numerator', 'real', 'to_bytes']

İşte konuya bu noktadan baktığımızda, Python’da her şey bir nesnedir. Yani Python’daki her şeyle, sahip oldukları metotlar ve nitelikler aracılığıyla etkileşebilirsiniz.

Python’ın bu özelliğini bilmek, muhatap olduğunuz programlama dilini ve onun kabiliyetlerini tanımak açısından önemlidir. Python’da her şeyin bir nesne olduğunu anladığınız anda, {'a': 0, 'b': 1} gibi bir kodla yalnızca basit bir sözlük tanımlamadığınızı, bunun arkaplanında, bu sözlükle etkileşim kurmanızı sağlayacak koca bir mekanizma bulunduğunu bilirsiniz.

Birinci Sınıf Öğeler

Tıpkı ‘her şey bir nesnedir’ sözü gibi, yine sağda solda sıklıkla duyabileceğiniz bir söz de Python’da nesnelerin ‘birinci sınıf öğeler’ olduğudur. Peki burada ‘birinci sınıf’ (first class) ifadesiyle kastedilen şey tam olarak nedir?

Programlama dillerinde herhangi bir öğenin birinci sınıf bir öğe olması, o öğenin, dil içindeki herhangi bir değer ile aynı kabiliyetlere sahip olması anlamına gelir. ‘Bunun birinci sınıf olmakla ne alakası var?’ diye sorduğunuzu duyar gibiyim…

Şöyle bir cümle kurduğunuzu düşünün: ‘Gelişmiş bir toplumda kadınlar birinci sınıf vatandaşlardır.’ Bu cümleden, bir toplumun gelişmiş sayılabilmesi için kadınların erkeklerle eşit haklara sahip olması gerektiğini anlıyoruz. Yani kadınların birinci sınıf vatandaşlar olması, erkeklerle eşit haklara sahip olması anlamına geliyor. İşte tıpkı bunun gibi, Python’daki sınıf yapılarının ‘birinci sınıf’ öğeler olması, bu yapıların, dil içindeki öteki değerlerle aynı özelliklere ve kabiliyetlere sahip olması demektir. Yani Python’daki sınıflar şu özelliklere sahiptir:

  1. Başka bir fonksiyona veya sınıfa parametre olarak verilebilirler

  2. Bir fonksiyondan döndürülebilirler

  3. Bir değişkene atanabilirler

Yani, bir öğenin ‘birinci sınıf’ olması demek, dil içindeki başka öğelerle yapabildiğiniz her şeyi o öğeyle de yapabilmeniz demektir.

Durumu biraz daha netleştirebilmek için, konu hakkında Guido Van Rossum’un ne dediğine bir bakalım:

Python’a ilişkin hedeflerimden bir tanesi de, bu dili, bütün nesneler “birinci sınıf” olacak şekilde tasarlamaktı. Bununla kastettiğim, dil içinde kendisine bir isim verilebilen bütün nesnelerin (örn. tam sayılar, karakter dizileri, fonksiyonlar, sınıflar, modüller, metotlar, vb.) eşit statüye sahip olmasıdır. Yani, bütün nesnelerin değişkenlere atanabilmesi, listelerin içine yerleştirilebilmesi, sözlükler içinde depolanabilmesi, argüman olarak atanabilmesi vesaire…

kaynak: http://python-history.blogspot.com.tr/2009/02/first-class-everything.html

Gelin bütün bu tanımları somutlaştıran birkaç örnek verelim.

Mesela Deneme() adlı basit bir sınıf tanımlayalım:

class Deneme():
    def __init__(self):
        self.değer = 0
    def metot(self):
        self.metot_değeri = 1

Yukarıdaki tanımlara göre, bu sınıfın birinci sınıf bir nesne olabilmesi için başka bir fonksiyona veya sınıfa parametre olarak atanabilmesi gerekiyor. Bakalım acaba gerçekten öyle mi?

print(Deneme())

Gördüğünüz gibi, gerçekten de sınıfımızı print() fonksiyonuna parametre olarak atayabildik.

Yine yukarıdaki tanıma göre birinci sınıf nesnelerin bir fonksiyondan döndürülebilmesi gerekiyor:

def fonksiyon():
    return Deneme()

print(fonksiyon())

Bu testi de başarıyla geçtik.

Son olarak, bir nesnenin birinci sınıf olabilmesi için bir değişkene atanabilmesi gerekiyor:

değişken = Deneme()

Gördüğünüz gibi, Python için bu da oldukça basit bir görev.

İlk bakışta bu özellikten pek etkilenmemiş olabilirsiniz… Şöyle bir düşününce, aslında çok da önemli bir özellik değilmiş gibi gelebilir bu size. Ancak başka programlama dillerinin;

  • Öğelerin kullanımına ilişkin çeşitli kısıtlamalar koyduğunu,

  • Yani öğeler arasında ayrım yaptığını,

  • Değişkenlerle fonksiyonların ve fonksiyonlarla sınıfların aynı haklara sahip olmadığını,

  • Mesela bir değişkeni veya herhangi bir değeri kullanabildiğiniz her yerde fonksiyon veya sınıf kullanamadığınızı,

  • Yani fonksiyonların ve/veya sınıfların birinci sınıf öğeler olmadığını

gördüğünüzde Python’daki bu esneklik daha bir anlam kazanacaktır.

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>