Kapsülleme, property ve geçerli durum

Geçen hafta BankAccount sınıfını yazdınız, ama account.balance = -500 gibi bir satırı hiçbir şey durdurmuyordu. Program yine çalışır, ama eksi bakiyeli bir hesapla yanlış sonuç verir. Bu hafta nesnenin kurallarını sınıfın içine yazacağız: kurala uymayan bir değer geldiğinde program hata verecek. Üç kavram kullanacağız: değişmez koşul (invariant), kapsülleme (encapsulation) ve property. Üçünü de ilk geçtikleri yerde birer örnekle tanımlayacağız.

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ı

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 amacı: erişimi yasaklamak değil, niteliklerin yalnız sınıfın metotlarıyla değişmesi
  • Tek alt çizgi geleneği ve Python’da dışarıdan erişimi engelleyen bir private belirtecinin olmaması
  • property ve setter yazma, üç adın aynı olması
  • Doğrulamayı tek yerde tutmak için __init__ içinde setter’ı kullanmak
  • Önce ön koşulları denetleyip sonra niteliği değiştirmek
  • Çift alt çizgi ad dönüştürmesinin (name mangling) nasıl çalıştığı
  • __set_name__ ve descriptor protokolü
  • dataclass ile doğrulama
  • __getattr__ ve __setattr__ ile erişim yakalama

Bu başlıklar konunun devamıdır. İleride karşınıza çıkar, ama bu derste ezberlemeniz beklenmiyor.

Ürünün beş kuralını C’de nasıl korurdunuz?

3. haftadaki envanter görevi bir soruyla bitiyordu: “Stok sıfırın altına düşmesin” kuralını nerede koruyoruz? 6. haftada kuralı sell fonksiyonunun içine yazdınız, ama sözlüğü programın her yeri yine değiştirebiliyordu. Bu hafta o soruyu cevaplıyoruz.

Bir kırtasiyenin stok programında her ürünün adı, fiyatı ve stok sayısı var. Program şu beş kuralı korumalı:

  1. Fiyatı sıfır ya da eksi olan, stoğu eksi olan bir ürün hiç kaydedilmemeli.
  2. Stok yalnız satışla ve mal girişiyle değişmeli. Stokta olandan fazlası satılamamalı.
  3. Programın öteki parçaları stoğu doğrudan değiştirememeli.
  4. Fiyat sonradan değiştirildiğinde de aynı kural denetlenmeli.
  5. Satış hata verirse ürün satıştan önceki hâlinde kalmalı.

C’de ürünü bir struct ile tutardınız. Bu kuralların her birini C’de nerede ve nasıl korurdunuz? Okumaya devam etmeden önce her kural için bir satır yazın. Geçen haftaki BankAccount sınıfıyla da düşünebilirsiniz. “Bunu C’de yapamam” da bir cevap.

Bölümde bu beş kuralı Python’da Product sınıfının içine yazacağız. Bölümün sonunda listenize döneceğiz.

Geçersiz ürünü hiç oluşturmamak

Fiyatın sıfırdan büyük olması ve stoğun eksiye düşmemesi, ürün kullanıldığı sürece her an doğru kalması gereken kurallardır. Böyle bir kurala değişmez koşul (invariant) denir. Niteliklerin değeri değişir, kural değişmez: stok 12 de olabilir 0 da, ama eksi olamaz. Bir banka hesabında balance >= 0, bir ders kaydında 0 <= score <= 100 de birer değişmez koşuldur.

Kuralı ilk denetleyecek yer __init__. C’de ürünü oluşturan bir fonksiyon, kurala uymayan bir değer gelince hata kodu döndürürdü. __init__ değer döndürmez, hatayı raise ile bildirir. Hücrede stoğu -5 olan bir kalem oluşturuluyor. Hücre ne yazar? pen adı bir nesneye bağlanır mı?

raise çalışınca __init__ yarıda kalır ve nesne hiç oluşmaz. Oluşan her ürün ilk andan itibaren kurala uyar.

Hatayı erken vermek: fail fast

Fail fast (erken hata ver) ilkesi, geçersiz bir değeri ortaya çıktığı anda bildirmeyi önerir. Stoğu -5 olan kalemin daha oluşturulurken hata vermesi, eksi stoğun programda dolaşıp haftalar sonra yanlış bir sipariş raporuna yol açmasından iyidir. Hata, değerin ortaya çıktığı yere yakın verilirse nedeni de kolay bulunur.

