Sınıf, nesne, self, __init__ ve metotlar
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. Geçen hafta sözlükte tuttuğumuz kaydın iki sorunu vardı: sözlükte hangi anahtarların bulunması gerektiğini ve sözlüğü hangi fonksiyonların değiştirdiğini izlemek zordu. Sınıf ikisini aynı tanımın içinde toplar.
Bu sayfadaki kod hücreleri kendiliğinden çalışmaz. Her hücrede önce çıktıyı tahmin edin, sonra Run Code düğmesine basın. Kodu değiştirip yeniden çalıştırabilir, Start Over ile ilk hâline döndürebilirsiniz.
Bu bölümün kapsamı
- Sınıf ile örnek ayrımı ve
classile tür tanımlama __init__içinde örnek niteliklerini oluşturmaselfparametresinin, metodu çağrılan nesne olması- Aynı sınıftan oluşturulan nesnelerin niteliklerinin birbirinden bağımsız olması
- Sınıf niteliği, örnek niteliği ve gölgeleme
- Paylaşılan mutable sınıf niteliğinin riski
- Niteliği doğrudan atamak yerine bir metotla değiştirmenin gerekçesi
@classmethodve@staticmethod__new__ve nesne oluşturma ayrıntısı__slots__
Bu başlıklar konunun devamıdır. İleride karşınıza çıkar, ama bu derste ezberlemeniz beklenmiyor.
Banka hesabındaki beş işi C’de nasıl yapardınız?
Bir bankanın hesap programını yazıyorsunuz. Her hesabın bir sahibi ve bir bakiyesi var. Program şu beş işi yapacak:
- Yeni hesap açmak: sahibin adı ve başlangıç bakiyesi bir arada dursun
- Bir hesaba para yatırmak
- Ada’nın ve Ece’nin hesaplarını ayrı tutmak: Ada’nın hesabına yatırılan para Ece’nin bakiyesini değiştirmesin
- Bütün hesaplarda aynı olan bir bilgiyi, örneğin bankanın adını, her hesaba ayrı ayrı yazmadan tutmak
- Para çekme kuralını tek yerde tutmak: bakiye yetmiyorsa para çekilmesin
Programlama Temelleri’nde C ile yazdığınız programları düşünün. Bu işlerin her birini C’de nasıl yapardınız? Okumaya devam etmeden önce her iş için bir satır yazın. Geçen hafta kırtasiyenin stok kaydını bir sözlük ve onu değiştiren fonksiyonlarla tuttuk, bu bilgiyle de cevap verebilirsiniz: “struct ve onu alan bir fonksiyon yazarım” da “sözlük ve fonksiyon yazarım” da bir cevap.
Bölümde bu beş işin bir Python sınıfıyla nasıl yapıldığını göreceğiz. Bölümün sonunda listenize döneceğiz.
İlk sınıfımız
C’de önce struct Account türünü tanımlar, sonra o türden bir değişken oluştururdunuz. Python’da yeni bir tür class deyimiyle tanımlanır. Hücrede BankAccount sınıfı tanımlanıyor ve Ada için bir hesap açılıyor. İki satır ne yazar?
BankAccount sınıftır, account ise bu sınıftan oluşturulmuş bir nesneye referanstır. BankAccount("Ada", 500) satırı sınıfı bir fonksiyon gibi çağırır: Python yeni bir nesne oluşturur, __init__ metodunu çalıştırır ve nesneyi döndürür. Sınıfın içine yazılan fonksiyonlara metot denir, __init__ de bir metottur. 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.
__init__ hazır gelen nesneyi başlatır
Python’da nesne önce oluşturulur, sonra başlatılır. Oluşturma adımı __new__ metodunda olur, bu derste ayrıntısına girmiyoruz. __init__ ikinci adımdır: hazır gelen nesneyi self parametresiyle alır ve niteliklerine ilk değerlerini verir. Bu yüzden __init__ için bazen “yapıcı” (constructor) denir, ama nesneyi o yaratmaz.
self nedir?
Hücrede aynı sınıftan iki hesap açılıyor. __init__ iki kez çalışıyor ve iki kez self.owner = owner satırını işletiyor. Ece’nin hesabı açılınca Ada’nın hesabındaki ad değişir mi? İki satır ne yazar?
self, metodu hangi nesne için çağırdıysanız o nesnedir. first oluşturulurken çalışan __init__ içinde self, first’ün gösterdiği nesnedir. second oluşturulurken self bu kez second’ın gösterdiği nesnedir. 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 kayıtla çalışacağını işaretçi parametresiyle söylerdiniz: void init_account(struct Account *a, ...). self o parametrenin Python’daki karşılığıdır. Farkı, çağrıda onu sizin yazmamanız: Python nesneyi kendisi gönderir.
Davranışı sınıfa eklemek
Para yatırma işini sınıfa bir metot olarak ekliyoruz. Hücrede Ada’nın hesabına 50 yatırılıyor. Son satır ne yazar?
C’de bu işi deposit(&account, 50) diye yazardınız. Python’da account.deposit(50) yazılır ve Python bu çağrıyı BankAccount.deposit(account, 50) gibi çalıştırır: noktanın solundaki nesne self parametresine gider. Kodda ilk biçimi yazarız, ikincisi self’in nereden geldiğini anlamaya yardım eder.
Metot, nesnenin kendi verisiyle yaptığı iştir
Metot sınıfın içine yazılmış bir fonksiyondur, ama fonksiyonu hangi sınıfa yazdığınız da bir şey söyler: o iş hangi nesneye ait? deposit() metodunu BankAccount içine koymak, bakiyeyi değiştirmenin hesabın işi olduğunu söyler. Bir sınıfın üstlendiği işlere o sınıfın sorumluluğu denir.
Aynı sınıftan bağımsız nesneler
Ada’nın hesabına yatırılan para Ece’nin hesabını değiştirmemeli. Bunu küçük bir sayaç sınıfıyla deneyelim. first iki kez, second bir kez artırılıyor. Üç satır ne yazar?
__init__ her nesne için ayrı çalışır ve her nesnenin kendi value niteliğini oluşturur. Sınıfı iki kez çağırmak, C’de iki ayrı struct değişkeni tanımlamak gibi iki ayrı nesne verir. 6. haftadaki gibi second = first yazsaydık iki ad aynı sayacı gösterirdi (aliasing: iki adın aynı nesneyi göstermesi) ve first is second True olurdu.
Sınıf niteliği ve örnek niteliği
Bankanın adı bütün hesaplarda aynıdır, okulun adı da bir okuldaki bütün öğrencilerde. Böyle bir bilgiyi her nesneye ayrı ayrı yazmak yerine sınıfa bir kez yazabilirsiniz. Hücredeki Student sınıfında school sınıfın gövdesinde, name ise __init__ içinde atanıyor. ada nesnesine school diye bir nitelik hiç atanmadı. print(ada.school) hata verir mi? Hücre ne yazar?
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.
Nitelik gölgeleme (shadowing)
Hücrede yalnız ada.school niteliğine yeni bir değer atanıyor. Üç satır ne yazar?
ada.school = ... sınıf niteliğini değiştirmez, yalnız 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. 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
Sınıf niteliği bir liste olursa ne olur? Hücrede yalnız first takımına üye ekleniyor. print(second.members) ne yazar?
members sınıf niteliği olduğu için iki nesne de aynı listeyi görür. first.members.append(...) bir atama değildir, bu yüzden gölgeleme olmaz: sınıftaki tek liste yerinde değişir. Her takımın kendi üye listesi olsun istiyorsak listeyi __init__ içinde oluştururuz:
class Team:
def __init__(self, name):
self.name = name
self.members = []Bütün örneklerin gerçekten aynı veriyi görmesi gerekiyorsa sınıf niteliği kullanılabilir. Her nesnenin kendi listesi, sözlüğü ya da kümesi gerekiyorsa mutable nesneyi __init__ içinde oluşturun.
Yanlış yazılan nitelik adı hata vermeden yeni nitelik olur
C’de bir struct’ın alanları tanımda sabittir. Olmayan bir alana değer atarsanız derleyici hata verir. Python’da program çalışırken çoğu nesneye yeni nitelik eklenebilir. Hücredeki reset() metodu bakiyeyi sıfırlamak için yazıldı. Son satır ne yazar?
reset() içindeki ad balance değil blance. self.blance = 0 satırı hata vermez, yanlış adla yeni bir nitelik oluşturur ve balance olduğu gibi kalır. Hatayı ancak sonuç yanlış çıkınca fark edersiniz. Bu bir mantık hatasıdır.
__init__ içinde oluşturun
Nesnede bulunması gereken bütün nitelikleri __init__ içinde atayın, öteki metotlarda yeni nitelik oluşturmayın. Böylece bir nesnenin hangi nitelikleri olduğunu tek bir yerde görürsünüz. Nitelik adını elle yazmak yerine editörün önerdiği tamamlamayı kullanın.
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:
@classmethod
def from_text(cls, text):
...
@staticmethod
def is_valid_name(name):
...@classmethodilk 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.@staticmethodneselfneclsalı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 ikisi sınavda sorulmaz. Önce nesnenin niteliklerini kullanan örnek metotlarını doğru yazmayı öğreneceğiz.
Sorumluluğu doğru yere koymak
Para çekme kuralı nerede durmalı? Aynı işi iki biçimde yazabilirsiniz:
# A: dışarıdan doğrudan güncelleme
account.balance -= 100# B: davranış üzerinden güncelleme
account.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. C’de de kuralı bir withdraw fonksiyonuna yazabilirdiniz, ama account.balance -= 100 yazan kodu derleyici durdurmazdı. Python da durdurmaz. Gelecek hafta bu kuralları, örneğin bakiyenin eksiye düşmemesini, withdraw içine yazacağız.
Soruya dönelim: banka hesabının beş işi
Bölümün başında banka programındaki beş işi C’de nasıl yapacağınızı sormuştuk. Bir Python sınıfıyla karşılıkları şöyle:
| İş | C’de ya da 6. haftada | Bu haftaki Python yolu | Dikkat |
|---|---|---|---|
| Yeni hesap açmak | struct Account tanımı ve struct Account a = {"Ada", 500}; ya da bir sözlük |
class BankAccount ve __init__, sonra BankAccount("Ada", 500) |
__init__ nesneyi oluşturmaz, hazır gelen nesneye ilk değerlerini verir |
| Para yatırmak | Hesabı işaretçiyle alan fonksiyon: deposit(&a, 50) |
account.deposit(50): Python hesabı self olarak gönderir |
Metot tanımında self unutulursa çağrı TypeError verir |
| Hesapları ayrı tutmak | İki ayrı struct değişkeni |
Sınıfı iki kez çağırmak: BankAccount("Ada", 500) ve BankAccount("Ece", 900) |
ece = ada yeni hesap açmaz, aynı nesneye ikinci bir ad verir |
| Ortak bilgiyi tek yerde tutmak | Global değişken ya da #define |
Sınıf niteliği: sınıf gövdesinde school = "Samsun MYO" |
Nesne üzerinden atama sınıf niteliğini değiştirmez, gölgeler. Liste gibi mutable değerleri __init__ içinde oluşturun |
| Para çekme kuralını tek yerde tutmak | withdraw fonksiyonu, ama a.balance -= 100 yazan kodu derleyici durdurmaz |
account.withdraw(100) metodu |
Python da account.balance -= 100 satırını durdurmaz |
Listenizi tabloyla karşılaştırın. İlk üç iş için C’de yazdığınız struct ve işaretçili fonksiyon, Python’da tek bir sınıf tanımına dönüştü: alanlar nitelik oldu, işaretçi parametresi self oldu. 4. işte global değişken yazdıysanız, sınıf niteliği bu bilgiyi programın her yerine değil yalnız o sınıfa bağlar. 5. iş henüz yarım: kural metotta durabilir, ama niteliği doğrudan değiştiren kodu Python durdurmuyor. Gelecek hafta bunu nasıl önleyeceğimizi göreceğiz.
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? Önce tahmin edin, sonra bu çağrıyı hücrenin sonuna ekleyip çalıştırın. Ardından 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. first ve second adında iki öğrenci nesnesi oluşturun, yalnız ilk öğrenciye first.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. Sınıfın üç niteliği olsun:
titleauthoris_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 ötekini 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.
Tek sayfa özet
- Sınıf bir tür tanımıdır. Nesne, o sınıfı çağırarak oluşturulan örnektir:
BankAccount("Ada", 500). __init__yeni nesnenin niteliklerine ilk değerlerini verir. Nesneyi oluşturmaz, hazır gelen nesneyi başlatır.selfmetodun çağrıldığı nesnedir. C’deki işaretçi parametresinin karşılığıdır, ama çağrıda yazılmaz:account.deposit(50),BankAccount.deposit(account, 50)gibi çalışır.- Aynı sınıftan oluşturulan her nesnenin kendi nitelikleri vardır.
- Ö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. Sınıf niteliğini sınıfın adıyla değiştirin.
- Her nesnenin kendi mutable koleksiyonu gerekiyorsa koleksiyonu
__init__içinde oluşturun. - Python program çalışırken nesneye yeni nitelik eklenmesine izin verir. Yanlış yazılmış bir nitelik adı hata vermeden yeni bir nitelik oluşturur.
- 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
classile kendi türünü tanımlar. __init__içinde örnek niteliklerini oluşturur.selfparametresinin, 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ırt eder 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.