Bu bölümde kendi nesne türlerimizi tanımlayacağız. C’de struct ile birkaç alanı tek bir ad altında topluyordunuz. Sınıf (class) da alanları bir araya getirir, ama yanlarına o alanlarla çalışan fonksiyonları da koyar. Alanların o anki değerleri nesnenin durumudur, fonksiyonların yaptığı işler de davranışıdır. Sınıftan oluşturulan her örnek (instance) kendi durumunu taşır.
Bu bölümün kapsamı
NoteSınavda sorulur
Sınıf ile örnek ayrımı ve class ile tür tanımlama
__init__ içinde örnek niteliklerini oluşturma
self parametresinin, metodu çağrılan nesne olması
Aynı sınıftan oluşturulan nesnelerin niteliklerinin birbirinden bağımsız olması
Sınıf niteliği ile örnek niteliği; gölgeleme
Paylaşılan mutable sınıf niteliğinin riski
Niteliği doğrudan atamak yerine bir metotla değiştirmenin gerekçesi
NoteSınav dışı (bilgi amaçlı)
@classmethod ve @staticmethod
__new__ ve nesne oluşturma ayrıntısı
__slots__
Bu başlıklar konunun devamıdır; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.
Sözlükle taşıdığımız kaydı sınıfa çevirmek ne kazandırır?
Geçen bölümde ürünü bir sözlükte tuttuk ve iki şeyi izlemenin zorlaştığını gördük: sözlükte hangi anahtarların bulunması gerektiğini ve sözlüğü hangi fonksiyonların değiştirdiğini. Sınıf ikisini bir araya getirir. Veri ve onu değiştiren fonksiyonlar aynı tanımın içinde durur.
İlk sınıfımız
BankAccount sınıftır; account ise bu sınıftan oluşturulmuş bir nesneye referanstır. self.owner ve self.balance nesneye bağlı değişkenlerdir. Bunlara nitelik (attribute) denir; C’deki struct alanlarına benzerler. __init__, yeni nesne oluşturulunca kendiliğinden çalışır ve niteliklere ilk değerlerini verir.
Python’da nesne önce oluşturulur, sonra başlatılır. Oluşturma adımı __new__ metodunda olur ve bu derste ayrıntısına girmiyoruz. __init__ ikinci adımdır: hazır gelen nesneyi alır ve niteliklerine ilk değerlerini verir.
init ve self akışı
self nedir?
self, metodu hangi nesne için çağırdıysanız o nesnedir. first oluşturulurken çalışan __init__ içinde self → first, second oluşturulurken self → second olur. Bu yüzden self.owner = owner ataması her seferinde başka bir nesnenin niteliğini doldurur.
C’de bir struct’la çalışan fonksiyona hangi struct’la çalışacağını parametreyle söylerdiniz. self o parametrenin Python’daki karşılığıdır. Farkı, çağrıda onu sizin yazmamanızdır; Python nesneyi kendisi gönderir.
self ve metot çağrısı
Davranışı sınıfa eklemek
account.deposit(50) çağrısını BankAccount.deposit(account, 50) gibi düşünebilirsiniz. Kodda ilk biçimi yazarız; ikincisi self’in nereden geldiğini anlamaya yardım eder.
Metot yalnızca “sınıf içindeki fonksiyon” değildir
Metot sınıfın içine yazılmış bir fonksiyondur. Bu tanım doğru, ama bir şeyi atlıyor: fonksiyonu hangi sınıfa yazdığınız, o işin kime ait olduğunu da söyler.
Metot, bir nesnenin kendi verisiyle yaptığı iştir.
deposit() metodunu BankAccount içine koymak, bakiyeyi değiştirmenin hesabın işi olduğunu söyler. Bir işin hangi sınıfa ait olduğuna o sınıfın sorumluluğu denir.
Aynı sınıftan bağımsız nesneler
first iki kez, second bir kez artırıldı ve iki sayaç farklı değer gösterdi: her nesnenin kendi value niteliği vardır. first is second satırı da False verir, çünkü bunlar iki ayrı nesnedir.
Sınıf niteliği ve örnek niteliği
name, __init__ içinde self.name ile atandığı için her öğrencinin kendisine aittir. Buna örnek niteliği denir. school ise sınıfın gövdesinde, metotların dışında tanımlanmıştır. Buna da sınıf niteliği denir. ada.school yazdığınızda Python önce ada nesnesinde school adlı bir nitelik arar. Bulamazsa sınıfa bakar ve oradaki değeri verir.
Sınıf ve örnek niteliği
Nitelik gölgeleme (shadowing)
Şu atama sınıf niteliğini değiştirmez:
ada.school = ... yalnızca ada nesnesinde aynı adlı bir örnek niteliği oluşturur. Artık ada.school okunurken Python bu niteliği bulur ve sınıfa hiç bakmaz. Buna gölgeleme (shadowing) denir: nesnedeki nitelik, sınıftaki aynı adlı değeri o nesne için örter. Student.school ve ece.school değişmez. Sınıftaki değeri gerçekten değiştirmek istiyorsanız bunu açıkça Student.school = ... ile yazın.
Paylaşılan mutable sınıf niteliği
members sınıf niteliği olduğu için iki nesne de aynı listeyi görür. Her takımın kendi üye listesi olsun istiyorsak listeyi __init__ içinde oluştururuz:
class Team:def__init__(self, name):self.name = nameself.members = []
WarningMutable sınıf niteliğine dikkat
Bütün örneklerin gerçekten aynı veriyi görmesi gerekiyorsa sınıf niteliği kullanılabilir. Her nesnenin kendi listesi, sözlüğü veya kümesi gerekiyorsa mutable nesneyi __init__ içinde oluşturun.
Yazım hatası bazen sessizce yeni nitelik oluşturur
C’de bir struct’ın alanları tanımda sabittir; olmayan bir alana değer atarsanız derleyici hata verir. Python’da öyle değildir: program çalışırken çoğu nesneye yeni nitelik eklenebilir.
class Account:def__init__(self):self.balance =100def reset(self):self.blance =0# balance yerine yanlışlıkla blance
self.blance = 0 satırı çoğu durumda hemen hata vermez; yanlış adla yeni bir nitelik oluşturur. Hatayı ancak sonra, balance hâlâ 100 çıktığında fark edersiniz. Bu bir mantık hatasıdır.
WarningNitelik adlarını tutarlı kullanın
Nitelik adını yazarken editörün önerdiği tamamlamayı kullanın ve uyarılarına bakın. Nitelikleri kodun oraya buraya dağılmış yerlerinde oluşturmayın. Nesnede bulunması gereken bütün nitelikleri __init__ içinde açıkça atamak bu hataları azaltır.
Tanımanız yeterli: @classmethod ve @staticmethod
Bu hafta yazacağınız metotlar örnek metotlarıdır: ilk parametreleri self olur ve account.deposit(50) gibi bir nesneyle çağrılırlar. İnternetteki örneklerde metodun üstüne @ ile yazılmış iki işarete sık rastlayacaksınız. Bu işaretlere dekoratör (decorator) denir:
class User:@classmethoddef from_text(cls, text): ...@staticmethoddef is_valid_name(name): ...
@classmethod ilk parametre olarak nesneyi değil, sınıfın kendisini (cls) alır. Nesneyi başka bir biçimden, örneğin bir metin satırından oluşturan metotlarda kullanılabilir.
@staticmethod ne self ne cls alır. Nesnenin verisini kullanmayan, ama sınıfla ilgili olduğu için sınıfın içine yazılan yardımcı fonksiyonlarda kullanılabilir.
Bu hafta bu ikisini kullanmanız beklenmiyor. Önce nesnenin niteliklerini kullanan örnek metotlarını doğru yazmayı öğreneceğiz.
Sorumluluğu doğru yere koymak
# A: dışarıdan doğrudan güncellemeaccount.balance -=100
# B: davranış üzerinden güncellemeaccount.withdraw(100)
A’da bakiyeyi dışarıdaki kod kendisi azaltır. Bakiye tutara yetiyor mu, bunu denetlemek bakiyeyi değiştiren her kod parçasının işi olur. B’de para çekmenin kuralları withdraw metodunda, yani hesabın içinde durur: para çekmek hesabın sorumluluğudur. Gelecek hafta bu kuralları, örneğin bakiyenin eksiye düşmemesini, withdraw içine yazacağız.
Alıştırma: Sınıfı tamamla
self.price *= (1- rate)
Alıştırma: self hatasını bul
Lamp().turn_on() çağrısında ne olur? Metot tanımındaki eksikliği düzeltin.
Alıştırma: Gölgelemeyi gözlemle
Bir Student sınıfında school sınıf niteliği oluşturun. İki öğrenci nesnesi oluşturun; yalnızca ilk öğrenciye student.school = "X" atayın. Üç değeri (first.school, second.school, Student.school) yazdırıp sonucu açıklayın.
Sıra sizde: Book sınıfı
Bir Book sınıfı oluşturun:
title
author
is_borrowed
Başlangıçta is_borrowed = False olsun. borrow() ve return_book() metotlarını ekleyin. İki ayrı kitap nesnesi oluşturun ve birini ödünç almanın diğerini değiştirmediğini gösterin.
Şimdilik aynı kitabın iki kez ödünç alınmasını engellemiyoruz. Bu tür geçersiz durumları gelecek hafta engelleyeceğiz.
Çalışır kod görevi: En az iki Book nesnesi oluşturun; borrow() ve return_book() çağrılarından sonra is_borrowed değerlerini assert ile kontrol edin. Ayrıca bir örnek niteliğinin sınıf niteliğini gölgelediğini gösteren en az bir kısa deney ekleyin.
Sınav provası
Önce kendi cevabınızı seçin; sonra cevap anahtarında her şıkkın neden doğru veya yanlış olduğunu okuyun. Çeldiriciler uydurma değil, bu konuda gerçekten yapılan hatalardır. Sınav maddeleri de bu mantıkla yazılır.
Madde 1. Sınıf ile nesne (örnek) arasındaki fark nedir?
A. Sınıf yalnızca veriyi, nesne yalnızca davranışı tutar
B. Nesne sınıfın kopyasıdır; sınıf değişirse nesne değişmez
C. Her sınıftan yalnızca bir nesne oluşturulabilir
D. Sınıf program çalışırken, nesne kod yazılırken var olur
E. Sınıf tanımı verir; nesne o tanımdan oluşturulan örnektir
Madde 2.__init__ metodu ne yapar?
A. Nesne silinirken çalışır ve kaynakları serbest bırakır
B. Örneğin metinsel temsilini döndürür
C. Nesneyi yoktan yaratır ve belleği ayırır
D. Yeni oluşturulan örneğe ilk nitelik değerlerini verir
E. Sınıf niteliklerini tanımlar
Madde 3. Aşağıdaki sınıfta reset() çağrıldıktan sonra balance neden hâlâ 100’dür?
class Account:def__init__(self):self.balance =100def reset(self):self.blance =0
A.reset() metodu self almadığı için çalışmaz
B.self.blance yeni bir nitelik oluşturur ve balance değişmez
C.balance__init__ içinde tanımlandığı için salt okunurdur
D.AttributeError yükselir ve atama yapılmaz
E. Metot içinde nitelik değiştirmek için global bildirimi gerekir
Madde 4. Bir metot içindeki self neyi temsil eder?
A. Metodun çağrıldığı nesneyi
B. Metodun dönüş değerini
C. Son oluşturulan örneği
D. Sınıfın kendisini
E. Python’ın ayrılmış bir anahtar sözcüğünü
Madde 5. Aşağıdaki sınıfta Lamp().turn_on() çağrısı ne yapar?
class Lamp:def__init__(self):self.is_on =Falsedef turn_on():self.is_on =True
A.TypeError yükselir; metotta örneği alacak parametre yoktur
B.NameError yükselir; self tanımsızdır
C. Metot tanımı söz dizimi hatası verir
D. Sorunsuz çalışır; self metot içinde kendiliğinden tanımlıdır
E.is_on değerini True yapar ama başka örnekleri etkilemez
Madde 6. Aşağıdaki program ne yazdırır?
class Counter:def__init__(self):self.value =0def increment(self):self.value +=1first = Counter()second = Counter()first.increment()first.increment()second.increment()print(first.value, second.value)
A.3 3
B.1 1
C.2 2
D.0 0
E.2 1
Madde 7. Aşağıdaki program ne yazdırır?
class Student: school ="Samsun MYO"def__init__(self, name):self.name = nameada = Student("Ada")ece = Student("Ece")ada.school ="Başka MYO"print(ada.school, ece.school, Student.school)
A.AttributeError; sınıf niteliği örnek üzerinden değiştirilemez
B.Başka MYO Samsun MYO Samsun MYO
C.Başka MYO Başka MYO Samsun MYO
D. Üç değer de Samsun MYO
E. Üç değer de Başka MYO
Madde 8. Sınıf düzeyindeki bir değeri gerçekten değiştirmek isteyen kod ne yazmalıdır?
A.global school bildirip atamak
B.Student.school = "Yeni MYO"; değişiklik sınıfın adıyla yazılır
C.Student().school = "Yeni MYO"
D.ada.school = "Yeni MYO"
E.self.school = "Yeni MYO" (herhangi bir metot içinde)
Madde 9. Aşağıdaki program ne yazdırır?
class Team: members = []def__init__(self, name):self.name = namefirst = Team("A")second = Team("B")first.members.append("Ada")print(second.members)
A.['Ada', 'Ada']
B.[]; her örneğin kendi listesi vardır
C.None
D.['Ada']; members sınıf niteliğidir ve bütün örnekler aynı listeyi görür
E.AttributeError; second için members tanımlı değildir
Madde 10. Her takımın bağımsız bir üye listesine sahip olması için sınıf nasıl yazılmalıdır?
A. Her metodun başında self.members = self.members.copy() çağrılmalıdır
B. Her örnek için ayrı bir sınıf tanımlanmalıdır
C. Liste __init__ içinde self.members = [] ile oluşturulmalıdır
D.members demet yapılmalıdır
E. Sınıf gövdesindeki members = [] satırı members = list() yapılmalıdır
Madde 11.account.balance -= 100 yerine account.withdraw(100) yazmanın tasarım gerekçesi nedir?
A. Python’da nitelikler dışarıdan değiştirilemez
B. Doğrudan atama söz dizimi hatası verir
C. Para çekme kuralı tek yerde, hesabın metodunda durur
D. Metot çağrısı doğrudan atamadan daha hızlı çalışır
E. Metot kullanmak nitelik adını gizler
TipCevaplar ve gerekçeler
Madde 1. Doğru: E. Sınıf, nesnelerin hangi nitelikleri ve metotları olacağını tanımlar. Nesne bu tanımdan oluşturulur ve kendi nitelik değerlerini taşır.
A yanlış: Nitelik ile metot ayrımı, sınıf ile nesne ayrımına yanlış eşlenebilir. Oysa sınıf hem nesnenin hangi nitelikleri taşıyacağını hem metotlarını tanımlar.
B yanlış: Nesnenin sınıftan “basıldığı” benzetmesi kopya fikrini doğurur. Ama örnek sınıfın kopyası değildir; metotlarını sınıfta bulur.
C yanlış: İlk örneklerde her sınıftan tek nesne oluşturulduğu için böyle bir sınır sanılabilir. Oysa bir sınıftan istediğiniz kadar nesne oluşturabilirsiniz.
D yanlış: İkisinin farklı zamanlarda var olduğu düşüncesi C’deki derleme anı ile çalışma anı ayrımından gelir. Oysa ikisi de program çalışırken vardır; class satırı da çalıştırılan bir deyimdir.
Madde 2. Doğru: D. Python’da nesne önce oluşturulur, sonra başlatılır. __init__ ikinci adımdır; self ile örneğin niteliklerine ilk değerlerini verir.
A yanlış: Nesnenin ömrünün iki ucu, başlangıç ve bitiş, kolayca karıştırılır. Bu, yıkıcının (destructor) davranışıdır; __init__ ise nesne oluşturulduğunda çalışır.
B yanlış: Çift alt çizgili metotların adları birbirine benzediği için görevleri karışabilir. Bu __str__ ve __repr__ metotlarının işidir.
C yanlış: “Yapıcı” adı nesneyi bu metodun kurduğu izlenimini verir. Ama nesneyi oluşturmak ayrı bir adımdır; __init__ hazır gelen nesneyi başlatır.
E yanlış:__init__ sınıfın içinde yazıldığı için sınıf düzeyinde iş yaptığı sanılabilir. Ama sınıf nitelikleri sınıf gövdesinde tanımlanır; __init__ örneğin niteliklerini kurar.
Madde 3. Doğru: B. Python program çalışırken nesneye yeni nitelik eklenmesine izin verir. Bu yüzden yanlış yazılmış ad hiçbir uyarı vermeden yeni bir nitelik olur; hata ancak sonuç yanlış çıkınca fark edilir.
A yanlış: Metotlarda self unutmak sık görülen bir hata olduğu için ilk akla gelen budur. Ama self parametresi tanımda vardır.
C yanlış: Başlangıç değerinin __init__ içinde sabitlendiği düşünülebilir. Oysa __init__ içinde kurulan nitelikler sonradan değiştirilebilir.
D yanlış: Yanlış yazılmış bir adın hata vermesi beklenir. Ama AttributeError olmayan bir niteliği okurken çıkar; olmayan bir niteliğe değer atamak ise yeni nitelik oluşturur.
E yanlış: Fonksiyon içinden dış bir adı değiştirmek için global gerektiği bilgisi buraya taşınabilir. Ama global modül düzeyindeki adlar içindir; nitelikler self üzerinden yazılır.
Madde 4. Doğru: A.first.deposit(50) çağrısında self → first; second.deposit(50) çağrısında self → second olur.
B yanlış: Dönüş değeri return ile verilir.
C yanlış:self en son oluşturulan nesne değil, metodu çağrılan nesnedir.
D yanlış: Sınıfı temsil eden parametre clstir ve @classmethod ile kullanılır.
E yanlış:self anahtar sözcük değil, geleneksel bir parametre adıdır.
Madde 5. Doğru: A. Python örneği ilk argüman olarak gönderir ama metodun hiç parametresi yoktur. Lamp().turn_on() çağrısı Lamp.turn_on(ornek) gibi çalışır; tanımda self olmadığı için argüman sayısı tutmaz.
B yanlış: Gövdede tanımsız bir self kullanıldığı için bu hata akla yatkındır. Ama hata, gövdeye hiç girilmeden, argümanlar parametrelerle eşleştirilirken çıkar.
C yanlış: Parametresiz bir metot tanımı yanlış göründüğü için hatanın tanımda çıkacağı düşünülebilir. Ama tanım geçerlidir; sorun çağrı anında ortaya çıkar.
D yanlış:self her metotta kullanıldığı için dilin ayırdığı bir ad gibi görünür. Ama self sihirli bir ad değildir; parametre olarak yazılmalıdır.
E yanlış: Metodun adı ve gövdesi niyeti açıkça gösterdiği için çalışacağı sanılabilir. Ama gövde hiç çalışmaz.
Madde 6. Doğru: E.value her örnekte __init__ içinde ayrı ayrı kurulur; sayaçlar bağımsızdır.
A yanlış: Bu, value sınıf niteliği olsaydı ve paylaşılsaydı beklenirdi.
B yanlış:first iki kez artırılmıştır.
C yanlış:second yalnızca bir kez artırılmıştır.
D yanlış:increment çağrıları durumu gerçekten değiştirir.
Madde 7. Doğru: B.ada.school = ... yalnızca ada örneğinde aynı adlı bir nitelik oluşturur ve sınıftaki değeri o nesne için gölgeler. Sınıf ve diğer örnek etkilenmez.
A yanlış: Atama hata vermez; yeni bir örnek niteliği oluşturur.
C yanlış:ece kendisinde böyle bir nitelik olmadığı için sınıftaki değeri görür.
D yanlış:ada.school okunurken ada nesnesine atanan yeni nitelik bulunur.
E yanlış: Bunun için Student.school = ... yazılması gerekirdi.
Madde 8. Doğru: B. Bir nesneye yapılan atama yalnız o nesneyi etkiler; sınıf niteliği sınıfın adıyla değiştirilir.
A yanlış:global modül düzeyindeki adlar içindir; sınıf niteliğine erişmez.
C yanlış: Geçici bir örnek oluşturur ve yalnız onu etkiler.
D yanlış: Yalnız ada örneğinde gölgeleyen bir nitelik oluşur.
E yanlış:self.school ataması da o nesnede bir örnek niteliği oluşturur.
Madde 9. Doğru: D.first.members.append(...) yeni bir nitelik oluşturmaz; sınıftaki listeyi yerinde değiştirir. first.members is second.members sonucu Truedur.
A yanlış: Tek bir append çağrısı vardır.
B yanlış: Bu ancak liste __init__ içinde oluşturulsaydı doğru olurdu.
C yanlış:members bir listedir; append onu None yapmaz.
E yanlış: Örnekte olmayan ad sınıfta aranır ve bulunur.
Madde 10. Doğru: C. Böylece her yeni takım için ayrı bir liste oluşturulur ve takımlar aynı listeyi paylaşmaz.
A yanlış: Karmaşık ve kırılgandır; sorun listenin nerede oluşturulduğudur.
B yanlış: Sınıf, aynı tanımdan çok sayıda nesne oluşturmak için yazılır.
D yanlış: Demete üye eklenemez; üstelik istenen, her takımın kendi listesinin olmasıdır.
E yanlış:list() de tek bir liste oluşturur; satır sınıf gövdesinde olduğu için yine yalnız bir kez çalışır.
Madde 11. Doğru: C. Bakiyeyi doğrudan değiştiren her kod parçası kuralı kendisi yeniden yazmak zorunda kalır. Metot ise kuralı nesnenin içinde, tek yerde tutar; kontrol orada yapılır.
A yanlış: Metot kullanma önerisi, dilin doğrudan atamayı yasakladığı izlenimini verir. Oysa nitelikler değiştirilebilir; dil engel olmadığı için bu kararı tasarımda vermek gerekir.
B yanlış: Önerilmeyen bir yazımın geçersiz olduğu düşünülebilir. Ama söz dizimi doğrudur; sorun tasarımdadır.
D yanlış: Metot çağrısı daha düzenli göründüğü için daha verimli sanılabilir. Ama hız farkı konu değildir; gerekçe, kuralın hangi nesnede durduğudur.
E yanlış: Nitelik adı artık çağrıda görünmediği için gizlenmiş gibi durur. Ama ad gizlenmez; değişiklik bu kez metodun içinden yapılır.
Tek sayfa özet
Sınıf bir tür tanımı; nesne o sınıfın çalışma anındaki örneğidir.
self ile metodun çağrıldığı nesneye erişiriz.
__init__ yeni nesnenin niteliklerine ilk değerlerini verir.
Örnek niteliği o nesneye aittir; sınıf niteliğini bütün örnekler ortak görür.
Bir nesneye sınıf niteliğiyle aynı adlı bir nitelik atamak, o nesne için sınıftaki değeri gölgeler.
Her nesnenin kendi mutable koleksiyonu gerekiyorsa koleksiyonu __init__ içinde oluşturun.
Python program çalışırken nesneye yeni nitelik eklenmesine izin verir. Bu yüzden yanlış yazılmış bir nitelik adı hata vermeden yeni bir nitelik oluşturabilir.
Bir metodu hangi sınıfa yazacağınıza karar verirken şunu sorun: bu iş hangi nesnenin sorumluluğu?
Bu bölümün kazanımları
Bu bölümü bitiren öğrenci:
Sınıf ile örnek ayrımını açıklar ve class ile kendi türünü tanımlar.
__init__ içinde örnek niteliklerini oluşturur.
self parametresinin, metodu çağrılan nesne olduğunu gösterir.
Aynı sınıftan oluşturulan nesnelerin niteliklerinin birbirinden bağımsız olduğunu belirler.
Sınıf niteliği ile örnek niteliğini ayırır ve gölgelemeyi tanır.
Paylaşılan mutable sınıf niteliğinin riskini fark eder ve koleksiyonu __init__ içinde oluşturur.
Niteliği doğrudan atamak yerine bir metotla değiştirmenin gerekçesini açıklar.