Stoğu yalnız metotlar değiştirsin

Stok iki yoldan değişir: mal girişi ve satış. Hücrede ikisi için birer metot var, add_stock ve sell. Stoğu okumak için de stock() metodu var. Kalemin stoğu 5, önce 3 tane ekleniyor, sonra 10 tane satılmak isteniyor. Hücre ne yazar?

İki metot da stoğa dokunmadan önce miktarı denetler, stoğu ancak kural tutuyorsa değiştirir. Kural artık sınıfın içinde, stoğu değiştiren iki metotta yazılı.

Niteliğin adı da değişti: stock yerine _stock. Bu alt çizginin ne işe yaradığına sıradaki başlıkta bakacağız.

Tek alt çizgi: “dışarıdan dokunmayın”

C’de bir struct’ın alanlarını, o struct’ı gören her kod değiştirebilirdi. Bazı dillerde bir nitelik private diye işaretlenir ve ona sınıfın dışından erişim tamamen engellenir. Python’da böyle bir belirteç yok. _stock adının başındaki tek alt çizgi bir adlandırma geleneğidir (convention). Python programcıları bu işareti şöyle okur: “Bu nitelik sınıfın iç ayrıntısıdır, dışarıdan dokunmayın.”

Python bu geleneği zorlar mı? Hücrede kalem 5 stokla oluşturuluyor, sonra dışarıdaki kod _stock niteliğine -5 atıyor. Bu atama hata verir mi? Son satır ne yazar?

__init__ içindeki kontrol yalnız nesne oluşturulurken bir kez çalışır. Sonraki atamayı denetleyen bir kod yok ve Python alt çizgili bir ada dışarıdan atama yapılmasını engellemez.

Nesne durumu yalnızca kurallı davranışlarla değiştiğinde invariantın korunduğunu gösteren diyagram.

Kapsülleme ve invariant
ImportantKuralı metotlar ve programcılar korur

Bir nesnenin dışarıdaki koda açtığı metot ve niteliklere o nesnenin arayüzü denir. Kapsülleme, niteliklerin yalnız arayüzdeki metotlarla, kurala uyarak değişmesidir. Python bunu bir erişim engeliyle sağlamaz. Kuralı programcılar korur: alt çizgili adlara dışarıdan dokunmaz, niteliği sınıfın metotlarıyla değiştirirler.

Çift alt çizgi ve ad dönüştürme

__stock gibi iki alt çizgiyle başlayan bir ad, sınıf gövdesinde Python’ın o adı değiştirmesine yol açar. Buna ad dönüştürme (name mangling) denir:

class Product:
    def __init__(self):
        self.__stock = 5

Python bu adı _Product__stock biçimine dönüştürür. Bu dönüşüm niteliği gizlemek ya da korumak için yapılmaz. Bir sınıftan yeni bir sınıf türetildiğinde (buna kalıtım denir, 10. bölümde göreceğiz) iki sınıftaki aynı adlı niteliklerin yanlışlıkla çakışmasını önler.

Warning__x gerçek anlamda private değildir

Dönüştürülmüş ada pen._Product__stock yazarak yine erişebilirsiniz. Çift alt çizgili bir nitelik için “kimse erişemez” demek bu yüzden yanlıştır. Bu derste sınıfın iç verisi için tek alt çizgi (_stock) kullanacağız. __x biçiminde bir ad gördüğünüzde ad dönüştürmenin çalıştığını bilmeniz yeter.

Fiyatı kurala uyarak değiştirmek: property

Stoğu pen.stock() diye, parantezle okuyoruz. Fiyat ise düz bir nitelik: pen.price = -12 satırını hiçbir şey durdurmaz. Fiyatın pen.price diye okunmasını, ama yalnız kurala uyan değerlerle değişmesini istiyoruz. Python’da bunun aracı property’dir: dışarıdan nitelik gibi görünen, ama okununca ya da atanınca bir metot çalıştıran ad.

Önce okuma tarafına bakalım. 7. haftada @classmethod için gördüğünüz gibi, @property de bir dekoratördür. Bir metodun üstüne yazılınca o metot parantezsiz çağrılır:

@property
def stock(self):
    return self._stock

