# # Python Sınıflar

Python nesne yönelimli bir dildir ve gücünü bu sayede katlayarak bizlere ulaşır. Python programlama dilinde neredeyse her şey bir objedir. Peki sınıf ve obje nedir?

Programlama dillerinde **sınıf** ; içerisinde değişken ve fonksiyonları barındıran, yeri geldiğinde bunları kullandıran taslaklardır. Bir örnek vererek bu tanımı güçlendirelim.

En klasik örnek üzerinden yürürsek, bir okulunuz var ve okulda şubelere, her şubede ise onlarca öğrenciye sahipsiniz. Bu elemanlarla bir yazılım hazırlayacak olursak farketmişsinizdir ki şimdiye kadar gördüğümüz dersler bu konuda yetersiz kalacaktır. Çünkü binlerce öğrenci için binlerce değişken oluşturmamız mı gerekecek? Veya her öğrencinin farklı özelliklerini, şube adını, veli bilgilerini vs nerede nasıl tutacağız? Ortada büyük bir kaos var maalesef Elimizde ki problemin kaynağını öğrendik. Büyük verilerle oynarken her üye ve bu üyenin özellikleri için bir değişken oluşturursak binlerce değişkenle işin içinden çıkamayız. İşte tam burada da Python sınıf kullanımı imdadımıza yetişiyor. Eğer elimizde bir sınıfın ve öğrencinin nasıl olacağına dair bir taslak olursa, bu sınıflardan istediğimiz kadar elemanı rahatlıkla oluşturabilir ve sınıfın özelliklerini **üyelere yani objelere (nesnelere)** atayabiliriz. Görselde gördüğünüz üzere basit bir öğrenci taslağı hazırladık. Bu taslakta öğrencilerin alacağı değişken özelliklerini ve sahip olacakları fonksiyonları oluşturduk. Artık bu taslağa bağlı olarak istediğimiz kadar öğrenci yani nesne (obje) oluşturabiliriz.

## 1. Class Oluşturma

Python programlama dilinde bir sınıf oluşturabilmek için **class** terimini kullanırız.

```python
class ogrenci: 
    pass
```

Evet işte bu kadar. En temel haliyle elimizde artık bir sınıfımız var. Şimdi ise tüm öğrencilerde olacak ortak sınıf değişkenlerimizi tanımlayalım.

```python
class ogrenci: 
    isim = None 
    yas = None 
    sinif = None 
    devamsizlik = 0
```

Burada ise sınıf değişkenlerimizi oluşturduk fakat bunları yaparken çoğu değişkene herhangi bir değer atamadık. İsteseydik burada `devamsizlik` değişkeninde olduğu gibi öntanımlı bir değer atayarak bu sınıf değişkenlerinin ilk başta bir değere sahip olmasını sağlayabilirdik. Sırada sınıfımıza fonksiyonel yetenek kazandıracak methodları ekleme var.

```python
class ogrenci: 
    isim = None 
    yas = None
    
    def selamVer():
        print("Merhaba")
```

Artık çok basit bir sınıfımız olduğuna göre ilk testlerimizi yapalım.

## 2. Class Değişken ve Fonksiyonlarını Çalıştırma

İlk başlığımızda basit bir sınıfın nasıl oluşturulduğunu gördük. Nesne ve diğer sınıf yeteneklerine ulaşmadan önce basit yoldan sınıf elemanlarına nasıl erişebileceğimizi görelim. Elimizdeki bir sınıfın değişken ve fonksiyonlarına ulaşmak için sınıf adını yazdıktan sonra ulaşmak istediğimiz elementi seçmemiz gerekiyor.

```python
class ogrenci:
    isim = "-"
    yas = 10

    def selamVer():
        print("Merhaba")

print(ogrenci.isim)    # -
print(ogrenci.yas)     # 10
ogrenci.selamVer()     # Merhaba
```

