Bir sınıf yazmak, nitelikleri dış dünyaya açıp birkaç metot eklemekten ibaret değildir. Nesnenin her zaman anlamlı bir durumda kalması gerekir. Bu hafta kapsülleme (encapsulation) fikrini, kontrollü durum değişimini ve değişmez koşul (invariant) kavramını kullanacağız.
Bu bölümün kapsamı
NoteSınavda sorulur
bir sınıf için değişmez koşul (invariant) yazma
geçersiz başlangıç durumunu __init__ içinde engelleme
kapsüllemenin erişimi yasaklamak değil değişimi tek arayüzde toplamak olması
tek alt çizgi sözleşmesi ile çift alt çizgi ad dönüştürmesi
property ve setter; üç adın aynı olması
__init__ içinde setter’ı kullanarak tek doğrulama noktası
önce ön koşul, sonra durum değişikliği sırası
NoteSınav dışı (bilgi amaçlı)
__set_name__, descriptor protokolü
dataclass ile doğrulama
__getattr__ / __setattr__ ile erişim yakalama
Bu başlıklar konunun devamıdır; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.
Nesne geçersiz bir duruma düşebilir mi?
Bir sınıf yazmak, o nesnenin hangi değerlerle anlamlı olduğunu da söylemektir. Bakiyesi eksi olan bir hesap ya da fiyatı sıfırın altında bir ürün çalışan ama yanlış bir programdır. Bu bölümde o kuralı yoruma bırakmayıp sınıfın içine yazacağız.
Değişmez koşul (invariant) nedir?
Değişmez koşul (invariant), nesnenin geçerli kabul edildiği her anda doğru olması gereken kuraldır.
Bir banka hesabı için:
balance >= 0
Bir ürün için:
price > 0 ve stock >= 0
Bir ders kaydı için:
0 <= score <= 100
Bu kurallar yalnızca veri doğrulama ayrıntısı değil, nesnenin ne anlama geldiğinin bir parçasıdır.
Geçersiz başlangıcı engellemek
Nesne ilk andan itibaren geçerli olmalıdır.
Kontrollü durum değişimi
_balance başındaki tek alt çizgi “bu niteliği sınıfın iç detayı olarak kabul et” biçiminde bir programcı sözleşmesidir. Python bunu teknik olarak tamamen erişilemez yapmaz.
Kapsülleme ve invariant
ImportantKapsülleme = erişimi yasaklamak değildir
Amaç, nesnenin durumunun hangi kurallarla değişeceğini tek bir güvenilir arayüzde toplamaktır. Python’da kapsülleme çoğu zaman katı erişim engelinden çok sorumluluk ve arayüz disiplini ile sağlanır.
Tek alt çizgi ve çift alt çizgi aynı şey değildir
Python’da _balance bir konvansiyondur: “bu ad sınıf/modülün iç ayrıntısıdır” mesajı verir. Buna karşılık __balance biçimindeki çift alt çizgi, sınıf gövdesinde name mangling denen ad dönüştürmesini tetikler:
class Account:def__init__(self):self.__balance =100
Python bu adı kabaca _Account__balance biçimine dönüştürür. Amaç gerçek bir güvenlik/private mekanizması kurmak değildir; özellikle alt sınıflarda aynı adlı niteliklerin kazara çakışmasını azaltmaktır.
Warning__x “asıl private” değildir
account._Account__balance gibi dönüştürülmüş ada teknik olarak erişilebilir. Bu nedenle çift alt çizgiyi “kimse erişemez” diye öğretmek yanlıştır. Bu derste normal iç durum için çoğunlukla tek alt çizgi (_balance) kullanacağız; __x sözdizimini gördüğünüzde name mangling olduğunu tanımanız yeterlidir.
property ne zaman anlamlı?
Bir niteliği kullanıcıya account.balance biçiminde okunabilir sunmak ama değişimini kontrol altında tutmak isteyebiliriz.
property erişim kapısı
@propertydef balance(self):returnself._balance
Bu durumda account.balance okunabilir; setter tanımlamadığımız için normal account.balance = -100 ataması yapılamaz.
Public özelliğin adı price ise getter metodu, @price.setter dekoratöründeki ad ve setter metodunun adı aynı olmalıdır. Öğrencilerin en sık yaptığı hatalardan biri setter metoduna farklı ad vermektir.
__init__ neden setter’ı kullanıyor?
Product.__init__ içinde doğrudan:
self._price = price
yazmak yerine:
self.price = price
yazdık. Böylece başlangıçtaki fiyat ile daha sonraki fiyat atamaları aynı doğrulama kuralından geçer.
class Product:def__init__(self, price):self.price = price # setter çalışır@propertydef price(self):returnself._price@price.setterdef price(self, value):if value <=0:raiseValueError("Fiyat pozitif olmalı")self._price = value
Bu, doğrulamayı iki yerde kopyalamaktan daha güvenlidir. Kural değişirse tek noktayı güncellersiniz.
ImportantTek doğrulama noktası
Aynı invariant’ı __init__ içinde bir kez, setter içinde bir kez daha yazmak kolayca tutarsızlığa yol açar. Setter gerçekten nesnenin public güncelleme kapısıysa, __init__ de mümkün olduğunda o kapıyı kullanarak başlangıç durumunu doğrulasın.
Fail fast
Geçersiz durum ortaya çıktığında sorunu mümkün olduğunca erken bildirmek genellikle daha güvenlidir:
Product("Klavye", -100)
oluşturulurken hata vermesi, negatif fiyatın sistemde dolaşıp çok daha sonra yanlış rapor üretmesinden iyidir.
Kontrol değişiklikten sonra yapılmıştır. Hata üretildiğinde self._balance zaten negatif olmuş olabilir.
Daha güvenli sıra:
Tüm ön koşulları kontrol et,
Sonra durumu değiştir.
Bu ilkeyi dönem boyunca kullanacağız.
Alıştırma — Invariant belirle
Aşağıdaki sınıflar için en az iki invariant yazın:
Student
Product
Reservation
Örneğin tarihlerin sırası, puan aralığı, pozitif fiyat, negatif olmayan stok gibi kurallar düşünün.
Alıştırma — Güvenli withdraw
if amount <=0:raiseValueError("Tutar pozitif olmalı")if amount >self._balance:raiseValueError("Yetersiz bakiye")
Alıştırma — Property mi metot mu?
Aşağıdakileri tartışın:
account.balance
account.withdraw(100)
rectangle.area
order.cancel()
Hangileri “durumdan hesaplanan bir özellik” gibi, hangileri “eylem/komut” gibi okunuyor? Python’da her şeyi property yapmak da her şeyi get_...() metoduna dönüştürmek de gerekli değildir. Arayüzün niyetini açık kılın.
Küçük üretim görevi — Güvenli ürün
Product sınıfı yazın:
name: boş olamaz,
price: 0’dan büyük olmalı,
stock: negatif olamaz.
Metotlar:
add_stock(amount)
sell(amount)
Fiyat için getter + setter property kullanın ve __init__ içinde self.price = price üzerinden aynı doğrulamayı yeniden kullanın. sell yetersiz stokta hata vermeli ve stok değerini değiştirmemelidir.
Çalışır kod teslimi: Geçerli ürün, geçersiz başlangıç fiyatı, sonradan geçersiz fiyat ataması, başarılı satış ve yetersiz stok olmak üzere en az beş senaryoyu çalıştırın. Başarısız işlemlerden sonra nesne durumunun değişmediğini assert ile doğrulayın.
Sınav provası
Önce kendi cevabını seç; sonra cevap anahtarında her şıkkın neden doğru veya yanlış olduğunu oku. Çeldiriciler uydurma değil, bu konuda gerçekten yapılan hatalardır. Sınav maddeleri de bu mantıkla yazılır.
Madde 1. Değişmez koşul (invariant) nedir?
A. Sınıfın bütün örneklerinde ortak olan sınıf niteliğidir
B. Metotların kaç parametre alacağını belirleyen sözleşmedir
C. Nesne üretildikten sonra hiç değişmeyen nitelik değeridir
D. Nesnenin geçerli kabul edildiği her anda doğru olması gereken kuraldır
E. Programın hiç hata vermeyeceğini garanti eden kontroldür
B. Hiçbir şey yazmaz; nesne balance = -100 ile oluşur
C. Program çöker; __init__ içinde raise kullanılamaz
D.None
E.Başlangıç bakiyesi negatif olamaz; nesne hiç oluşmaz
Madde 3. “Fail fast” ilkesi nesne tasarımında neyi savunur?
A. Geçersiz durumu mümkün olan en erken noktada bildirmeyi; Product("Klavye", -100) çağrısının hemen hata vermesi, negatif fiyatın sistemde dolaşmasından iyidir
B. Doğrulamayı nesne kullanıldığı ana ertelemeyi
C. Doğrulamaları kaldırarak programı hızlandırmayı
D. Bütün hataların except Exception ile yakalanmasını
E. Programın hata anında hemen sonlanmasını
Madde 4. Python’da kapsülleme temel olarak neyi amaçlar?
A. Niteliklere dışarıdan erişimi teknik olarak imkânsız kılmayı
B. Nesnenin durumunun hangi kurallarla değişeceğini tek bir güvenilir arayüzde toplamayı
C. Bütün nitelikleri salt okunur yapmayı
D. Nesneyi kopyalanamaz hâle getirmeyi
E. Sınıfın metot sayısını azaltmayı
Madde 5. Sınıf gövdesinde self.__balance = 100 yazmak ne yapar?
A. Niteliği salt okunur yapar
B. Niteliği gerçekten erişilemez yapar
C. Ad dönüştürmesini (name mangling) tetikler; nitelik kabaca _Account__balance adıyla saklanır
D. Niteliği sınıf niteliğine çevirir
E. Tek alt çizgiyle tümüyle aynı şeyi yapar
Madde 6._balance adındaki tek alt çizgi ne anlatır?
A. Python’ın private erişim belirtecidir ve dışarıdan okumayı engeller
B. Niteliğin property ile sarmalandığını bildirir
C. Niteliğin ad dönüştürmesine gireceğini gösterir
D. “Bu ad sınıfın iç ayrıntısıdır” biçiminde bir programcı sözleşmesidir; Python erişimi teknik olarak engellemez
E. Niteliğin sınıf düzeyinde tanımlandığını gösterir
Madde 7. Aşağıdaki sınıfta product.price = -50 satırı ne yapar?
class Product:def__init__(self, name, price):self.name = nameself.price = price@propertydef price(self):returnself._price@price.setterdef price(self, value):if value <=0:raiseValueError("Fiyat pozitif olmalı")self._price = value
A. Yeni bir price örnek niteliği oluşturur ve property’yi gölgeler
B.AttributeError yükseltir; price salt okunurdur
C. Hiçbir şey yapmaz; atama yok sayılır
D._price değerini sessizce -50 yapar
E. Setter çalışır ve ValueError yükseltir; _price değişmez
Madde 8.price adlı bir property yazarken hangi üç ad aynı olmalıdır?
A. Getter metodunun adı, @price.setter dekoratöründeki ad ve setter metodunun adı
B.__init__ parametresinin adı, getter’ın adı ve dosyanın adı
C. Getter metodunun adı, iç niteliğin adı ve parametrenin adı
D. Sınıfın adı, property’nin adı ve iç niteliğin adı
E. Yalnız iki ad aynı olmalıdır; setter serbestçe adlandırılabilir
Madde 9.__init__ içinde self._price = price yerine self.price = price yazmanın gerekçesi nedir?
A. Nesne üretimini hızlandırır
B. Başlangıç değeri de setter’daki doğrulamadan geçer; kural tek noktada kalır
B.amount negatif olabileceği için tür denetimi gerekir
C. Kontrol değişiklikten sonra yapılır; hata yükseldiğinde self._balance zaten negatife düşmüş olur
D. Sorun yoktur; istisna yükselince değişiklik kendiliğinden geri alınır
E.other nesnesinin özel niteliğine erişilemez
Madde 11. Durumu birden çok adımda değiştiren bir metotta hangi sıra izlenmelidir?
A. Önce durum değiştirilir, sonuç yanlışsa geri alınır
B. Kontrol hiç yapılmaz; çağıran doğru değer göndermekle yükümlüdür
C. Kontroller try/except içine alınır ve hata yutulur
D. Önce bütün ön koşullar sınanır, sonra durum değiştirilir
E. Her adımdan sonra ayrı ayrı kontrol yapılır
TipCevaplar ve gerekçeler
Madde 1 — Doğru: D.balance >= 0 veya 0 <= score <= 100 gibi kurallar nesnenin ne anlama geldiğinin parçasıdır; yalnız bir doğrulama ayrıntısı değildir.
A yanlış: Sınıf niteliği paylaşılan veridir; kural değildir.
B yanlış: İmza sözleşmesi ayrı bir konudur.
C yanlış: Değişmeyen değer sabittir; invariant değerin değil, kuralın sabitliğidir.
E yanlış: İnvariant korunurken hata yükseltilebilir; amaç geçersiz durumu engellemektir.
Madde 2 — Doğru: E.raise çalıştığı anda __init__ yarıda kesilir ve account adına hiçbir şey bağlanmaz. Nesne ilk andan itibaren geçerli olmalıdır.
A yanlış: Sessiz düzeltme yapılmaz; hata yükseltilir.
B yanlış: Doğrulama bunu engeller.
C yanlış:__init__ içinde hata yükseltmek olağan ve önerilen bir kalıptır.
D yanlış: Yazdırılan, yakalanan istisnanın mesajıdır.
Madde 3 — Doğru: A. Hata kaynağına yakın yakalanırsa nedeni bulmak kolaydır. Geç fark edilen geçersiz durum yanlış raporlara dönüşür.
B yanlış: Kaçınılmak istenen şey zaten ertelemedir.
C yanlış: Tam tersi savunulur.
D yanlış: Geniş yakalama hatayı gizler; ilkeye aykırıdır.
E yanlış: İstisna yakalanarak işlenebilir; savunulan, geç değil erken bildirimdir.
Madde 4 — Doğru: B. Python’da kapsülleme katı erişim engelinden çok sorumluluk ve arayüz disiplinidir.
A yanlış: Python böyle bir engel sunmaz; _balance ve __balance de erişilebilir kalır.
C yanlış: Durum çoğu nesnede değişmelidir; kontrol edilen, nasıl değiştiğidir.
D yanlış: Kopyalama ayrı bir konudur.
E yanlış: Kapsülleme çoğu zaman metot ekler.
Madde 5 — Doğru: C. Amaç gerçek bir private mekanizması değil, özellikle alt sınıflarda aynı adlı niteliklerin kazara çakışmasını azaltmaktır.
A yanlış: Salt okunurluk için property kullanılır.
B yanlış:account._Account__balance ile erişilebilir; teknik bir engel değildir.
D yanlış:self. ile yazılan nitelik örneğe aittir.
E yanlış: Tek alt çizgi yalnız bir sözleşmedir; ad dönüştürmesi yapmaz.
Madde 6 — Doğru: D. Alt çizgi bir niyet bildirimidir. Disiplini sağlayan, dilin zorlaması değil ekibin sözleşmesidir.
A yanlış: Python’da böyle bir erişim belirteci yoktur.
B yanlış: İlişkili kullanılabilir ama alt çizgi tek başına bunu söylemez.
C yanlış: Ad dönüştürmesi çift alt çizgide olur.
E yanlış: Düzeyi belirleyen self. yazımıdır.
Madde 7 — Doğru: E.property sayesinde normal görünen atama bir metot çağrısına dönüşür ve doğrulamadan geçer.
A yanlış: Veri tanımlayıcısı olan property örnek sözlüğünden önce gelir; gölgeleme olmaz.
B yanlış: Setter tanımlandığı için atama desteklenir.
C yanlış: Atama setter’ı çağırır ve hata yükselir.
D yanlış: Setter bunu engeller.
Madde 8 — Doğru: A. Üçü de public özelliğin adını taşır. Setter’a farklı ad vermek en sık yapılan hatalardandır.
B yanlış: Dosya adının bir ilgisi yoktur.
C yanlış: İç nitelik genellikle _price olur; parametre adı serbesttir.
D yanlış: Sınıf adının property adıyla ilgisi yoktur.
E yanlış: Setter farklı adla yazılırsa property’ye bağlanmaz ve atama çalışmaz.
Madde 9 — Doğru: B. Aynı invariant’ı iki yerde yazmak tutarsızlığa açıktır. Kural değişince tek nokta güncellenir.
A yanlış: Aksine bir metot çağrısı ekler; gerekçe başarım değildir.
C yanlış: Property zaten tanımlıdır; bu satır onu kullanır.
D yanlış: Setter kontrollü değişiklik içindir.
E yanlış: Ad yine erişilebilir; kazanç doğrulamanın tekilliğidir.
Madde 10 — Doğru: C. Nesne yarım güncellenmiş, geçersiz bir durumda kalır. Doğru sıra önce bütün ön koşulları sınamak, sonra durumu değiştirmektir.
A yanlış: Kullanılabilir; sorun çağrının yeridir.
B yanlış: Bu ayrı bir eksiktir ama sorulan sorun sıralamadır.
D yanlış: Python’da otomatik geri alma yoktur; değişiklik kalıcıdır.
E yanlış: Tek alt çizgi teknik bir engel değildir; aynı sınıf içinde bu erişim olağandır.
Madde 11 — Doğru: D. Böylece hata yükseldiğinde nesne hiç değişmemiş olur ve geçerli durumunu korur.
A yanlış: Geri alma kodu kolayca eksik kalır ve nesne yarım güncellenmiş kalabilir.
B yanlış: İnvariant’ı korumak nesnenin kendi sorumluluğudur.
C yanlış: Hata gizlenir ve geçersiz durum sessizce kalıcı olur.
E yanlış: İlk adım geçip ikincisi düşerse nesne yine yarım kalır.
Tek sayfa özet
Kapsülleme, nesnenin geçerli durumunu koruyan bir sorumluluk sınırıdır.
Invariant, nesnenin her geçerli durumda sağlaması gereken kuraldır.
Başlangıç ve durum geçişleri doğrulanmalıdır.
_name bir erişim yasağı değil, iç kullanım niyetidir; __name ise name mangling uygular, güvenlik sağlamaz.
property, arayüzü sade tutarken kontrol ekleyebilir.
Getter/setter adları tutarlı olmalı; __init__ setter’ı kullanarak doğrulamayı tek noktada tutabilir.
Bu bölümün kazanımları
Bu bölümü bitiren öğrenci:
Bir sınıf için değişmez koşul (invariant) yazar.
Geçersiz başlangıç durumunu __init__ içinde engeller.
Kapsüllemenin erişimi yasaklamak değil, değişimi tek arayüzde toplamak olduğunu açıklar.
Tek alt çizgi sözleşmesi ile çift alt çizgi ad dönüştürmesini ayırır.
property ve setter tanımlar; üç adın aynı olması gerektiğini bilir.
__init__ içinde setter’ı kullanarak doğrulamayı tek noktada toplar.
Önce ön koşulları sınayıp sonra durumu değiştirerek yarım güncellemeyi önler.