[Bölüm 16] Python Dilinde Nesne Yönelimli Programlama (OOP)

Merhaba arkadaşlar, bu yazımda sizlere Pyhon programlama dilindeki nesne yönelimli programlamadan bahsedeceğim. Bana göre Pythonda ki en önemli konu bu konu. Çünkü eğer Pythonda ki nesnelere hakimseniz kod geliştirirken çok da zorlanacağınız düşünmüyorum.

Konuya girmeden önce nesne yönelimli programlamaya neden ihtiyaç duyduğumuzu ve neden çok sık gündemde olduğundan bahsetmek istiyorum.

Nesne yönelimli programlama yazılım geliştiricilerin yazmış oldukları kodların karmaşıklığını azaltmak, okunurluğunu ve anlaşılabilirliğini artırmak ve yazılım geliştiricinin daha verimli kodlar yazmasını sağlayan bir tekniktir. Bu sayede bir yazılımcı nesne ve sınıfları birbiri ile ilşkilendirerek çok uzun ve karmaşık bir kodu daha kısa ve okunur halde belirli bir mantık çerçevesinde yazabilir. Biraz sonra yazacağım örnek kodlarla bu durumu daha iyi izah edeceğim.

Öncelikle nesne nedir biraz bunun üzerinde konuşmak lazım. Bir çok programlama dili hakkında nesneye dayalı veya nesne yönelimli diye duymuşsunuzdur belki. Peki nesne nedir hiç işin detayına indiniz mi?

Nesne Nedir?

Nesne dediğimiz kavram aslına bakarsak her şeydir. Türkçede ki “şey” kelimesinin Pythonda ki karşılığıdır desek yeridir. Bir kalem, silgi, ayakkabı bunlar birer nesnedir ve nesneler sınıflardan ürer. Aslında her nesne bir sınıftır. Sınıflardan bazı özellikler kazanırlar ve bunu duruma göre yeni oluşan nesnelere de miras bırakabilirler. Yine bu detayları ilerleyen başlıklarda daha detaylı anlatacağım.

Aslına bakarsak Python dilinde karşılaştığımız tüm her şey birer nesnedir. Nesneler ise sınıflardan ürerler. Bizim karşımıza çıkan sıradan nesnelerin bile arkasında bir sınıf vardır. Bazen bu sınıfları biz oluştururuz bazen ise Python dilinin kendi sınıflarıdır. Bu sınıflara ise 1. sınıf deriz. Peki ya sınıf nedir?

Sınıf Nedir?

Sınıf dediğimiz kavram ortak özellikleri olan nesnelerin bu ortak özellikler çerçevesinde bir araya gelerek oluşturduğu yapıdır. Örnek vermek gerekirse A,B,C,D, ve E olmak üzere 5 farklı marka araba olsun. Bu arabaların özellikleri birbirinden farklı ama hepsi aynı zamanda birer araba. Bu durumda A,B,C,D ve E arabalarını kapsayan ARABA isimli bir sınıf oluşturabiliriz. Bu örnek sınıf kavramını anlamak için güzel bir örnek oldu ama ilerleyen başlıklarda daha kapsamlı örnekler de yazacağım.

Şimdi elimizde bir elektrik projesi olsun. Bir aydınlatma projesi. Bu projede lambaların ve prizlerin nerelere montajının yapılacağı, kabloların hangi yollardan geçeceği ve panonun nerede olacağı gibi bazı detaylar olsun. Şimdi bu durumda örnek vermek gerekirse bizim elektrik projemiz bir sınıftır. Prizlerin, kabloların, panonun ve lambaların nasıl montajının yapılacağı ise bizim sınıfımızın içerisinde tanımlanmıştır. Şimdi biz bu planı kullanarak aynı elektrik projesini bir sürü daireye uygulayabiliriz ve uyguladığımız dairelerin elektrik projeleri birbirinin tıpatıp aynısı olur. Sınıflarda da durum aynıdır. Bir sınıf tanımlandığında o sınıfı kullanarak birbirinin aynısı bir çok nesne ortay çıkarabiliriz. Tabi bu aynı olma koşuluna bağlı kalacağız diye bir kuralımız yok elbette. İlerki başlıklarda bahsedeceğim yollarla sınıflardaki bu birebir aynı olma durumunu ortadan rahatlıkla kaldırabiliriz.

