Python’da nesne zihinsel modeli: kimlik, değer, durum ve referans
Bu haftadan itibaren dersin ana konusu nesne tabanlı programlamadır. Python’da programın kullandığı her değer, yani her sayı, metin, liste ve sözlük, bir nesnedir (object). Kendi nesne türlerimizi tanımlamak için kullanacağımız class deyimine gelecek hafta geçeceğiz. Bu hafta değişken adlarının nesnelere nasıl bağlandığını ve aynı nesneye iki farklı addan erişmenin ne sonuç doğurduğunu göreceğiz. 3. haftada listelerle yaptığımız deneyleri bu kez bir kaydın üzerinde yineleyeceğiz.
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ı
- Değişken adının bir nesneye bağlanması
- Nesnenin kimlik, tür ve değer özellikleri
- Aynı nesneye iki referans olduğunda değişikliğin iki addan da görülmesi
isile==ayrımı- Küçük tamsayı ve string önbelleklemesinden dil kuralı çıkarılamayacağı
- Nesnenin durumu ve durumun hangi yollarla değiştiği
- Yerinde değiştirme ile yeniden bağlama ayrımı
id()değerinin bellek adresiyle ilişkisi- Çöp toplama (garbage collection) ayrıntıları
- CPython’ın hangi nesneleri yeniden kullandığının ayrıntıları
Bu başlıklar konunun devamıdır. İleride karşınıza çıkar, ama bu derste ezberlemeniz beklenmiyor.
Kırtasiyenin stok programındaki beş işi C’de nasıl yapardınız?
Bir kırtasiyenin stok programını yazıyorsunuz. Her ürünün bir adı ve depoda kaç tane kaldığını gösteren bir stok sayısı var. Program şu beş işi yapacak:
- Kalemin adını ve stok sayısını tek bir kayıtta tutmak
- Kasa ekranıyla depo ekranının aynı kaydı kullanması: kasada satılan kalem depo ekranında da eksilmeli
- Elinizdeki iki kaydın aynı kayıt mı, yoksa aynı bilgileri taşıyan iki ayrı kayıt mı olduğunu anlamak
- Satış fonksiyonu yazmak: fonksiyon, onu çağıran koddaki kaydın stoğunu azaltmalı
- Stoğun hiçbir zaman eksiye düşmemesini sağlamak
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. 3. haftadan beri bildiğiniz Python’la da cevap verebilirsiniz: “struct tanımlarım” da “sözlükte tutarım” da bir cevap.
Bölümde bu beş işin Python’da nasıl yürüdüğünü göreceğiz. Bölümün sonunda listenize döneceğiz.
Ad bir nesneye bağlanır
C’de struct Product pencil = {"Kalem", 10}; yazdığınızda pencil bellekte iki alanı olan bir kutuydu. pencil.stock = 8; o kutunun içindeki alanı değiştirirdi. Python’da aynı kaydı 3. haftada gördüğünüz sözlükle tutabilirsiniz:
product = {"name": "Kalem", "stock": 10}Bu satır önce bir sözlük nesnesi oluşturur, sonra product adını o nesneye bağlar. Kutu benzetmesi tek bir adla çalışırken işe yarar, ama iki ad aynı nesneyi gösterdiği anda yetmez. Bu derste şu modeli kullanacağız:
Değişken adı, bir nesneye bağlanır.
Model sayılar için de geçerli. 3. haftada gördüğünüz id(), bir nesnenin kimlik numarasını döndürüyordu. Hücrede score adına iki kez değer atanıyor ve her atamadan sonra id(score) yazdırılıyor. C’deki kutu modeli doğru olsaydı iki sayı aynı çıkardı. Python’da aynı mı çıkar?
score = 90 satırı 80 nesnesinin içini 90 yapmaz. score adını, değeri 90 olan başka bir nesneye bağlar. Buna yeniden bağlama denir.
İki farklı sayı. Sayıların kendisi her çalıştırmada değişebilir, önemli olan birbirinden farklı olmaları: score adı önce bir nesneyi, sonra başka bir nesneyi gösterdi.
Her nesnenin kimliği, türü ve değeri var
Python’ın resmî belgelerindeki veri modeline (data model) göre her nesnenin bir kimliği (identity), bir türü (type) ve bir değeri (value) vardır. Kimlik nesneyi ötekilerden ayırır ve nesne var olduğu sürece değişmez. Tür, nesneyle hangi işlemleri yapabileceğinizi ve değerinin değişip değişemeyeceğini belirler. Değer nesnenin içeriğidir.
Hücre kalem kaydının üç özelliğini yazdırıyor. Hangi satır kimliği, hangisi türü gösterir?
type(product) türü, id(product) kimliği, product ise değeri gösterir.
id() değerini bellek adresi diye ezberlemeyin
Python kodunu çalıştıran programa Python uygulaması denir. Bilgisayarınıza kurduğunuz CPython ve bu sayfadaki Pyodide birer uygulamadır. Bazı uygulamalarda id() ile bellek adresi arasında yakın bir ilişki olabilir, ama dil bunu garanti etmez. Biz id() değerini yalnız iki adın aynı nesneyi gösterip göstermediğini görmek için kullanacağız. Bu iş için is daha okunaklıdır.
İki ad, tek kayıt
C’de struct ataması alanları kopyalar: struct Product selected = pencil; satırından sonra iki ayrı kayıt olur. Kasa ekranıyla depo ekranının aynı kaydı kullanması için işaretçi gerekirdi: struct Product *selected = &pencil;.
- haftada Python’da
=işaretinin kopya almadığını, var olan nesneye bir ad daha bağladığını gördünüz. Buna aynı nesneye referans verme (aliasing) dedik. Sözlük de listeyle aynı davranır. Hücrede kasa ekranıselectedadıyla iki kalem satıyor.print(product)ne yazar?
selected = product yeni bir sözlük oluşturmaz. product ve selected aynı sözlüğün iki adıdır, bu yüzden selected ile yapılan değişikliği product ile bakınca da görürüz. C’de işaretçinin yaptığı işi Python’da düz bir atama yapar.
Gelecek hafta account, customer, order gibi adlar kendi yazdığınız sınıflardan oluşturulan nesneleri gösterecek. other = account satırı da hesabın kopyasını almaz, aynı hesaba bir ad daha bağlar. Hesap bir addan değiştirilirse öteki ad da yeni durumu görür.
Aynı kayıt mı, eşit kayıt mı?
C’de iki işaretçinin aynı kaydı gösterip göstermediğini işaretçileri == ile karşılaştırarak, iki kaydın aynı bilgileri taşıyıp taşımadığını ise alanları tek tek karşılaştırarak anlardınız. Python’da bu iki soru için iki ayrı işleç var. 3. haftada gördüğünüz gibi is iki adın aynı nesneyi gösterip göstermediğini, == iki değerin eşit olup olmadığını sorar.
Hücrede second, first sözlüğünün .copy() ile alınmış kopyası (3. haftada gördüğümüz, yeni bir nesne oluşturan kopya). third ise first ile atanıyor. Üç satır ne yazar?
İçeriği eşit iki nesne, iki ayrı kayıt olabilir. Aynı kaydı gösteren iki adı yalnız is ayırt eder.
first == second→True: iki sözlüğün anahtarları ve değerleri eşit.first is second→False:.copy()yeni bir sözlük oluşturdu.first is third→True:third = firstaynı sözlüğe bir ad daha bağladı.
Küçük sayılarda is neden bazen True verir?
Hücrede aynı değerdeki sayılar iki kez karşılaştırılıyor: önce 5 ile 5, sonra metinden dönüştürülen iki 1000. Üç satır ne yazar?
Python uygulaması küçük tamsayılar ve bazı stringler için her seferinde yeni nesne oluşturmaz, var olanı yeniden kullanır. Buna önbellekleme (caching) denir, stringlerde interning adı da kullanılır. Uygulama bunu hız ve bellek kazanmak için yapar. “Aynı değerdeki sayılar aynı nesnedir” diye bir dil kuralı yoktur: CPython, Pyodide ya da başka bir uygulama hangi nesneleri yeniden kullanacağına kendisi karar verir.
== ile karşılaştırın
Bir is deneyinde True görmeniz, başka bir sayıda ya da başka bir uygulamada da True göreceğiniz anlamına gelmez. Değer sorusunu == ile sorun. is en sık value is None biçiminde, bir değerin None olup olmadığını denetlemek için kullanılır. Programda tek bir None nesnesi olduğu için bu soru kimlikle sorulur.
Fonksiyon kaydın kopyasını değil kendisini alır
C’de bir struct’ı fonksiyona verdiğinizde fonksiyon onun kopyasını alırdı. Satışın çağıran koddaki kayda yansıması için işaretçi verirdiniz: sell(&pencil, 2). 4. haftada Python’da parametre adının, çağıranın verdiği nesneye bağlandığını gördünüz.
Hücredeki sell fonksiyonu sözlüğü işaretçi olmadan alıyor. Fonksiyonun içindeki ad product, dışarıdaki ad pencil. Son satır ne yazar?
Çağrıda product parametresi pencil sözlüğüne bağlanır, Python sözlüğü kopyalamaz. Fonksiyonun sözlükte yaptığı değişikliği çağıran kod da görür.
Parametreye yeni değer atamak çağıranı etkilemez
C’de void increase(int n) fonksiyonu çağıranın değişkenini değiştiremezdi. Python’da aşağıdaki fonksiyon parametresine yeni bir değer atıyor ve içeride 11 yazdırıyor. Son satır ne yazar?
number = number + 1 satırı yeni bir int nesnesi oluşturur ve yalnız fonksiyonun yerel number adını ona bağlar. value adı hâlâ eski nesneyi gösterir.
İki fonksiyonun farklı davranmasının nedeni sayı ile sözlüğün türü değil, fonksiyonun içinde yapılan iştir. sell sözlüğü yerinde değiştirdi: aynı nesnenin içeriği değişti. increase ise adı yeniden bağladı: yerel ad başka bir nesneyi göstermeye başladı. sell içinde product = {"name": "Silgi", "stock": 0} yazsaydık bu da yalnız yerel adı yeniden bağlardı ve pencil değişmezdi.
Nesnenin durumu
OOP’de bir nesnenin o anki verilerine, özellikle de nesnenin nasıl davranacağını etkileyenlere, onun durumu (state) deriz. Değiştirilebilir (mutable) bir nesnenin durumu program çalışırken değişebilir.
{"name": "Kalem", "stock": 8} sözlüğünde stock değeri durumun bir parçasıdır. Stok 8’den 7’ye inerse nesnenin durumu değişir, kimliği ve türü aynı kalır. Stok 0 olunca satış yapılamaz: durum, nesneyle neyin yapılabileceğini belirler.
Değer (value) Python veri modelinin her nesne için kullandığı terimdir: [70, 80] listesinin değeri içindeki iki sayıdır. Durum (state) ise OOP’de nesneyi tasarlarken kullandığımız terimdir: nesnenin o anki verilerinden davranışını etkileyenler. Bir banka hesabının bakiyesi onun durumudur, bakiye yetmezse para çekilemez. Mutable bir nesnenin değeri değiştiğinde çoğu zaman “nesnenin durumu değişti” deriz. İki terim birbirine yakındır, ama ikisini birbirinin yerine kullanmayacağız.
OOP’de sık sık şu soruyu soracağız:
Bu durumun hangi yollarla değişmesine izin veriyoruz?
Stok örneğinde: stock değerini yalnız satış yapan fonksiyon mu değiştirebilmeli, yoksa programın her yerinden product["stock"] = -100 yazılabilmeli mi?
Davranış neden verinin yanında olmalı?
Az önceki sell fonksiyonuyla, stokta iki kalem varken beş kalem satalım. Son satır ne yazar?
Program hata vermeden çalışır. “Stok eksiye düşemez” kuralını denetleyen tek bir satır bile yok.
- haftada gördüğümüz
raiseile fonksiyona bir denetim ekleyelim. Hücre ne yazar? Satış reddedilirse stok değişir mi?
Denetim, stoğu değiştiren satırdan önce çalışır. Satış reddedilince stok olduğu gibi kalır.
Bu küçük örnekte kural sell fonksiyonunun içinde duruyor. Ama program büyüdükçe product sözlüğünde hangi anahtarların bulunması gerektiğini ve sözlüğü hangi fonksiyonların değiştirdiğini izlemek zorlaşır. Başka bir dosyadaki bir fonksiyon product["stock"] değerini hiç denetlemeden değiştirebilir. C’de bir struct ile onu değiştiren fonksiyonlar da böyle ayrı ayrı dururdu.
OOP’de nesnenin verisine durum, bu veriyle yapılan işlere, yani veriyi okuyan ve değiştiren fonksiyonlara davranış denir. Gelecek hafta göreceğimiz sınıf (class), durumu ve davranışı tek bir ad altında, aynı yapıda toplar.
Soruya dönelim: stok programının beş işi
Bölümün başında kırtasiyenin stok programındaki beş işi C’de nasıl yapacağınızı sormuştuk. Python’daki karşılıkları şöyle:
| İş | C’de ya da önceki haftalarda | Bu haftaki Python yolu | Dikkat |
|---|---|---|---|
| Kaydı tutmak | struct Product pencil = {"Kalem", 10}; |
product = {"name": "Kalem", "stock": 10}: product adı bir sözlük nesnesine bağlanır |
Yeni atama nesneyi değiştirmez, adı başka bir nesneye bağlar |
| Kasa ve depo aynı kaydı kullanır | İşaretçi: struct Product *selected = &pencil; |
selected = product |
= hiçbir türde kopya almaz. Ayrı bir kayıt için .copy() gerekir |
| Aynı kayıt mı, eşit mi? | İşaretçileri == ile, alanları tek tek karşılaştırmak |
is kimliği, == değeri sorar |
Sayıları ve metinleri is ile karşılaştırmayın |
| Satış fonksiyonu | Fonksiyona işaretçi vermek: sell(&pencil, 2) |
sell(product, 2): parametre aynı sözlüğe bağlanır |
Parametreye yeni değer atamak çağıranın adını değiştirmez |
| Stok eksiye düşmesin | sell içinde if ile denetlemek |
sell içinde raise ValueError |
Kodun başka bir yeri yine product["stock"] = -100 yazabilir |
Listenizi tabloyla karşılaştırın. 2. iş için “selected = product yazarım” dediyseniz Python’da doğru cevabı vermişsiniz, ama C’de aynı satır iki ayrı kayıt oluştururdu: C’de struct ataması kopyalar, Python’da = paylaşır. 4. işte C’de işaretçi gerekirdi. Python’da gerekmez, çünkü parametre zaten verilen nesneye bağlanır. 5. iş iki dilde de yarım kalıyor: kural sell içinde, ama kayıt programın her yerinden değiştirilebilir. Gelecek hafta veriyi ve onu değiştiren fonksiyonları bir sınıfta toplayacağız. 8. haftada da kuralı sınıfın içinde koruyacağız.
Alıştırma: Aynı mı, eşit mi?
Hücre dört satır yazdırıyor. Önce dört satırı tahmin edin, sonra çalıştırın.
Ardından koda c.append(3) satırını ekleyin ve a, b, c değerlerini yeniden yazdırın. Hangi adların değiştiğini açıklayın.
Alıştırma: Referans diyagramı
first = {"balance": 100}
second = first
third = {"balance": 100}Üç değişken adını ve kaç ayrı sözlük nesnesi olduğunu gösteren bir diyagram çizin. Ardından second["balance"] += 50 satırından sonra hangi adlarla bakınca 150 görüleceğini açıklayın.
Alıştırma: Geçersiz durumu engelle
if amount > account["balance"]:
raise ValueError("Yetersiz bakiye")Sıra sizde: Prosedürel hesabı incele
account = {
"owner": "Ada",
"balance": 500,
}Veriyi bir sözlükte, işlemleri ayrı fonksiyonlarda tutan bu yaklaşıma prosedürel yaklaşım denir. C’de bir struct ve onu işleyen ayrı fonksiyonlarla çalışmak da aynı yaklaşımdır. Para yatırma, para çekme ve bakiye sorgulama fonksiyonlarını yazın. Geçersiz tutarlarda ValueError yükseltin ve başarısız bir işlemden sonra sözlüğün değişmediğini doğrulayın.
Çalışır kod görevi: Üç durumu deneyin: normal para çekme, yetersiz bakiye ve negatif tutar. Bakiyeyi en az üç assert ile denetleyin. 5. haftadaki gibi assert satırı, koşul yanlışsa AssertionError yükseltir: assert account["balance"] == 400.
Sonunda şu soruyu cevaplayın:
Bu üç fonksiyonu ve hesabın verisini tek bir yapıda toplarsak ne kazanırız?
Bu sorunun cevabı bizi gelecek hafta class BankAccount tasarımına götürecek.
Tek sayfa özet
- Python’da her değer bir nesnedir. Değişken adı nesne değildir, bir nesneye bağlanır.
- Yeni atama nesnenin içini değiştirmez, adı başka bir nesneye bağlar. Buna yeniden bağlama denir.
- Python veri modelinde her nesnenin kimliği, türü ve değeri vardır.
b = akopya almaz. İki ad aynı nesneyi gösterir ve bir addan yapılan değişikliği öteki de görür. C’de işaretçinin yaptığı işi Python’da düz atama yapar.==eşitlik,iskimlik sorusudur. Eşit içerikli iki nesne iki ayrı nesne olabilir.- Küçük tamsayı ve string önbelleklemesi uygulamanın tercihidir. Değer karşılaştırmasını
==ile yapın,is’iNonedenetimi gibi kimlik soruları için kullanın. - Fonksiyon çağrılınca parametre adı verilen nesneye bağlanır. Fonksiyon nesneyi yerinde değiştirirse çağıran da görür, parametreyi yeniden bağlarsa çağıranın adı değişmez.
- OOP’de nesnenin davranışını etkileyen, zamanla değişen verilerine durum deriz.
- Sözlükteki veriyi programın her yeri değiştirebilir. Veriyi ve onu kurallara uyarak değiştiren fonksiyonları tek bir yapıda, sınıfta toplamamızın bir nedeni bu.
Bu bölümün kazanımları
Bu bölümü bitiren öğrenci:
- Değişken adının bir nesneye bağlandığını, kutu olmadığını açıklar.
- Bir nesnenin kimlik, tür ve değer özelliklerini ayırt eder.
- Aynı nesneye iki referans olduğunda bir addan yapılan değişikliğin diğerinden görüldüğünü gösterir.
isile==sorularını ayırt eder veisişlecini yalnızca kimlik sorusu için kullanır.- Küçük tamsayı ve string önbelleklemesinden dil kuralı çıkarılamayacağını açıklar.
- Nesnenin durumunu tanımlar ve durumun hangi yollarla değişebileceğini sorgular.
- Mutable nesnede yerinde değişiklik ile immutable nesnede yeniden bağlamayı ayırt eder.