Örnekte gördüğünüz gibi basit bir sınıfın elemanlarına basit bir yöntemle ulaştık. Şimdi ise sınıf `devamsizlik` değişkenini her çağırdığımızda bir artıran fonksiyonu yazalım.

```python
class ogrenci:
    isim = "-"
    devamsizlik = 0

    def devamsizlikEkle():
        ogrenci.devamsizlik += 1

print(ogrenci.devamsizlik)    # 0

ogrenci.devamsizlikEkle()     # Merhaba

print(ogrenci.devamsizlik)     # 1
```

Buradaki örnekte ise sınıf `devamsizlik` değişkenimizin sınıf dışından nasıl artırılacağını gördük.

Peki bu yöntemleri normal bir fonksiyondan ayıran özellikler nerede? Özellikle de bu taslağı farklı farklı nesneler üzerinde kullanacağımız objeler nerede? Sanki bir şeyler eksik gibi Şimdiye kadar gördüğümüz kısımlar bir sınıfın en temelidir ve iskeletinin nasıl olduğunu anlatır. Fakat bu haliyle sınıfları sınıf gibi kullanamazsınız. Çünkü elimizde halen objeler yok ve yaptığımız her işlem sınıfın kendi değişkenleri üzerinde gerçekleşti. Öyleyse şimdi hemen nesne kullanımını öğrenelim ve asıl sınıf dersine başlayalım.

## 3. Nesne (Obje) Oluşturma

Sınıflar (classes) konusunun kullanılmasının asıl amacı değişken yığınlarıyla uğraşmayıp bir taslak oluşturmak ve bu taslağı tüm değişkenlerimize uygulayabilmektir. Böylece hem bir kargaşadan kurtuluruz hemde daha profesyonel bir kod yazabiliriz. Python programlama dilinde yazdığınız bir sınıftan nesneler oluşturmak için değişkeninize sınıf ismini fonksiyon olarak atamalısınız. Örnekleri hemen görelim.

```python
class ogrenci:
    isim = None
    yas = None

o1 = ogrenci()
o2 = ogrenci()
```

Artık elimizde `ogrenci` sınıfından türemiş iki adet nesnemiz (objemiz) oldu. Öyleyse bu nesnelerimiz için sınıfımızın isim değişkenine değer atamakla başlayalım.

```python
class ogrenci:
    isim = None
    yas = None

o1 = ogrenci()
o2 = ogrenci()

o1.isim = "Yunus"
o2.isim = "İsmet"

print(o1.isim)    # Yunus
print(o2.isim)    # İsmet
```

Nesnelerimizle ilk temasımızı kurduk. Artık istediğiniz sayıda obje oluşturabilirsiniz. Hatta basit bir fonksiyonla her öğrencinin selam vermesini de sağlayabiliriz.

```python
class ogrenci:
    isim = None
    yas = None
    devamsizlik = 0

    def selamVer():
        print("Merhaba !")

o1 = ogrenci()
o2 = ogrenci()

o1.selamVer()    # Merhaba !
o2.selamVer()    # Merhaba !
```

Bu örnekte her öğrencinin kendi ismiyle selam vermesini istersek peki nasıl yapabiliriz?

```python
class ogrenci:
    isim = "---"
    devamsizlik = 0

    def selamVer():
        print("Merhaba, ben " + ogrenci.isim)

o1 = ogrenci()
o1.isim = "Yunus"


ogrenci.selamVer()       # Merhaba, ben ---
o1.selamVer()            # !!!!!! HATA !!!!!!
```

Bu da nedir? Neden böyle bir hata aldık ki? Peki şimdi sınıfımıza devamsızlık değişkenini ve her çağrıldığında öğrenci nesnesinin devamsızlık sayısını bir artıracak fonksiyonu yazsak yine hata alır mıyız acaba?