Yani özet geçmek gerekirse dairelerin hazırlanan elektrik projeleri birer nesne, o projenin nasıl oluşturulduğunu kapsayan kurallar bütünü ise bir sınıftır.

Şimdi çok basit bir nesne örneği göstermek istiyorum.

etk = 136
print("etk:", etk) #etk nesnesini ekrana yazdırdım
print("-"*40)
print("etk nesnesinin veri tipi:", type(etk)) #etk nesnesinin veri tipini ekrana yazdırdım
print("-"*40)
print(dir(etk)) # etk nesnesinin özelliklerini dir fonksiyonu ile ekrana yazdırdım

Çıktı:

etk: 136
----------------------------------------
etk nesnesinin veri tipi: <class 'int'>
----------------------------------------
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__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']

Dikkat ettiyseniz arkadaşlar hemen üstteki örnek kodda ki dir(etk) kodunun çıktısındaki metod ve özelliklerin neredeyse hepsinde _ (alt tire) ifadesi var. Bazılarında çift bazılarında tek var ama bazılarında ise hiç yok. Peki nedeni ne?

  • Tek alt tire (_) ifadesinin olduğu isimler ise sınıf dışarısında kullanılmaması gerekmektedir. Bu tarz özelliklere korunmuş (protected) özellikler deriz.
  • Çift alt tire(__) ifadesinin yer aldığı isimler de ise özel bir durum söz konusudur. Bu isimler sınıf dışarısında kullanılamazlar. Erişime ve görünmeye kapalıdırlar. Bu tarz özelliklere özel (private) özellikler deriz.
  • Alt tire ifadesinin olmadığı yani alfabetik bir karakter ile başlayan isimler sınıf içerisinde ve dışarısında kullanılabilirler. Yani genel erişime açıktırlar diyebiliriz.

Şimdi hemen aşağıda yeni bir sınıf oluşturduğumuz zaman Python tarafından otomatik olarak oluşturulan metodları ve bu metodların işlevlerini bir tablo olarak veriyorum. Bu metodlar sınıf tanımlarken çok işimize yarayacak.

Nesne Metodu İşlevi
__new__( ) Sınıf argümanları ilk olarak buraya aktarıldıktan sonra nesne __init__ metodu ile başlatılır.
__init__( ) Bir nesne bir sınıfa atandıktan sonra ilk olarak çağrılır. Sınıf tanımlanırken de genelde ilk yazılır.
__str__( ) Sınıfın string karşılığıdır.
__repr__( ) String tanıtımıdır.
__bytes__( ) Byte array karşılığıdır.
__format__(format açıklaması) Format açıklamalarıdır.
__del__( ) Nesneyi siler. Pek kullanılmaz.

Hemen aşağıda örnek bir sınıf oluşturalım ve üzerinde bazın metodları uygulamaya başlayalım.

class muhendis():
    def __init__(self,isim,soyisim,yaş,bölüm,deneyim,maaş,diller):
        self.isim = isim
        self.soyisim = soyisim
        self.yas = yas
        self.bolum = bolum
        self.deneyim = deneyim
        self.maas = maas
        self.diller = diller
        
    #yukarıda __init__ metodunu kullandık.
    
    def bilgiler(self):
        print("""
        Mühendis bilgileri aşağıdaki gibidir.
        
        İsim: {}
        
        Soyisim: {}
        
        Yaş: {}
        
        Bölüm: {}
        
        Deneyim: {}
        
        Maaş: {}
        
        Bildiği Diller: {}
        """.format(self.isim,self.soyisim,self.yas,self.bolum,self.deneyim,self.maas,self.diller))
    
    #yukarıda bilgiler isimli bir metod tanımladım
        
    def maas_arttir(self,zam):
        self.maas += zam
        print("Maaş eklendi.")
        
    #yukarıda maas_arttir isimli bir metod tanımladım
        
    def dil_ekle(self,yeni_dil):
            self.diller.append(yeni_dil)
            print("Yeni dil eklendi.")   
        
    #yukarıda dil_ekle isimli bir metod tanımladım
