Python’da nesne zihinsel modeli: kimlik, değer, durum ve referans
Bu haftadan itibaren dersin ana konusu nesne tabanlı programlamadır. Kendi veri türlerimizi tanımlamak için kullanacağımız class deyimine gelecek hafta geçeceğiz. Önce Python’da bir nesnenin ne olduğunu, 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örelim.
Bu bölümün kapsamı
NoteSınavda sorulur
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
is ile == 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ı
NoteSınav dışı (bilgi amaçlı)
id() değerinin bellek adresiyle ilişkisi
Çöp toplama (garbage collection) ayrıntıları
CPython’ın interning politikasının ayrıntıları
Bu başlıklar konunun devamıdır; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.
Değişken bir kutu değilse nedir?
C’de int x = 5; yazdığınızda x bellekte bir kutuydu; x = 6; o kutunun içini değiştirirdi. Python’da da değişkeni içine değer konan bir kutu gibi düşünmek başta işe yarar. Ama iki ad aynı nesneyi gösterdiği anda bu benzetme yetersiz kalır. Onun yerine tek cümlelik bir model kullanacağız: ad bir nesneye bağlanır. Dersin geri kalanında hep bu modelden yola çıkacağız.
“Değişken kutudur” modeli neden yetersiz kalır?
Python’da adlarla nesneler arasındaki ilişkiyi anlamak için kutudan daha doğru bir modele ihtiyacımız var:
Değişken adı, bir nesneye bağlanır.
score =80
satırında score adı, değeri 80 olan bir int nesnesine bağlanır. Daha sonra:
score =90
yazdığımızda 80 nesnesinin içi 90 olmaz; score adı değeri 90 olan başka bir nesneye yeniden bağlanır.
Python veri modelinde: kimlik, tür ve değer
Python’ın resmî dil başvurusuna (language reference) göre her nesnenin bir kimliği (identity), bir türü (type) ve bir değeri (value) vardır. Tür, nesneyle hangi işlemleri yapabileceğinizi ve değerinin değişip değişemeyeceğini belirler.
Nesnenin kimlik, tür ve değeri
Burada:
type(scores) nesnenin türünü,
id(scores) nesne var olduğu sürece onu ötekilerden ayıran kimlik değerini,
scores ise nesnenin değerini, yani içeriğini gösterir.
Important“Değer” ile “durum” aynı terim değildir
Değer (value) Python’ın her nesne için kullandığı genel terimdir: [70, 80] listesinin değeri içindeki iki sayıdır. Durum (state) ise OOP’de kullandığımız bir tasarım terimidir: nesnenin o anki verilerinden, nesnenin nasıl davranacağını etkileyenler. Örneğin bir banka hesabının bakiyesi onun durumudur; bakiye yetmezse para çekme işlemi reddedilir. Mutable bir nesnenin değeri değiştiğinde çoğu zaman “nesnenin durumu değişti” deriz. İki terim ilişkilidir ama birbirinin yerine kullanılmaz.
Noteid() 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ızca iki referansın aynı nesneyi gösterip göstermediğini görmek için kullanacağız. Bu iş için is daha okunaklıdır.
Aynı nesneye iki referans
product ve selected iki ayrı sözlük değildir, aynı sözlüğün iki adıdır. selected ile yapılan değişikliği product ile baktığımızda da görürüz.
Kimlik ve eşitlik aynı soru değildir
first == second: değerleri eşit mi?
first is second: aynı nesne mi?
first is third: aynı nesne mi?
Kimlik ve eşitlik
Importantis değer karşılaştırma operatörü değildir
Sayıları ve stringleri karşılaştırmak için == kullanın. is iki adın aynı nesneyi gösterip göstermediğini sorar. En sık value is None biçiminde, bir değerin None olup olmadığını denetlemek için kullanılır.
Küçük tamsayı ve string “interning” tuzağı
Öğrenciler genellikle şu deneyi yapar:
a =5b =5print(a is b)
ve bazı ortamlarda True görür. Bunun nedeni, Python uygulamasının küçük tamsayılar ve bazı stringler için her seferinde yeni nesne oluşturmayıp var olanı yeniden kullanabilmesidir (interning/caching). Bu, uygulamanın bellek ve zaman kazanmak için yaptığı bir optimizasyondur. “Aynı değerdeki sayılar aynı nesnedir” diye bir dil kuralı yoktur.
x =int("1000")y =int("1000")print(x == y) # güvenilir değer sorusuprint(x is y) # uygulama ayrıntısına bağlı olabilir
Warningis deneylerinden yanlış dil kuralı çıkarmayın
Nesnelerin bu şekilde yeniden kullanılmasına önbellekleme denir ve CPython’da, Pyodide’de ya da başka bir Python uygulamasında farklı işleyebilir. Bu yüzden sayıları ve stringleri == ile karşılaştırın. Bir is deneyinin sonucundan, uygulamanın nesneleri nasıl yeniden kullandığına dair genel bir kural çıkarmayın.
Nesnenin durumu
OOP’de bir nesnenin o anki verilerine, özellikle de nesnenin nasıl davranacağını etkileyenlere, onun durumu (state) deriz. 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ı kalabilir.
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ızca satış yapan fonksiyon mu değiştirebilmeli, yoksa programın her yerinden product["stock"] = -100 yazılabilmeli mi?
Davranış neden verinin yanında olmalı?
Program hata vermeden çalışır ama stok -3 olur. “Stok eksiye düşemez” iş kuralını denetleyen hiçbir satır yok.
Fonksiyona bir denetim ekleyelim:
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ştirebildiğini izlemek zorlaşabilir. 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.
Fonksiyon parametresi nesneyi kopyalamaz
tags parametresi labels ile aynı küme nesnesine bağlanır. Fonksiyon kümeyi değiştirince labels da değişmiş olur. Immutable bir sayıyla çalışırken ise parametreyi yeni bir değere bağlamak, fonksiyonu çağıran taraftaki adı etkilemez:
Alıştırma: Aynı mı, eşit mi?
Ardından koda c.append(3) satırını ekleyin ve a, b, c değerlerini yeniden yazdırın.
Alıştırma: Referans diyagramı
first = {"balance": 100}second = firstthird = {"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"]:raiseValueError("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.
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 Account tasarımına götürecek.
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.score = 80 ve ardından score = 90 yazıldığında ne olur?
A.score adlı kutunun içindeki 80 değeri 90 ile üzerine yazılır
B. İki ayrı score değişkeni oluşur
C. Hata verir; bir ada iki kez atama yapılamaz
D.score adı önce bir nesneye, sonra başka bir nesneye bağlanır
E. 80 değeri bellekten hemen silinir ve score geçersiz olur
Madde 2. Python veri modelinde her nesnenin sahip olduğu üç özellik nedir?
A. Durum, davranış ve sorumluluk
B. Sınıf, metot ve nitelik
C. Değer, kopya ve referans
D. Ad, adres ve boyut
E. Kimlik, tür ve değer
Madde 3. Bu derste “değer” ile “durum” terimleri nasıl ayrılır?
A. “Durum” nesnenin kimliğidir
B. İkisi eş anlamlıdır; yalnız farklı kitaplarda farklı yazılır
C. “Değer” veri modelinin, “durum” nesne tasarımının terimidir
D. “Değer” çalışma anında, “durum” derleme anında belirlenir
E. “Değer” yalnızca sayılar, “durum” yalnızca nesneler için kullanılır
A. Stok -3 olur; iş kuralı hiçbir yerde denetlenmediği için nesne geçersiz bir duruma düşer
B.amount parametresi product ile aynı adı taşıdığı için çakışma olur
C. Sorun yoktur; negatif stok geçerli bir durumdur
D.sell fonksiyonu sözlüğü değiştiremez; değişiklik kaybolur
E.KeyError yükselir
Madde 9. Sözlükte tutulan bir kaydı sınıfa taşımanın temel gerekçesi nedir?
A. Sınıflar veriyi değiştirilemez yaptığı için
B. Sınıflar sözlüklerden daha hızlı çalıştığı için
C. Sözlükler fonksiyonlara parametre olarak verilemediği için
D. Durum ve onu değiştiren davranış tek bir ad altında, aynı yapıda toplanır
E. Sözlükler yalnızca metin anahtar kabul ettiği için
Madde 10. Aşağıdaki iki program neden farklı davranır?
# Adef add_tag(tags): tags.add("oop")labels = {"python"}add_tag(labels)print(labels)# Bdef increase(number): number = number +1value =10increase(value)print(value)
A. A’da küme, B’de sayı kullanıldığı için Python birinde referans, diğerinde değer geçirir
B. İkisi de aynı davranır; 10 ve {'python'} yazdırır
C. A’da global bir değişken kullanıldığı için
D. B’de fonksiyon return içermediği için değişiklik kaybolur
E. A’da küme yerinde değişir; B’de yalnız yerel number adı yeniden bağlanır
TipCevaplar ve gerekçeler
Madde 1. Doğru: D. Atama bir kutunun içeriğini değiştirmez; adın hangi nesneyi gösterdiğini değiştirir. İlk nesnenin içi değiştirilmez.
A yanlış: C’deki int score; bir bellek kutusu gibi çalıştığı için bu model akla yatkındır. Ama kutu modeli burada yanıltır; int nesneleri değiştirilemez ve içleri güncellenmez.
B yanlış: İki atama iki ayrı nesneyle çalıştığı için iki değişken varmış gibi görünür. Oysa tek bir ad vardır; yalnız bağlandığı nesne değişir.
C yanlış: Bir adın bir kez bağlandıktan sonra sabit kaldığı düşünülebilir. Oysa yeniden atama geçerli ve olağandır.
E yanlış: Eski değerin artık kullanılmaması, onun hemen silindiği izlenimini verir. Ama ad her zaman geçerli bir nesneyi gösterir; eski nesneye ne olacağı ise ayrı bir konudur.
Madde 2. Doğru: E. Tür, nesneyle yapılabilecek işlemleri ve değerin değişip değişemeyeceğini belirler; kimlik nesneyi ötekilerden ayırır.
A yanlış: Bunlar OOP tasarım terimleridir; veri modelinin üç özelliği değildir.
B yanlış: Bunlar sınıf yazarken kullanılan kavramlardır; veri modelinin her nesne için saydığı özellikler değildir.
C yanlış: Kopya ve referans işlem biçimleridir; nesnenin özellikleri değildir.
D yanlış: Ad nesnenin parçası değildir, yalnızca nesneye bağlanır; dil de adres ve boyutu her nesnenin özelliği olarak tanımlamaz.
Madde 3. Doğru: C. “Değer” Python veri modelinin genel terimidir. “Durum” ise OOP’de nesnenin o anki verilerinden davranışını etkileyenleri anlatan tasarım terimidir. Mutable bir nesnenin değeri değişince çoğu zaman “durumu değişti” deriz; iki terim ilişkilidir ama birbirine karıştırılmamalıdır.
A yanlış: Durum da kimlik de nesneyi tanıtan özellikler olduğu için karıştırılabilir. Ama kimlik nesne yaşadığı sürece sabittir; durum değişebilir.
B yanlış: Günlük dilde iki sözcük birbirinin yerine kullanıldığı için eş anlamlı sanılabilir. Ama “değer” Python dil başvurusunun, “durum” nesne tasarımının terimidir.
D yanlış: C’deki derleme anı ile çalışma anı ayrımı bu eşleştirmeyi akla getirir. Python’da böyle bir derleme anı ayrımı yoktur.
E yanlış: Örneklerde “değer” çoğunlukla sayılar için geçtiğinden böyle bir ayrım sanılabilir. Oysa iki terim de her türden nesne için kullanılır.
Madde 4. Doğru: A.selected = product yeni bir sözlük oluşturmaz; aynı sözlüğün iki adı olur. Birinden yapılan değişiklik ötekinden de görülür.
B yanlış: Bu yazım selected["stock"] = selected["stock"] - 2 ile aynıdır ve geçerlidir.
C yanlış: Atama kopya oluşturmaz; iki ad aynı nesneyi gösterir.
D yanlış:is aynı nesne için True verir.
E yanlış: Aynı nesne değiştirildiği için product da yeni değeri gösterir.
Madde 5. Doğru: B.first ve second eşit içerikli ama ayrı nesnelerdir. third ise first ile aynı nesnedir.
A yanlış:third = first aynı nesneye ikinci bir ad bağlar.
C yanlış: Sözlüklerde == anahtarları ve değerleri karşılaştırır; ikisi de aynı olduğu için sonuç True olur.
D yanlış: İlk iki sonuç ters yazılmış.
E yanlış: Eşit içerik aynı nesne olmayı gerektirmez.
Madde 6. Doğru: C. Programda tek bir None nesnesi vardır; bu yüzden bir değerin None olup olmadığı kimlikle sorulur.
A yanlış: Her [] yeni bir liste oluşturur; bu karşılaştırma hep False verir.
B yanlış:x is int, x değerini int türünün kendisiyle karşılaştırır; bu anlamsızdır.
D yanlış: Sayı eşitliği için == kullanılır.
E yanlış: Metin eşitliği için == kullanılır; is sonucu uygulama ayrıntısına bağlı olabilir.
Madde 7. Doğru: B. Uygulama küçük tamsayıları yeniden kullanır. Bu önbellekleme davranışı CPython, Pyodide ya da başka bir uygulamada farklı görünebilir. Değer sorusu için == kullanılmalıdır.
A yanlış: Ortamlar arasında farklı sonuç görmek, birinin hatalı olduğu izlenimini verir. Dil önbelleklemeyi zorunlu tutmaz; uygulamaların farklı davranması bir bozulma sayılmaz.
C yanlış: İki operatör burada aynı sonucu verdiği için aynı işi yapıyormuş gibi görünür. Ama farklı sorulardır; sonuçların rastlantısal olarak örtüşmesi bunu değiştirmez.
D yanlış:is daha kesin bir karşılaştırma gibi göründüğü için tercih edilesi sanılabilir. Tersine, değer karşılaştırmasında güvenilir olan ==dır.
E yanlış: Deneyde True görüldüğü için sonucu her sayıya genellemek kolaydır. Ama int("1000") ile oluşturulan iki değer aynı nesne olmayabilir.
Madde 8. Doğru: A. Program hata vermeden çalışır. Eksik olan, stokun eksiye düşmesini engelleyen bir denetimdir.
B yanlış: Adlar farklıdır ve çakışma yoktur.
C yanlış: Stok eksiye düşemez; bu durumun hata vermeden kabul edilmesi asıl sorundur.
D yanlış: Fonksiyon aynı sözlük nesnesini değiştirir; değişiklik çağıran taraftan da görülür.
E yanlış:stock anahtarı mevcuttur.
Madde 9. Doğru: D. Program büyüdükçe sözlükte hangi anahtarların bulunması gerektiğini ve kaydı hangi fonksiyonların değiştirebildiğini izlemek zorlaşır. Sınıf, veriyi ve onu değiştiren fonksiyonları aynı yerde toplar.
A yanlış: Sınıftan oluşturulan nesnelerin verisi de değişebilir; sınıfla korunan şey, verinin uyması gereken kurallardır.
B yanlış: Gerekçe hız değil, verinin ve onu değiştiren kodun bir arada durmasıdır.
C yanlış: Verilebilir; bu bölümdeki sell(product, 5) çağrısı bunu yapıyor.
E yanlış: Sözlükler hashlenebilir her türü anahtar olarak kabul eder.
Madde 10. Doğru: E. Yerinde değiştirme paylaşılan nesneyi etkiler. B’de parametre adı yeni bir nesneye bağlanır; bu yalnız fonksiyonun kendi adını etkiler, çağıran taraftaki value değişmez. A {'python', 'oop'}, B 10 yazdırır.
A yanlış: C’deki değerle ve işaretçiyle geçirme ayrımı bu açıklamayı akla yatkın kılar. Ama iki çağrıda da argüman aynı biçimde aktarılır; fark fonksiyonun içinde yapılan işlemdedir.
B yanlış: B’nin davranışını A’ya da genellemek kolaydır. Ama A’daki add çağrısı kümeyi gerçekten değiştirir.
C yanlış:labels fonksiyonun dışında tanımlandığı için global gibi görünebilir. Ama labels parametreyle geçirilir; global bildirimi yoktur.
D yanlış: Sonucun dışarı çıkması için return gerektiği bilgisi burada yanlış yere uygulanır. return olsa bile dönen değer value adına atanmadıkça çağıranın adı değişmezdi; farkın nedeni yeniden bağlamadır.
Tek sayfa özet
Python veri modelinde her nesnenin kimliği, türü ve değeri vardır.
OOP’de mutable bir nesnenin zamanla değişen ve davranışını etkileyen verilerine durum deriz.
Değişken adı nesne değildir; nesneye bağlanır.
== eşitlik, is kimlik sorusudur.
Küçük tamsayı/string önbelleklemesi uygulama ayrıntısıdır; değer karşılaştırmasını == ile yapın.
Aynı mutable nesneye birden fazla referans olabilir; birinden yapılan değişikliği ötekiler de görür.
OOP’yi kullanmamızın bir nedeni, veriyi ve onu kurallara uyarak değiştiren fonksiyonları tek bir yapıda, sınıfta toplamaktır.
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ırır.
Aynı nesneye iki referans olduğunda bir addan yapılan değişikliğin diğerinden görüldüğünü gösterir.
is ile == sorularını ayırır ve is iş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ırır.