```python
class ogrenci:
    isim = None
    devamsizlik = 0

    def devamsizlikEkle():
        ogrenci.devamsizlik += 1

o1 = ogrenci()
print(o1.devamsizlik)    # 0

ogrenci.devamsizlikEkle()
print(o1.devamsizlik)    # 1

o1.devamsizlikEkle()     # !!!!!! HATA !!!!!!
```

Sanırım bir terslik var. Objelerimiz sınıf değişkenlerine erişebiliyor ama onları sınıf değişkenleri üzerinden değiştiremiyor Şimdi bunun sebebini yeni başlığımız altında öğrenelim. (Cevabı konu sonunda)

## 5. **init**() Fonksiyonu ve self Terimi Kullanımı

Şu ana kadar sınıfları nasıl oluşturup kullanacağımızı ve bu **sınıflara ait değişkenleri** nasıl tanımlayacağımızı gördük. Bu cümledeki sınıflara ait değişkenler kısmı önemlidir çünkü oluşturduğumuz tüm değişkenler nesnelerimize ait değil, sınıfımıza aittir. Hatırlayalım hemen;

```python
class ogrenci: 
    isim = "-"
```

Bu örnekte **isim** değişkeni **ogrenci** sınıfının bir değişkenidir. Yani bir nesne (obje) oluşturduğunuzda bu nesne üzerinden ilgili değişkene ulaşabilir ve değerini değiştirebilirsiniz fakat yaptığınız değişiklikten tüm nesneler etkilenir.

```python
class ogrenci:
    yas = 10

o1 = ogrenci()
o2 = ogrenci()

ogrenci.yas = 20

print(ogrenci.yas)    # 20
print(o1.yas)         # 20
print(o2.yas)         # 20
```

Örnekte gördüğünüz gibi `o1` ve `o2` olmak üzere iki adet nesne oluşturduk ve sadece sınıf `yas` değişkene ulaşarak değerini değiştirmemize rağmen her iki nesnede de bu değer değişti. **Çünkü yas değişkeni bu nesnelerin kendi değişkeni değil, sınıfın kendi değişkenidir ve nesneler ilgili değişkenlere değer atamadığı sürece tüm değişikliklerden etkilenir.**

{% hint style="warning" %}
Bu şekilde sınıfın kendi değişkenlerini kullanarak objeleri etkili şekilde kullanmayız. Çünkü elimizdeki her objenin *(örneğimizde yer alan öğrencilerin)* kendine has değişken değerleri olmalıdır ve diğer nesnelerden etkilenmemelidir.
{% endhint %}

**init(self)** fonksiyonu Python programlama dilinde bir nesne oluşturulduğunda çalışan özel bir elemandır. Nesne oluşturur oluşturmaz çalışır ve **self** ile içinde oluşturduğumuz değişkenleri nesneye atar. Böylece her nesnenin kendine has değişken değerleri olmuş olur. Bu kısmı örneklerle adım adım anlatalım.

```python
class ogrenci(): 
    yas = 0 
    print("Merhaba")
    
# Merhaba
```

Bu kodu çalıştırır çalıştırmaz ekranda *"Merhaba"* yazısını görürsünüz. Çünkü sınıflar kodlanır kodlanmaz kendi değişkenlerini ve fonksiyonlarını tanımlar. Burada da `ogrenci` sınıfına ait olan `print` komutu çalıştı.

```python
class ogrenci():

    def __init__(self):
        print("Merhaba")
```

Bu kodu çalıştırdığınızda ise ekranda hiçbir çıktı göremezsiniz. Çünkü biraz yukarıda **init(self)** fonksiyonunun sınıf örneklenir örneklenmez, yani sınıftan bir nesne(obje) oluşturduğumuzda çalıştığını öğrendik. Öyleyse kodumuzu şu şekilde düzenlersek ekranda çıktı görebiliriz.

```python
class ogrenci():

    def __init__(self):
        print("Merhaba")

o1 = ogrenci()

# Merhaba
```