HCD = muhendis("Hamit Can","Dinç",21,"Elektrik Mühendisliği",5,3000,["Türkçe","İngilizce"])
HCD.bilgiler()

Çıktı:

Mühendis bilgileri aşağıdaki gibidir.
        
        İsim: Hamit Can
        
        Soyisim: Dinç
        
        Yaş: 21
        
        Bölüm: Elektrik Mühendisliği
        
        Deneyim: 5
        
        Maaş: 3000
        
        Bildiği Diller: ['Türkçe', 'İngilizce']
HCD.maas_arttir(500)

Çıktı:

Maaş eklendi.
HCD.bilgiler()

Çıktı:

Mühendis bilgileri aşağıdaki gibidir.
        
        İsim: Hamit Can
        
        Soyisim: Dinç
        
        Yaş: 21
        
        Bölüm: Elektrik Mühendisliği
        
        Deneyim: 5
        
        Maaş: 3500
        
        Bildiği Diller: ['Türkçe', 'İngilizce']
HCD.dil_ekle("Almanca")
Yeni dil eklendi.
HCD.bilgiler()

Çıktı:

Mühendis bilgileri aşağıdaki gibidir.
        
        İsim: Hamit Can
        
        Soyisim: Dinç
        
        Yaş: 21
        
        Bölüm: Elektrik Mühendisliği
        
        Deneyim: 5
        
        Maaş: 3500
        
        Bildiği Diller: ['Türkçe', 'İngilizce', 'Almanca']

Yukarıda güzel ve açıklayıcı bir örnek verdim. Bu örnek sayesinde nesne yönelimli programlamanın genel çalışma mantığını Pythonda yavaş yavaş anlamış olduk. Aslına bakarsanız sınıfın içerisinde metod tanımlamanın öyle çok da zor bir yanı yokmuş. Bildiğimiz fonksiyon tanımlanın aynısı. Zaten fonksiyonlarda aslında “main” sınıfının içerisine tanımlanırlar. Bu ek bilgiyi de burada söylemiş olalım. 🙂

Sınıfların içerisinde 2 tane değişken tip vardır. Bunlar sınıf değişklenleri ve nesne değişkenleridir. Sınıf değişkenleri aynı birer sınıf metodu gibi davranırlar ve her yerden erişime açıktırlar. Nesne değişkenleri ise sadece nesne içinde erişilebilirlerdir çünkü nesne içerisinde tanımlanırlar.

Tanımlamış olduğumuz sınıfın içerisinde hem değişken hem de metodları bir arada kullanırsak, barındırırsak kapsülleme (encapsulation) yapmış oluruz.

Nesnelerde Miras – Kalıtım (Inheritance)

Konu başlığından da anlaşılacağı üzere sınıflar üzerinde bir miras bırakacağız. Bu olayın mantığı daha önce bir sınıf tanımlamış ve içerisine metodlar, değişkenler eklemişsek aynılarını bir daha eklememektir. Bu sayede kodlarımızı daha da kısaltmış oluyoruz.

Hemen aşağıda örnek bir kod yazarak durumu açıkladım.

class sinif1():
    def __init__(self,a1,b1,c1):
        print("Şu anda sinif1' in __init__ fonksiyonu çalışmaktadır.")
        self.a1 = a1
        self.b1 = b1
        self.c1 = c1
        