@property ile işaretlenen metot değer okunurken çalışır. Buna getter denir. Artık pen.stock() yerine pen.stock yazarız. Değer atanırken çalışan metoda ise setter denir. Hücredeki sınıfta stock için setter yok. print(pen.stock) ne yazar? Son satırda ne olur?

Setter’ı olmayan bir property’ye değer atanamaz. Stok için istediğimiz de bu: stok yalnız add_stock ve sell yoluyla değişsin.

property ve setter'ın bir niteliği okumak ve değiştirmek için kontrollü bir arayüz oluşturduğunu gösteren diyagram.

property erişim kapısı

Fiyat ise değişebilmeli, ama yalnız kurala uyarak. Bunun için setter yazılır. Hücredeki pen.price = 12 ve pen.price = -12 satırları setter’ı çalıştırır. İlk print() ne yazar? Son satır çalışır mı?

pen.price = -12 sıradan bir atama gibi görünür, ama Python bu atamayı setter’a gönderir. Setter kurala uymayan değeri reddeder ve _price eski değerinde kalır.

Setter söz diziminde üç ad aynı olmalıdır

Yukarıdaki örnekte şu üç satıra dikkat edin:

@property
def price(self):
    ...

@price.setter
def price(self, value):
    ...

Dışarıdan kullanılan ad price ise getter metodunun adı, @price.setter dekoratöründeki ad ve setter metodunun adı da price olmalıdır. Setter metoduna farklı bir ad vermek bu konuda en sık yapılan hatalardandır.

__init__ neden setter’ı kullanıyor?

Son hücredeki __init__, fiyatı self._price = price diye değil self.price = price diye atıyor. __init__ içinde hiç if yok. Product("Klavye", -100) hata verir mi?

__init__ içindeki self.price = price ataması da setter’dan geçer. Böylece başlangıçtaki fiyat da sonraki fiyat atamaları da aynı kuraldan geçer. C’de ürünü oluşturan fonksiyonun, fiyatı denetleyen set_price() fonksiyonunu çağırması gibi düşünebilirsiniz.

ImportantTek doğrulama noktası

Aynı kuralı __init__ içinde bir kez, setter içinde bir kez daha yazarsanız ikisi kolayca birbirini tutmaz hâle gelir: birini güncelleyip ötekini unutursunuz. Niteliği dışarıdan değiştirmenin yolu setter’sa __init__ de ilk değeri o setter’la atasın. Kural değişirse yalnız setter’ı güncellersiniz.

Yarım güncelleme tehlikesi

Satış metodunu bu kez başka bir sırayla yazalım: stok önce azaltılıyor, kontrol sonra yapılıyor. Kalemin stoğu 3, 5 tane satılmak isteniyor ve satış hata veriyor. Son satır kaç yazar?

Kontrol hata verdiğinde _stock çoktan eksiye düşmüştür ve öyle kalır. raise o ana kadar yapılan değişikliği geri almaz. Ürün yarım güncellenmiş, kurala uymayan bir durumda kalır.

Bir işlemin yapılabilmesi için önceden doğru olması gereken koşula ön koşul denir. Burada ön koşul, stoğun satılan miktara yetmesidir. Güvenli sıra şudur:

  1. Bütün ön koşulları denetle.
  2. Sonra durumu değiştir.

“Stoğu yalnız metotlar değiştirsin” başlığındaki sell bu sırayı izliyordu. İki nesneyi birlikte değiştiren işlemlerde sıra daha da önemlidir. Bir hesaptan ötekine para aktaran metot kontrolü arada yaparsa, hata anında para bir hesaptan çıkmış, öteki hesaba girmemiş olur. Bu sırayı dönem boyunca kullanacağız.

Soruya dönelim: beş kural Python’da

Bölümün başında ürünün beş kuralını C’de nasıl koruyacağınızı sormuştuk. Python’daki karşılıkları şöyle:

Kural C’de ya da 7. haftada Bu haftaki Python yolu Dikkat
Geçersiz ürün kaydedilmesin Ürünü oluşturan fonksiyon değerleri denetler, hata kodu döndürür __init__ içinde raise ValueError(...) __init__ değer döndürmez, hatayı raise ile bildirir
Stok yalnız satışla ve mal girişiyle değişsin sell() ve add_stock() fonksiyonları if ile denetler sell() ve add_stock() metotları, _stock niteliği Stoğu okumak için setter’ı olmayan bir stock property’si yeter
Öteki kod stoğu doğrudan değiştiremesin Yapılamaz: struct alanını her kod değiştirebilir Tek alt çizgi: _stock Python da engellemez, pen._stock = -5 çalışır
Fiyat sonradan değişince de denetlensin set_price() fonksiyonu yazılır, herkesin onu çağırdığına güvenilir @property ve @price.setter, __init__ içinde self.price = price Getter, @price.setter ve setter aynı adı taşır
Satış hata verirse ürün eski hâlinde kalsın Önce if ile denetlenir, sonra alan değiştirilir Aynı sıra: önce ön koşullar, sonra atama raise yapılmış değişikliği geri almaz

