Ders 02 / 16
Demetler
Altı işlemden üçü demette istisnayla düşer ama iç öğeyi değiştiren işlem demette de geçer; değişmezlik bir katman derinliğindedir ve karşılığında kabın kazandığı yetki, beş adayın üçünde görüldüğü gibi anahtar olabilmektir.
İçindekiler
Önceki dersin bütün ölçümleri listenin değiştirilebilir olmasına dayanıyordu: yuva eklenebiliyor, silinebiliyor, yeniden dizilebiliyordu. Var edilen nesne sayısı çoğu biçimde sıfırdı, çünkü liste yalnız yuvaları düzenliyordu.
Bu ders aynı yuvaları değişmez bir kapta yeniler. Demet (tuple) de sıralı bir yuva dizisidir, ama kurulduktan sonra yuvaları yeniden yazılamaz. Soru iki parçalıdır ve ikisi de sayılabilir: değişmezlik hangi işlemi gerçekten imkânsız kılıyor, ve bu imkânsızlığın karşılığında kap hangi yetkiyi kazanıyor?
Değişmezlik Neyi Kapatır
Değişmezlik kavramının kendisi bu kursun konusu değildir. Programlama Temelleri kursunda değiştirilebilir ile değiştirilemez nesnenin ayrımı kuruldu; Yazılım Tasarımı ve Mimari İlkeler kursu değişmezliği paylaşılan durumu ortadan kaldıran bir tasarım tekniği olarak ele aldı. İkisi de burada tekrarlanmaz. Burada ölçülen şey daha dar ve daha somut: aynı işlem dizisi iki kapta çalıştırıldığında hangi satırlar geçiyor, hangileri istisnayla düşüyor.
Ayrımın nerede durduğunu baştan söylemek gerekir, çünkü ölçüm tam oraya bakacak. Bir demet kendi yuvalarını korur. Yuvaların gösterdiği nesnelerin içine karışmaz ve karışamaz — onlar ayrı nesnelerdir ve kendi kurallarına tabidir. Demetin verdiği güvence bu yüzden “içindekiler değişmez” değil, “hangi nesneye baktığım değişmez” güvencesidir.
- KL13 — Altı işlem her iki kapta da aynı sırayla ve her seferinde tazeden kurulmuş bir kapla denenir; kaplar aynı değerlerden üretilir.
- KL14 — Sonuç sütunu üç değerden birini alır: işlem geçtiyse sonucun aynı kap mı yoksa yeni kap mı olduğu, geçmediyse düşen istisnanın sınıf adı.
- KL15 — “Aynı kap” kararı işlemden önce tutulan bağla
iskarşılaştırmasıdır. - KL16 — Sıralama bir anahtar işleviyle yapılır; öğe sınıfına karşılaştırma yöntemi eklenmez ve sayma düzeni değişmez.
- KL17 — İç öğeyi değiştiren satır kabın kendisine hiç dokunmaz; yalnız bir yuvanın gösterdiği nesnenin alanına yazar.
"""Ayni alti islem listede ve demette: degismezlik neyi imkansiz kiliyor.""" SAYAC = {"uretilen": 0} class Oge: def __init__(self, deger): SAYAC["uretilen"] += 1 self.deger = deger def __repr__(self): return f"Oge({self.deger})" def sifirla(): SAYAC["uretilen"] = 0 def uretilen(): return SAYAC["uretilen"] def deger(o): return o.deger HAVUZ = [Oge(i) for i in (3, 1, 4)] def yerine_yaz(k): k[0] = Oge(9) return k def sona_ekle(k): k.append(Oge(9)) return k def sil(k): del k[0] return k def yerinde_topla(k): k += type(k)((Oge(9),)) return k def ic_ogeyi_degistir(k): k[0].deger = 9 return k def sirala(k): return sorted(k, key=deger) ISLEMLER = (("k[0] = yeni", yerine_yaz), ("k.append(yeni)", sona_ekle), ("del k[0]", sil), ("k += (yeni,)", yerinde_topla), ("k[0].deger = 9", ic_ogeyi_degistir), ("sorted(k, ...)", sirala)) print(f"{'işlem':<18s}{'listede':<16s}{'demette'}") for ad, islem in ISLEMLER: satir = [] for tip in (list, tuple): k = tip(Oge(o.deger) for o in HAVUZ) once = k try: sonuc = islem(k) satir.append("aynı kap" if sonuc is once else "yeni kap") except Exception as e: satir.append(type(e).__name__) print(f"{ad:<18s}{satir[0]:<16s}{satir[1]}") print() demet = tuple(HAVUZ) sifirla() buyuk = demet + (Oge(9),) print(f"demete ekleme: var edilen {uretilen()} | eski yuva {len(demet)} -> " f"yeni yuva {len(buyuk)}") print(f"buyuk is demet -> {buyuk is demet} | " f"buyuk[0] is demet[0] -> {buyuk[0] is demet[0]}") print() print(f"demet[0].deger okuma: {demet[0].deger}") demet[0].deger = 9 print(f"iç öğe değiştirildikten sonra: {demet[0].deger} | " f"demet[0] is HAVUZ[0] -> {demet[0] is HAVUZ[0]}")
işlem listede demette k[0] = yeni aynı kap TypeError k.append(yeni) aynı kap AttributeError del k[0] aynı kap TypeError k += (yeni,) aynı kap yeni kap k[0].deger = 9 aynı kap aynı kap sorted(k, ...) yeni kap yeni kap demete ekleme: var edilen 1 | eski yuva 3 -> yeni yuva 4 buyuk is demet -> False | buyuk[0] is demet[0] -> True demet[0].deger okuma: 3 iç öğe değiştirildikten sonra: 9 | demet[0] is HAVUZ[0] -> True
Üç Satır Düşüyor, Üç Satır Geçiyor
Altı işlemin üçü demette istisnayla düşüyor ve üçü geçiyor. Düşen üçü aynı şeyi denedi:
var olan bir yuvayı yeniden yazmak, yuva eklemek ya da yuva silmek. Düşme biçimleri
ayrı. Yuvaya yazmak ve yuva silmek TypeError veriyor — bu iki yazım demette de bir
anlama gelir, ama demet o anlamı desteklemiyor. Sona ekleme ise AttributeError veriyor:
demette append diye bir yöntem yoktur. Birincisi “yapamam”, ikincisi “böyle bir şey
bilmiyorum” der.
Geçen üç satırın ikisi zaten kabı değiştirmiyordu. sorted her iki kapta da yeni kap
veriyor; zaten hep yeni bir liste döndürür ve kaynağa dokunmaz. k += (yeni,) satırı ise
ayrımın tam ortasında duruyor: listede aynı kap, demette yeni kap. Yazım aynı, sonuç
aynı görünüyor, ama biri var olan kabı uzatıyor, öbürü yeni bir kap kurup adı ona bağlıyor.
Demet değişmez olduğu için yerinde büyütme demette bir uzatma değil, bir yeniden
kurmadır.
Ortadaki sayılar bu yeniden kurmanın faturasını veriyor: var edilen 1, ve o bir tane de
eklenen öğenin kendisi. Kap büyüyor — üç yuvadan dört yuvaya — ama kabın içindeki eski üç
nesne yeniden üretilmiyor. buyuk is demet False, yani kap gerçekten yeni; ama
buyuk[0] is demet[0] True, yani öğe eski. Bir demeti bin kez bir öğe ekleyerek
büyütmek, bin kap kurmak demektir; içindeki nesneleri bin kez kopyalamak demek değildir.
Son iki satır bu dersin en kritik ölçümüdür. k[0].deger = 9 demette geçiyor — hem de
hiçbir uyarı vermeden. Demet kendi yuvasını korudu: yuva hâlâ aynı nesneye bakıyor ve
demet[0] is HAVUZ[0] bunu doğruluyor. Ama o nesnenin içi değişti; okunan değer 3
iken 9 oldu. Demek ki değişmezlik bir katman derinliğindedir: kabın verdiği güvence
yuvaların hangi nesneyi gösterdiğiyle sınırlıdır, o nesnelerin durumuyla değil.
Değişmezliğin Karşılığı
Kapatılan üç işlemin karşılığında bir yetki açılır. Bir nesnenin karmalanabilir olması, ondan sabit bir karma değeri hesaplanabilmesi demektir; bu değer nesnenin ömrü boyunca değişmemelidir. Yuvaları yeniden yazılabilen bir kapta böyle bir söz verilemez, çünkü kap değiştiğinde karma değeri de değişirdi ve nesne konduğu yerde kaybolurdu. Demet bu sözü verebilir — ama yalnız içindekiler de verebiliyorsa.
- KL18 — Karmalanabilirlik doğrudan sınanır: karma değeri alma denenir ve düşen istisnanın sınıf adı yazılır. Karma değeri basılmaz; ölçülen şey değerin kendisi değil, alınıp alınamadığıdır.
- KL19 — Anahtar olabilme ayrı bir sütundur ve tek girdilik bir eşleme kurularak sınanır; iki sütun birbirini doğrular.
- KL20 — Çoklu dönüş ölçümünde işlev var olan öğeleri döndürür; böylece sayılan şey yalnız dönüşün ve çözmenin kendi maliyetidir.
- KL21 — Çözmede var edilen sayısı, dönüşten sonraki sayaçtan dönüşteki sayı düşülerek bulunur; iki aşama ayrı ayrı okunur.
"""Demetin kazandigi yetki: anahtar olabilmek ve coklu donus degeri tasimak.""" SAYAC = {"uretilen": 0} class Oge: def __init__(self, deger): SAYAC["uretilen"] += 1 self.deger = deger def __repr__(self): return f"Oge({self.deger})" def sifirla(): SAYAC["uretilen"] = 0 def uretilen(): return SAYAC["uretilen"] ADAYLAR = (("(2031, 7)", (2031, 7)), ("[2031, 7]", [2031, 7]), ("((2031, 7), (2032, 1))", ((2031, 7), (2032, 1))), ("(2031, [7])", (2031, [7])), ("'kuzey'", "kuzey")) print(f"{'aday':<24s}{'karmalanabilir':<16s}{'anahtar olabilir'}") for ad, x in ADAYLAR: try: hash(x) h = "evet" except TypeError as e: h = type(e).__name__ try: {x: 0} a = "evet" except TypeError as e: a = type(e).__name__ print(f"{ad:<24s}{h:<16s}{a}") KAYIT = {(2031, 7): "yamac", (2031, 8): "kuzey", (2032, 1): "ozet"} print() print(f"demet anahtarlı sözlük: {len(KAYIT)} girdi | " f"KAYIT[(2031, 8)] -> {KAYIT[(2031, 8)]!r}") HAVUZ = [Oge(i) for i in (3, 1, 4)] def uclu(): return HAVUZ[0], HAVUZ[1], HAVUZ[2] sifirla() sonuc = uclu() donus_uretimi = uretilen() a, b, c = sonuc cozme_uretimi = uretilen() print() print(f"dönüş kabının tipi {type(sonuc).__name__} | yuva {len(sonuc)} | " f"dönüşte var edilen {donus_uretimi} | çözmede var edilen " f"{cozme_uretimi - donus_uretimi}") print(f"a is HAVUZ[0] -> {a is HAVUZ[0]} | c is HAVUZ[2] -> {c is HAVUZ[2]}") sifirla() x, y = HAVUZ[0], HAVUZ[1] x, y = y, x print(f"iki adı takas etmek: var edilen {uretilen()} | " f"x is HAVUZ[1] -> {x is HAVUZ[1]}") sifirla() bas, *orta = HAVUZ print(f"yıldızlı çözme: var edilen {uretilen()} | orta bir " f"{type(orta).__name__} ve {len(orta)} yuva | " f"orta[0] is HAVUZ[1] -> {orta[0] is HAVUZ[1]}")
aday karmalanabilir anahtar olabilir (2031, 7) evet evet [2031, 7] TypeError TypeError ((2031, 7), (2032, 1)) evet evet (2031, [7]) TypeError TypeError 'kuzey' evet evet demet anahtarlı sözlük: 3 girdi | KAYIT[(2031, 8)] -> 'kuzey' dönüş kabının tipi tuple | yuva 3 | dönüşte var edilen 0 | çözmede var edilen 0 a is HAVUZ[0] -> True | c is HAVUZ[2] -> True iki adı takas etmek: var edilen 0 | x is HAVUZ[1] -> True yıldızlı çözme: var edilen 0 | orta bir list ve 2 yuva | orta[0] is HAVUZ[1] -> True
Beş Adayın Üçü Geçiyor
Tablo karmalanabilirliğin kaba değil, kabın bütününe baktığını gösteriyor. Sayı çifti geçiyor, iç içe demet de geçiyor — çünkü içindeki her şey de karmalanabilir. Liste düşüyor; bu beklenirdi. Asıl satır dördüncüsüdür: bir liste taşıyan demet de düşüyor. Kabın kendisi değişmez olduğu hâlde. Bir demetin karma değeri içindekilerin karma değerlerinden hesaplanır, ve içlerinden biri karma değeri veremiyorsa demet de veremez.
Bu, bir önceki bölümün bulgusunun ikinci yüzüdür. Orada değişmezliğin bir katman derinliğinde olduğunu gördük; burada dilin bunu bildiği görülüyor. Demet, güvenceyi ancak içindekilerin de verdiği kadar verebilir ve veremediğinde anahtar olma yetkisini kaybeder. İki sütunun aynı yanıtı vermesi rastlantı değildir: eşlemeye anahtar olma koşulu tam olarak karma değeri alınabilmesidir.
Alt bloktaki üç ölçüm demetin görünmeyen kullanımını sayıyor. Bir işlev virgülle ayrılmış
üç değer döndürdüğünde ortaya bir demet çıkar — bu, ayrı bir sözdizim değil, demet
kurmanın parantezsiz yazımıdır. Dönüşte var edilen 0, çözmede var edilen 0. İşlev
üç nesneyi paketleyip verdi, atama üç adı o nesnelere bağladı; hiçbir aşamada kopya
çıkarılmadı ve a is HAVUZ[0] bunu doğruluyor.
İki adı takas eden satır aynı düzeneğin en kısa hâlidir: sağ taraf bir demet kurar, sol taraf onu çözer, var edilen öğe 0. Yıldızlı çözme ise bir ayrıntı ekliyor — artan yuvaları toplayan kap bir demet değil, bir listedir, ve o listenin yuvaları yine eski nesnelere gidiyor. Çoklu dönüş değeri bu yüzden ucuz bir yazımdır: taşınan şey nesneler değil, onlara giden bağlardır.
Anahtar Neye Göre Bulunur
Bir demetin anahtar olabilmesi ölçüldü; anahtarın nasıl bulunduğu ölçülmedi. Bu ayrım önemlidir, çünkü önceki dersin silme ölçümünde eşitliğe bakan bir yöntem kimliğe düşmüştü. Eşlemede öyle bir düşüş yoktur: anahtar her zaman karma değeri ve eşitlikle aranır, kimlikle değil.
- KL22 — Aranan anahtar iki değişkenden çalışma anında kurulur; eşlemenin içindeki
anahtar ise kaynak metindeki yazımdan gelir. İkisinin ayrı nesne olduğu
isile sınanır. - KL23 — Karma değerlerinin eşitliği karşılaştırılır; değerlerin kendisi basılmaz.
- KL24 — Aynı değerleri taşıyan üç demet bir döngüde ayrı ayrı kurulur; ikisi eşittir ve eşlemede kaç girdi kaldığı sayılır.
"""Anahtar kimlikle degil esitlikle bulunur; kabin tipi esitlige girer.""" KAYIT = {(2031, 7): "yamac", (2031, 8): "kuzey"} YIL, AY = 2031, 7 ARANAN = (YIL, AY) # calisma aninda kurulur, sabit degildir ILK = next(iter(KAYIT)) print(f"aranan is sözlükteki anahtar -> {ARANAN is ILK}") print(f"aranan == sözlükteki anahtar -> {ARANAN == ILK}") print(f"karma değerleri eşit -> {hash(ARANAN) == hash(ILK)}") print(f"KAYIT[aranan] -> {KAYIT[ARANAN]!r}") print() print(f"(2031, 7) == [2031, 7] -> {(2031, 7) == [2031, 7]}") print(f"(2031, 7) == (2031, 7, None) -> {(2031, 7) == (2031, 7, None)}") for aday in ((2031, 7), [2031, 7]): try: var = aday in KAYIT except TypeError as e: var = type(e).__name__ print(f"{str(aday):<12s} in KAYIT -> {var}") print() DEMETLER = [(2031, a) for a in (7, 8, 7)] print(f"üç demet kuruldu | ayrı nesne mi: " f"{DEMETLER[0] is DEMETLER[2]} | eşit mi: {DEMETLER[0] == DEMETLER[2]}") KUME = {d: i for i, d in enumerate(DEMETLER)} print(f"üçü de anahtar yapıldı, eşlemede kalan girdi: {len(KUME)}")
aranan is sözlükteki anahtar -> False aranan == sözlükteki anahtar -> True karma değerleri eşit -> True KAYIT[aranan] -> 'yamac' (2031, 7) == [2031, 7] -> False (2031, 7) == (2031, 7, None) -> False (2031, 7) in KAYIT -> True [2031, 7] in KAYIT -> TypeError üç demet kuruldu | ayrı nesne mi: False | eşit mi: True üçü de anahtar yapıldı, eşlemede kalan girdi: 2
İlk dört satır aranan kuralı veriyor: nesne ayrı, değer eşit, özetler eşit, ve arama başarılı. Anahtarı bulan şey kimlik değildir. Bu, demetin ucuz bir anahtar olmasının nedenidir — anahtarı taşımak gerekmez, aynı değerlerden yenisini kurmak yeter.
Aradaki satırlar eşitliğin kap tipini de kapsadığını gösteriyor. Aynı iki sayıyı taşıyan
liste demede eşit değildir, ve tipi kaldırıp yalnız içeriğe bakan bir kural yoktur. Uzunluğu
farklı bir demet de eşit değildir. Üyelik sınamasında ayrım daha da serttir: liste yalnız
eşit sayılmakla kalmaz, TypeError ile düşer — çünkü aranmadan önce karma değeri alınması gerekir
ve o adım hiç gerçekleşmez.
Son iki satır eşitliğin sonucunu somutlaştırıyor. Üç demet ayrı ayrı kuruldu; birinci ile üçüncü ayrı nesne ama eşit. Üçü de anahtar yapıldığında eşlemede 2 girdi kalıyor: eşit iki anahtar aynı yere düştü ve ikincisi birincinin değerini ezdi. Kabın kimliği hiçbir yerde işe karışmadı.
Özet
- Değişmezlik üç işlemi kapatır: yuvaya yazmak, yuva silmek ve yuva eklemek. İlk ikisi
TypeError, sonuncusuAttributeErrorile düşer — biri “yapamam”, öbürü “bilmiyorum” der. - Demette
k += (yeni,)bir uzatma değil yeniden kurmadır: kap yenidir (buyuk is demet→ False) ama öğeler eskidir (buyuk[0] is demet[0]→ True) ve var edilen 1’dir. - Değişmezlik bir katman derinliğindedir:
k[0].deger = 9demette de geçer, çünkü demet yuvasını korur, yuvanın gösterdiği nesnenin içini değil. - Kabın kazandığı yetki anahtar olabilmektir ve bu yetki içindekilere bağlıdır: beş adaydan üçü geçer, liste taşıyan demet kabın kendisi değişmez olduğu hâlde düşer.
- Çoklu dönüş değeri bir demettir ve 0 öğe var eder; çözme de 0 var eder, çünkü taşınan şey nesneler değil onlara giden bağlardır.
Sonraki Adım
Demet anahtar olabildi; ama anahtarı alıp değere ulaştıran kabın kendisi henüz ölçülmedi. Sonraki ders eşlemeye bakar ve kursun dördüncü okumasının ilk yarısını öder: aynı iki yüz öğe bir listede ve bir kümede arandığında kaç karşılaştırma yapılır? Veri aynı, öğeler aynı, değişen tek şey onları hangi kapta tuttuğumuz. Aynı ders bir ikinci soruyu daha sorar: bir sözlüğün anahtarlarını istediğimizde elimize geçen şey bir kopya mı, yoksa sözlüğe bakmayı sürdüren bir pencere mi?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.