Gördüğünüz gibi `o1` isimli nesne oluşturduğumuza ilklendirme fonksiyonumuz çalıştı ve ekrana *"Merhaba"* yazdı. Öyleyse sınıf değişkenlerimize ek olarak nesnelerimize ait olacak değişkenlerimizi de tanımlayalım ve böylece her nesneye özel değişken değeri atayabilelim. Sınıf değişkenleri ise tüm sınıf genelinde değişmeyecek sabit değerler için kullanılabilir. Örneğin pi sayısı gibi.

```python
class ogrenci():

    def __init__(self):
        self.isim = "YOK"
        self.yas = 0

o1 = ogrenci()
o2 = ogrenci()

print(o1.isim + " - " + str(o1.yas))    # YOK - 0
print(o2.isim + " - " + str(o2.yas))    # YOK - 0
```

Burada gördüğünüz gibi nesnemiz oluşturulduğunda `__init__(self)` fonksiyonu objeler oluşturulduğu anda çalıştı ve nesnelerimize ilk değerlerini atadı. **self** değişkeni ise **nesnelerimizi temsil ederek** *(o1 ve o2)* ilgili nesne üzerinde işlem yapılmasını sağladı.

```python
class ogrenci():

    def __init__(self):
        self.isim = None
        self.yas = None

o1 = ogrenci()
o2 = ogrenci()

o1.isim = "Yunus"
o2.isim = "İsmet"

o1.yas = 10
o2.yas = 15

print(o1.isim + " - " + str(o1.yas))    # Yunus - 10
print(o2.isim + " - " + str(o2.yas))    # İsmet - 15
```

Yine bu örnekte olduğu gibi her nesne **self** ile kendi değişkenlerine ulaşarak değer atamalarını gerçekleştirdi.

{% hint style="info" %}
**self** terimi nesneyi temsil eder `(o1.isim)` , `(o2.isim)` gibi doğru nesne üzerinde çalışılmasını sağlar. Siz bu terimi herhangi bir isimle değiştirerek *(örneğin self yerine elma kullanarak)* kullanabilirsiniz ama programlama da **self** kelimesi kalıplaşmıştır.
{% endhint %}

Bu başlığa son bir örnek daha verelim ve sınıf değişkenleri ile nesne değişkenleri arasındaki farkı anlayalım.

```python
class ogrenci():

    yas = 10

    def __init__(self):
        self.yas = 20

o1 = ogrenci()

print(ogrenci.yas)    # 10
print(o1.yas)         # 20
```

Gördüğünüz gibi obje oluşturulduğunda kendi yas değişkenini tanımladı ve sınıf değişkeninden bağımsız olarak kullandı.

```python
class ogrenci():

    def selamVer():
        print("Merhaba")

    def selamVer(self):
        print("Merhaba !!")


o1 = ogrenci()

o1.selamVer()    # Merhaba !!
```

Aynı şekilde `selamVer()` fonksiyonunu nesne üzerinden çalıştırdığımızda kendini temsil eden **self** teriminin olduğu fonksiyonu görerek ilkini değil ikinci fonksiyonu çalıştırdı.

{% hint style="warning" %}
Eğer bir nesne değişkeniyle çalışıyorsanız değişkenin başına **self** terimini getirmeyi unutmayınız.
{% endhint %}

## 6. Ön Tanımlı Değerlerle Obje Oluşturmak

Bir nesneyi isterseniz öntanımlı değerlerle oluşturabilirsiniz. Böylece daha sonrasında tek tek nesne değerleri atamak zorunda kalmazsınız.

```python
class ogrenci:

  def __init__(armut, isim, yas):
    armut.isim = isim
    armut.yas = yas

  def selamVer(armut):
    print("Merhaba, ben " + armut.isim)

o1 = ogrenci("İsmet", 24)
o1.selamVer()
```