Listenizi tabloyla karşılaştırın. 3. kural için “C’de yapamam” diye yazdıysanız Python’da da durum pek farklı değil: alt çizgi bir engel koymaz, programcılara “dokunmayın” der. Kuralı korumanın yolu, niteliği yalnız sınıfın metotlarıyla değiştirmektir. 4. kural için ayrı bir set_price() fonksiyonu düşündüyseniz Python’daki setter aynı işi yapar, üstelik dışarıdaki kod sıradan bir atama yazar.

3. haftadaki soruya da artık cevap verebiliriz. Stok bir sözlükte dururken “stok sıfırın altına düşmesin” kuralı, sözlüğü değiştiren her satırda ayrı ayrı korunmak zorundaydı. Şimdi kural Product sınıfında, __init__ ve sell içinde yazılı. Stoğu değiştirmek isteyen kod bu metotları çağırır ve kural her seferinde denetlenir.

Alıştırma: Invariant belirle

Aşağıdaki sınıfların her biri için en az iki invariant yazın:

  • Student
  • BankAccount
  • Reservation

Puan aralığı, bakiyenin eksiye düşmemesi ya da tarihlerin sırası gibi kurallar düşünebilirsiniz.

Alıştırma: Güvenli withdraw

7. haftada bakiyenin eksiye düşmemesi kuralını withdraw metoduna yazacağımızı söylemiştik. Boşluğa iki kontrol yazın: tutar pozitif olmalı ve bakiye tutara yetmeli. Kural bozulursa metot ValueError yükseltsin. İki kontrol de bakiyeyi azaltan satırdan önce gelmeli.

if amount <= 0:
    raise ValueError("Tutar pozitif olmalı")
if amount > self._balance:
    raise ValueError("Yetersiz bakiye")

Alıştırma: Property mi metot mu?

Aşağıdaki dört yazımı karşılaştırın:

  • account.balance
  • account.withdraw(100)
  • rectangle.area
  • order.cancel()

Hangileri nesnenin verisinden hesaplanan bir değer gibi, hangileri nesneye verilen bir komut gibi okunuyor? Python’da her şeyi property yapmanız gerekmez, her değeri bir get_...() metoduyla okumanız da gerekmez. Arayüzü, kodu okuyan kişi neyin değer, neyin eylem olduğunu hemen anlayacak biçimde kurun.

Sıra sizde: Güvenli kitap

7. haftada yazdığınız Book sınıfı aynı kitabın iki kez ödünç alınmasını engellemiyordu. Şimdi engelleyin. Book sınıfı şu kuralları korusun:

  • title boş olamaz.
  • is_borrowed dışarıdan okunabilir, ama yalnız borrow() ve return_book() metotlarıyla değişir.
  • Ödünçteki kitap yeniden ödünç alınamaz.
  • Ödünçte olmayan kitap iade edilemez.

title niteliğini getter ve setter’ı olan bir property ile yazın. __init__ içinde self.title = title yazarak aynı doğrulamayı yeniden kullanın. is_borrowed için yalnız getter yazın. Kural bozulursa metot ValueError yükseltmeli ve kitabın durumunu değiştirmemeli.

Çalışır kod görevi: En az şu beş durumu çalıştırın: geçerli bir kitap, boş başlıkla oluşturulan kitap, sonradan atanan boş başlık, aynı kitabı iki kez ödünç alma ve ödünçte olmayan kitabı iade etme. Hata veren işlemlerden sonra niteliklerin değişmediğini assert ile doğrulayın.

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. Değişmez koşul (invariant) nedir?

  • A. Sınıfın bütün örneklerinde ortak olan sınıf niteliğidir
  • B. Nesne kullanılırken her zaman doğru olması gereken kuraldır
  • C. Metotların kaç parametre alacağını belirleyen sözleşmedir
  • D. Nesne üretildikten sonra hiç değişmeyen nitelik değeridir
  • E. Programın hiç hata vermeyeceğini garanti eden kontroldür