class sinif2(sinif1): #sinif1 den miras alındı
    def __init__(self,a1,b1,c1,a2,b2,c2): #tüm parametreler tanımlandı
        sinif1.__init__(self,a1,b1,c1) #sinif1 in örneği
        self.a2 = a2
        self.b2 = b2
        self.c2 = c2
örnek = sinif2(1,2,3,4,5,6)

Çıktı:

Şu anda sinif1' in __init__ fonksiyonu çalışmaktadır.

Yukarıda da görüldüğü üzere print fonksiyonu ile ekrana yazdırdığımız çıktı sınıf1 adlı sınıfımızda tanımlanmış bir fonksiyondu. Biz kalıtım yaptıktan sonra sınıf2 üzerinde bu fonksiyonu kullanmış olduk.

Çoklu Miras – Kalıtım (Multiple Inheritance)

Çoklu kalıtım yaparken ise bir değil birden fazla sınıfı bir başka sınıfa miras olarak bırakıyoruz. Geriye kalan her şey ise aynı. Hemen aşağıdaki örnekte nasıl yapıldığını anlattım.

class sinif1():
    def __init__(self,a1,b1,c1):
        print("Şu anda sinif1' in __init__ fonksiyonu çalışmaktadır.")
        self.a1 = a1
        self.b1 = b1
        self.c1 = c1
        
class sinif2(): 
    def __init__(self,a2,b2,c2): 
        print("Şu anda sinif2' nin __init__ fonksiyonu çalışmaktadır.")
        self.a2 = a2
        self.b2 = b2
        self.c2 = c2
        
class sinif3(sinif1,sinif2):
    def __init__(self,a1,b1,c1,a2,b2,c2,a3,b3,c3):
        sinif1.__init__(self,a1,b1,c1)
        sinif2.__init__(self,a2,b2,c2)
        self.a3 = a3
        self.b3 = b3
        self.c3 = c3
etk = sinif3(1,2,3,4,5,6,7,8,9)

Çıktı:

Şu anda sinif1' in __init__ fonksiyonu çalışmaktadır.
Şu anda sinif2' nin __init__ fonksiyonu çalışmaktadır.
class sinif1():
    def __init__(self,a1,b1,c1):
        print("Şu anda sinif1' in __init__ fonksiyonu çalışmaktadır.")
        self.a1 = a1
        self.b1 = b1
        self.c1 = c1
        
class sinif2(): 
    def __init__(self,a2,b2,c2): 
        print("Şu anda sinif2' nin __init__ fonksiyonu çalışmaktadır.")
        self.a2 = a2
        self.b2 = b2
        self.c2 = c2
    
    def degis(self,b1,b2):
        self.b1 = "Y"
        self.b2 = "Y"
        
class sinif3(sinif1,sinif2):
    def __init__(self,a1,b1,c1,a2,b2,c2,a3,b3,c3):
        sinif1.__init__(self,a1,b1,c1)
        sinif2.__init__(self,a2,b2,c2)
        self.a3 = a3
        self.b3 = b3
        self.c3 = c3
        
    def degis_a(self,a1,a2):
        self.a1 = "X"
        self.a2 = "X"
        
    def bilgiler(self):
        print("Sırasıyla tüm elemanlar: {}, {}, {}, {}, {}, {}, {}, {}, {}".format(self.a1,self.b1,self.c1,self.a2,self.b2,self.c2,self.a3,self.b3,self.c3))

Şimdi aşağıdaki kod ile yukarı degis_a metodunu uygulayalım.

elektrik = sinif3(1,2,3,4,5,6,7,8,9)
elektrik.bilgiler()
elektrik.degis_a(1,2)
elektrik.bilgiler()

Çıktı:

Şu anda sinif1' in __init__ fonksiyonu çalışmaktadır.
Şu anda sinif2' nin __init__ fonksiyonu çalışmaktadır.
Sırasıyla tüm elemanlar: 1, 2, 3, 4, 5, 6, 7, 8, 9
Sırasıyla tüm elemanlar: X, 2, 3, X, 5, 6, 7, 8, 9