{% hint style="info" %}
**self** terimi yerine istediğiniz bir kelime ( **armut** gibi) kullanabilirsiniz demiştik. Fakat siz **self** kullanmakta ısrarcı olun çünkü Python programlama dilinde **self** terimi kalıplaşmıştır.
{% endhint %}

## 7. Nesne ve Obje Değişkeni Silme

Sınıfımızı örneklendirerek istediğimiz kadar nesne oluşturabileceğimizi şimdiye kadar gördük. Peki oluşturduğumuz nesneleri nasıl silebiliriz? Elimizdeki nesneleri **del** terimi ile rahatlıkla silebiliriz.

```python
class ogrenci():
    yas = 10

o1 = ogrenci()

del o1

print(o1.yas)    # !!! HATA !!!
```

Bu örnekte hata alırız çünkü nesnemizi artık mevcut olmadığı için kullanamayız. Aynı şekilde yine **del** terimiyle nesnelerimize ait değişkenleri de silebiliriz.

```python
class ogrenci():

    def __init__(self):
        self.yas = 20

o1 = ogrenci()
o2 = ogrenci()

del o2.yas

print(o1.yas)    # 20
print(o2.yas)    # !!! HATA !!!
```

Burada ise `o2` objesinin `yas` değişkenini sildiğimiz için artık ulaşamıyoruz.

Soru Cevabı Üçüncü başlığımız olan Nesne (Obje) Oluşturma'nın sonundaki sorumuzun cevabına gelmeden önce sorunumuzu bir hatırlayalım: Oluşturduğumuz `sınıf değişkenine` nesneler üzerinden bir değer atamadan direkt `sınıf değişkeni` üzerinden değer atama yaptığımızda tüm `nesnelerdeki değerler` değişiyor ama önce nesne üzerinden sınıf değişkenine değer atadığımızda sınıf değişkeni üzerinden değer atasak bile değer nesnelerde değişmiyor. Sebebi Nedir?

```python
class ogrenci:
    yas = 10

o1 = ogrenci()
o2 = ogrenci()


print(o1.yas)        # 10
print(o2.yas)        # 10
```

Yukarıdaki örnekte objeler `yas` sınıf değişkeninin değerini öntanımlı değerinden alır.

```python
class ogrenci:
    yas = 10

o1 = ogrenci()
o2 = ogrenci()

ogrenci.yas = 20

print(o1.yas)        # 20
print(o2.yas)        # 20
print(ogrenci.yas)   # 20
```

Ama yukarıdaki gibi nesnenin `yas` değişkenine değer atamadan önce sınıf değişkeni üzerinden değer atarsak bu değişiklik tüm nesneleri etkiler.

```python
class ogrenci:
    yas = 10

o1 = ogrenci()
o2 = ogrenci()

o1.yas = 30
ogrenci.yas = 20

print(o1.yas)        # 30
print(o2.yas)        # 20
print(ogrenci.yas)   # 20
```

Fakat yukarıdaki örnekte olduğu gibi önce nesnenin yas değişkenini değiştirirsek artık o nesne için ilgili değer sınıd değişkeni üzerinden değiştirilemez.

Bunun sebebi yas değişkeninin nesnelerin değil sınıfın bir değişkeni olmasıdır. Bu nedenle bu değişken üzerinde yaptığımız bir değişiklik tüm nesneleri etkiler. Fakat siz bir nesne üzerinden bu değere ulaşıp kullandığınızda ilgili değişken ayriyeten objeye has bir değişken olarakta saklanır.

Bu sebeplerden dolayı pi sayısı, firma adı, okul adı gibi değişmeyecek veya değişince herkeste değişmesini istediğiniz değerleri **sınıf değişkeni** olarak tutabilirsiniz. Fakat her nesne için değişecek değerleri **init(self)** fonksiyonu ile oluşturmanızda fayda vardır. Aksi halde bu başlıktaki sorun programınızın düzensiz çalışmasına sebep olabilir.