Madde 2. Aşağıdaki program ne yazdırır?

class BankAccount:
    def __init__(self, owner, balance=0):
        if balance < 0:
            raise ValueError("Başlangıç bakiyesi negatif olamaz")
        self.owner = owner
        self.balance = balance

try:
    account = BankAccount("Ada", -100)
except ValueError as error:
    print(error)
  • A. 0; negatif değer varsayılana düşer
  • 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 olabildiğince erken bildirmeyi
  • 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. Bütün nitelikleri salt okunur yapmayı
  • C. Niteliklerin yalnız sınıfın kurala uyan metotlarıyla değişmesini
  • D. Nesneyi kopyalanamaz hâle getirmeyi
  • E. Sınıfın metot sayısını azaltmayı

Madde 5. Aşağıdaki program çalıştırıldığında ne olur?

class Account:
    def __init__(self, balance):
        if balance < 0:
            raise ValueError("Bakiye negatif olamaz")
        self._balance = balance

acc = Account(100)
acc._balance = -50
print(acc._balance)
  • A. ValueError verir, çünkü __init__ içindeki kural her atamada yeniden çalışır
  • B. -50 yazdırılır, çünkü kural yalnız __init__ içinde denetleniyor
  • C. AttributeError verir, çünkü _balance private bir niteliktir
  • D. 100 yazdırılır, çünkü alt çizgili nitelik dışarıdan değiştirilemez
  • E. -50 yazdırılır ve Python ekrana bir uyarı basar

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. Sınıfın iç ayrıntısı olduğunu bildiren bir adlandırma geleneğidir
  • D. Niteliğin ad dönüştürmesine gireceğini gösterir
  • 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 = name
        self.price = price

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        if value <= 0:
            raise ValueError("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. Setter çalışır ve ValueError yükseltir; _price değişmez
  • E. _price değerini hata vermeden -50 yapar

Madde 8. price adlı bir property yazarken hangi üç ad aynı olmalıdır?

  • A. __init__ parametresinin adı, getter’ın adı ve dosyanın adı
  • B. Getter metodunun adı, iç niteliğin adı ve parametrenin adı
  • C. Sınıfın adı, property’nin adı ve iç niteliğin adı
  • D. Yalnız iki ad aynı olmalıdır; setter serbestçe adlandırılabilir
  • E. Getter metodunun adı, @price.setter dekoratöründeki ad ve setter metodunun adı

Madde 9. __init__ içinde self._price = price yerine self.price = price yazmanın gerekçesi nedir?

  • A. Başlangıç değeri de setter’dan geçer
  • B. Nesne oluşturmayı hızlandırır
  • C. Property tanımını zorunlu kılar
  • D. Nesneyi değiştirilemez yapar
  • E. _price adını dışarıdan gizler

Madde 10. Aşağıdaki metot neden güvenli değildir?

def transfer_to(self, other, amount):
    self._balance -= amount
    if self._balance < 0:
        raise ValueError("Yetersiz bakiye")
    other._balance += amount
  • A. Kontrol değişiklikten sonra yapıldığı için bakiye eksiye düşmüş kalır
  • B. raise bir metot içinde kullanılamaz
  • C. amount negatif olabileceği için tür denetimi gerekir
  • 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

Madde 1. Doğru: B. balance >= 0 ya da 0 <= score <= 100 gibi kurallar nesne kullanıldığı sürece doğru kalmalıdır. Niteliklerin değeri değişir, kural değişmez.

  • A yanlış: Sınıf niteliği paylaşılan veridir; kural değildir.
  • C yanlış: Metodun kaç parametre aldığını imzası belirler; bu ayrı bir konudur.
  • D yanlış: Hiç değişmeyen değer sabittir; invariant’ta ise değer değişebilir, değişmeyen kuraldır.
  • E yanlış: Kuralı korumanın yolu çoğu zaman hata yükseltmektir; amaç geçersiz değeri 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ış: Kod değeri kendiliğinden düzeltmez; hata yükseltir.
  • B yanlış: if kontrolü raise ile nesnenin oluşmasını 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. Product("Klavye", -100) çağrısının hemen hata vermesi, negatif fiyatın sistemde dolaşmasından iyidir. Hata ortaya çıktığı yere yakın bildirilirse nedenini bulmak kolaylaşır; geç fark edilen yanlış değer ise ilerideki raporları bozar.

  • B yanlış: Değeri kullanıldığı yerde denetlemek de akla yatkın görünebilir. Ama ilke tam da bu ertelemeye karşıdır.
  • C yanlış: “Fast” sözcüğü hızı çağrıştırdığı için bu anlam akla gelebilir. Ama tam tersi savunulur: doğrulama erkene alınır, kaldırılmaz.
  • D yanlış: Hatalarla çabuk başa çıkmanın yolu hepsini yakalamak gibi görünebilir. Ama her hatayı yakalamak sorunu gizler; bu ilkeye aykırıdır.
  • E yanlış: “Fail fast” adı, programın hata anında hemen çökmesi gerektiği izlenimini verir. Ama istisna yakalanıp işlenebilir, program sonlanmak zorunda değildir. İlke yalnız sorunun erken bildirilmesini ister.

Madde 4. Doğru: C. Python erişimi engellemez. Kuralı, niteliği değiştiren metotlar ve alt çizgi geleneğine uyan programcılar korur.

  • A yanlış: Python’da böyle bir engel yoktur; _balance adına da __balance adına da dışarıdan ulaşılabilir.
  • B yanlış: Çoğu nesnenin nitelikleri değişmek zorundadır; denetlenen, nasıl değiştikleridir.
  • D yanlış: Kopyalama ayrı bir konudur.
  • E yanlış: Kapsülleme çoğu zaman metot ekler.

Madde 5. Doğru: B. Tek alt çizgi erişimi engellemez: _balance bir adlandırma geleneğidir, Python bunu zorlamaz. Kontrol yalnız nesne oluşturulurken bir kez çalıştı. Dışarıdan yapılan doğrudan atama bu kontrolden geçmez. Kuralın her değişiklikte denetlenmesi için değişikliği bir metotla ya da property ile yapmak gerekir.

  • A yanlış: Kural sınıfın içinde yazıldığı için niteliği her zaman koruduğu düşünülebilir. Ama __init__ yalnız nesne oluşturulurken bir kez çalışır. Sonraki atamalar onu yeniden çalıştırmaz.
  • C yanlış: Alt çizgi “dokunmayın” uyarısı gibi okunduğu için erişimi engellediği sanılabilir. Ama Python’da dışarıdan erişimi engelleyen bir private belirteci yoktur; alt çizgi yalnız niyeti gösterir.
  • D yanlış: Alt çizgili ad korunuyormuş gibi göründüğü için atamanın etkisiz kaldığı sanılabilir. Ama atama başarılı olur ve değer gerçekten -50 olur.
  • E yanlış: Geleneğe aykırı bir kullanımın en azından uyarı vereceği beklenebilir. Ama Python alt çizgili bir ada yapılan atama için uyarı vermez. Geleneğe uymak programcıya kalır.

Madde 6. Doğru: C. Alt çizgi yalnız niyeti gösterir; Python erişimi teknik olarak engellemez. Bu geleneğe uyulmasını Python zorlamaz; kodu yazan ekip kendi arasında uyar.

  • A yanlış: Alt çizgi başka dillerdeki private sözcüğünün karşılığıymış gibi anlatıldığında bu yanılgı doğar. Python’da böyle bir erişim belirteci yoktur; dışarıdan okuma da yazma da yapılabilir.
  • B yanlış: Alt çizgili ad çoğunlukla bir property arkasında durduğu için bu bağ kurulabilir. property ile birlikte sık kullanılır, ama alt çizgi tek başına bunu söylemez.
  • D yanlış: Tek ve çift alt çizgi kolayca karıştırılır. Ad dönüştürmesi çift alt çizgide olur.
  • E yanlış: Alt çizgi sınıfa ait bir işaret gibi görünebilir. Ama niteliğin nesneye ait olduğunu self. ile atanması gösterir.

Madde 7. Doğru: D. property sayesinde normal görünen atama bir metot çağrısına dönüşür ve doğrulamadan geçer.

  • A yanlış: Python price adına yapılan atamayı setter’a gönderir; nesnede aynı adlı yeni bir nitelik oluşmaz, gölgeleme olmaz.
  • B yanlış: Setter tanımlı olduğu için atama yapılabilir.
  • C yanlış: Atama setter’ı çağırır ve hata yükselir.
  • E yanlış: Setter bunu engeller.

Madde 8. Doğru: E. Üçü de dışarıdan kullanılan price adını taşır. Setter’a farklı ad vermek en sık yapılan hatalardandır.

  • A yanlış: Dosya adının bir ilgisi yoktur.
  • B yanlış: İç nitelik genellikle _price olur; parametre adı serbesttir.
  • C yanlış: Sınıf adının property adıyla ilgisi yoktur.
  • D yanlış: Setter farklı adla yazılırsa property’ye bağlanmaz ve atama çalışmaz.

Madde 9. Doğru: A. Başlangıç değeri de setter’daki doğrulamadan geçer ve kural tek yerde kalır. Aynı kuralı iki yerde yazarsanız zamanla ikisi birbirini tutmayabilir. Kural tek yerdeyse değiştiğinde yalnız orayı güncellersiniz.

  • B yanlış: self.price daha sade göründüğü için daha hızlı sanılabilir. Tersine, araya bir metot çağrısı girer; gerekçe hız değildir.
  • C yanlış: Satır bir property kullandığı için onu tanımlatan şey sanılabilir. Ama property zaten tanımlıdır; bu satır onu kullanır.
  • D yanlış: Doğrulama nesneyi koruduğu için onu kilitlediği düşünülebilir. Ama setter niteliği kurala uyarak değiştirmek için vardır; nesneyi değiştirilemez yapmaz.
  • E yanlış: Alt çizgili ad satırdan kalktığı için gizlenmiş gibi görünür. Ama ad yine erişilebilir. Bu satırın yararı, doğrulamanın tek yerde kalmasıdır.

Madde 10. Doğru: A. Hata yükseldiğinde self._balance zaten negatife düşmüştür; 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.

  • B yanlış: raise örnekleri çoğunlukla fonksiyonlarda görüldüğü için metotta kullanılamayacağı sanılabilir. Ama kullanılabilir; sorun raise satırının değişiklikten sonra gelmesidir.
  • C yanlış: Negatif amount gerçekten ayrı bir eksik olduğu için cazip gelir. Ama bu bir tür denetimi değil, değer denetimidir; metodu güvensiz yapan da işlemlerin sırasıdır.
  • D yanlış: İstisna işlemi iptal ediyormuş gibi göründüğü için yapılan değişikliğin de geri alınacağı sanılabilir. Ama Python değişikliği kendiliğinden geri almaz; bakiye eksi kalır.
  • E yanlış: Alt çizgili bir niteliğe başka bir nesne üzerinden erişmek kurala aykırı görünebilir. Ama 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 kimse fark etmeden kalıcı olur.
  • E yanlış: İlk adım geçip ikincisi düşerse nesne yine yarım kalır.

Tek sayfa özet

  • Değişmez koşul (invariant), nesne kullanıldığı sürece her an doğru kalması gereken kuraldır: stock >= 0 gibi.
  • Kuralı ilk __init__ denetler. Kurala uymayan değer gelirse raise nesnenin oluşmasını engeller.
  • Kapsülleme, niteliklerin yalnız sınıfın metotlarıyla, kurala uyarak değişmesidir.
  • _stock gibi tek alt çizgili bir ad “bu ad içeride kullanılır” der, ama erişimi engellemez. Python’da private belirteci yoktur.
  • property, pen.price = 12 gibi sade bir atamanın arkasına denetim koyar. Setter’ı olmayan bir property’ye değer atanamaz.
  • Getter, @price.setter ve setter aynı adı taşır. __init__ ilk değeri setter’la atarsa doğrulama tek yerde kalır.
  • Önce bütün ön koşulları denetleyin, sonra durumu değiştirin. raise yapılmış değişikliği geri almaz.

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üllemede amacın erişimi yasaklamak değil, niteliklerin yalnız sınıfın metotlarıyla değişmesi olduğunu açıklar.
  • Tek alt çizginin bir adlandırma geleneği olduğunu ve Python’ın dışarıdan erişimi engellemediğini gösterir.
  • property ve setter tanımlar; üç adın aynı olması gerektiğini bilir.
  • __init__ içinde setter’ı kullanarak doğrulamayı tek yerde tutar.
  • Önce ön koşulları sınayıp sonra durumu değiştirerek yarım güncellemeyi önler.
Back to top