Ders 06 / 16
Dilimleme
Sekiz dilim biçiminin sekizi de 0 yeni öğe var edip yeni bir kap döndürür; iç içe listede 4 öğe üretilir ve dilim kopyasında iki iç listenin ikisi de paylaşılır — dış kap yenidir, içindekiler eskidir.
İçindekiler
Önceki dersin döndürme ölçümünde iki dilim alındı ve sonuç yeni bir kap oldu. Dilim orada bir ayrıntı olarak geçti; oysa kendi başına bu kursun en çok yanlış anlaşılan işlemidir. Bu ders tek bir soruyu sonuna kadar götürür: bir dilim alındığında ne kopyalanır? Kabın kendisi mi, içindekiler mi, yoksa ikisi de mi?
Dilim Bir Kap İster
Dilimleme (slicing) yazımı üç parça alır: başlangıç, bitiş ve adım. Üçü de isteğe bağlıdır ve verilmediklerinde kabın uçlarına ve bire düşer. Python Temelleri kursunda bu yazımın bir kısaltma olduğu ölçülmüştü; orada hangi yöntemin çağrıldığı sayıldı, burada o çağrının kaç kap kurduğu sayılıyor.
Dizin tek bir yuva ister ve olmayan yuvayı istemek bir kusurdur; dilim bir aralık ister ve aralığın kabın dışına taşan kısmı sessizce kırpılır.
- KL65 — Sekiz dilim biçimi de aynı altı öğelik kaynakla çalışır; öğeler bir kez var
edilir ve sayaç her dilimden hemen önce sıfırlanır. “Öğeler kaynaktan” sütunu, dilimdeki
her öğenin kaynakta
isile aranmasıyla bulunur. - KL66 — Dilime atama ve dilim silme ölçümü tek bir liste üzerinde sırayla ilerler; ikinci satır birincinin bıraktığı durumdan devam eder.
"""Dilim okumasi: her dilim yeni bir kap kurar, hicbiri yeni oge var etmez.""" 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"] KAYNAK = [Oge(i) for i in range(6)] BICIMLER = (("k[1:4]", lambda k: k[1:4]), ("k[:3]", lambda k: k[:3]), ("k[3:]", lambda k: k[3:]), ("k[::2]", lambda k: k[::2]), ("k[::-1]", lambda k: k[::-1]), ("k[:]", lambda k: k[:]), ("k[4:100]", lambda k: k[4:100]), ("k[10:20]", lambda k: k[10:20])) print(f"{'dilim':<12s}{'yeni öğe':>9s}{'yuva':>6s}{'aynı kap':>10s}" f"{'öğeler kaynaktan':>18s}{'içerik':>26s}") for ad, al in BICIMLER: sifirla() dilim = al(KAYNAK) kaynaktan = all(any(x is y for y in KAYNAK) for x in dilim) print(f"{ad:<12s}{uretilen():>9d}{len(dilim):>6d}" f"{str(dilim is KAYNAK):>10s}{str(kaynaktan):>18s}" f"{str([o.deger for o in dilim]):>26s}") print() try: KAYNAK[10] except IndexError as e: print(f"sınır dışı dizin: {type(e).__name__} | " f"sınır dışı dilim: {[o.deger for o in KAYNAK[10:20]]}") print() hedef = list(KAYNAK) once = hedef sifirla() hedef[1:3] = [Oge(9), Oge(9), Oge(9)] print(f"dilime atama: var edilen {uretilen()} | aynı kap " f"{hedef is once} | yuva {len(once)} -> {[o.deger for o in hedef]}") sifirla() del hedef[::2] print(f"dilim silme: var edilen {uretilen()} | aynı kap " f"{hedef is once} | yuva {len(hedef)} -> {[o.deger for o in hedef]}") print() metin = "kuzey-2031" print(f"dizgi dilimi {metin[6:]!r} | demet dilimi " f"{tuple(o.deger for o in KAYNAK)[1:4]}")
dilim yeni öğe yuva aynı kap öğeler kaynaktan içerik k[1:4] 0 3 False True [1, 2, 3] k[:3] 0 3 False True [0, 1, 2] k[3:] 0 3 False True [3, 4, 5] k[::2] 0 3 False True [0, 2, 4] k[::-1] 0 6 False True [5, 4, 3, 2, 1, 0] k[:] 0 6 False True [0, 1, 2, 3, 4, 5] k[4:100] 0 2 False True [4, 5] k[10:20] 0 0 False True [] sınır dışı dizin: IndexError | sınır dışı dilim: [] dilime atama: var edilen 3 | aynı kap True | yuva 7 -> [0, 9, 9, 9, 3, 4, 5] dilim silme: var edilen 0 | aynı kap True | yuva 3 -> [9, 9, 4] dizgi dilimi '2031' | demet dilimi (1, 2, 3)
Sekiz Dilim, Sıfır Öğe
Tablonun ilk sütunu sekiz satırda da 0: dilim almak hiçbir öğe var etmez. Üçüncü sütun
sekiz satırda da False: her dilim listede yeni bir kaptır, tam dilim bile. Dördüncü
sütun sekiz satırda da True: dilimdeki her öğe kaynağın öğesidir. Üç sütun birlikte bu
dersin tek cümlelik bulgusunu veriyor — dilim kabı yeniler, içindekileri değil. Adım
parçası içeriği seçer ama tabloyu değiştirmez; k[::2] üç, k[::-1] altı yuva alır ve
ikisi de 0 öğe var eder.
Son iki satır dilimin dizinden ayrıldığı yeri gösteriyor: kabın dışındaki bir yuva
istendiğinde IndexError doğuyor, kabın dışındaki bir aralık istendiğinde k[4:100]
iki, k[10:20] sıfır öğe veriyor ve hiçbiri kusur saymıyor. Bir aralık istemek belli bir
yuvayı istemekten farklı bir sorudur.
Alt bloktaki üç satır dilimin öteki yüzüdür: dilim okunduğunda yeni bir kap doğar, dilime
yazıldığında kap yerinde değişir — hedef is once True kalıyor. Bu satırların ölçüsü
“Yazmak Kopyalamak Değildir” bölümünde açılıyor.
Kopya Ne Kadar Derin
Buraya kadar ölçülen kaynak düz bir listeydi. Asıl soru kap içinde kap olduğunda ortaya çıkar: iç içe bir listede dilim alındığında dış kap yeni olur — bu ölçüldü. Peki iç kaplar?
- KL67 — İç içe kaynak iki iç liste ve dört yaprak öğe taşır; ölçümün ilk satırı bunu doğrular. Değiştirme sınaması ölçümün sonunda yapılır; ondan öncesi kaynağa dokunulmamış hâliyle alınmıştır.
- KL68 — Derin kopya nesneleri kurucudan geçirmez, bu yüzden üretim sayacı onu
göremez; yeni nesne kurulup kurulmadığı kimlikle sayılır: kopyanın yapraklarından
kaynakta
isile bulunamayanlar.
"""Kopya bir katman derinligindedir: dis kap yenidir, icindekiler eskidir.""" import copy 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 ic_ice(): sifirla() ic = [[Oge(1), Oge(2)], [Oge(3), Oge(4)]] dilim = ic[:] liste_kopya = list(ic) paylasilan = sum(1 for a, b in zip(ic, dilim) if a is b) return uretilen(), paylasilan, dilim[0] is ic[0], liste_kopya[0] is ic[0] ic_uretim, paylasilan, dilim_ayni, liste_ayni = ic_ice() print(f"iç içe listede üretilen öğe {ic_uretim}, dilim kopyasında paylaşılan " f"iç liste {paylasilan}/2") print(f"dilim[0] is ic[0] -> {dilim_ayni}, list(ic)[0] is ic[0] -> {liste_ayni}") def yapraklar(k): return [o for ic_liste in k for o in ic_liste] def ayri_oge(kopya, kaynak): """Kopyadaki yapraklardan kaynakta bulunmayanlarin sayisi.""" eski = yapraklar(kaynak) return sum(1 for o in yapraklar(kopya) if not any(o is y for y in eski)) print() sifirla() ic = [[Oge(1), Oge(2)], [Oge(3), Oge(4)]] print(f"kaynak kurulurken var edilen öğe: {uretilen()} | " f"iç liste {len(ic)} | yaprak {len(yapraklar(ic))}") KOPYALAR = (("k[:]", ic[:]), ("list(k)", list(ic)), ("copy.copy(k)", copy.copy(ic)), ("copy.deepcopy(k)", copy.deepcopy(ic))) print(f"{'kopya biçimi':<20s}{'ayrı yaprak':>12s}{'dış kap aynı':>13s}" f"{'iç liste aynı':>14s}{'yaprak aynı':>12s}") for ad, k in KOPYALAR: print(f"{ad:<20s}{ayri_oge(k, ic):>12d}{str(k is ic):>13s}" f"{str(k[0] is ic[0]):>14s}{str(k[0][0] is ic[0][0]):>12s}") print() dilim = KOPYALAR[0][1] derin = KOPYALAR[3][1] ic[0][0].deger = 99 print(f"kaynaktaki yaprak değiştirildi -> ic[0][0] = {ic[0][0]}") print(f" dilim[0][0] = {dilim[0][0]} | derin[0][0] = {derin[0][0]}") ic.append([Oge(5)]) print(f"kaynağa iç liste eklendi -> ic yuva {len(ic)} | " f"dilim yuva {len(dilim)} | derin yuva {len(derin)}")
iç içe listede üretilen öğe 4, dilim kopyasında paylaşılan iç liste 2/2 dilim[0] is ic[0] -> True, list(ic)[0] is ic[0] -> True kaynak kurulurken var edilen öğe: 4 | iç liste 2 | yaprak 4 kopya biçimi ayrı yaprak dış kap aynı iç liste aynı yaprak aynı k[:] 0 False True True list(k) 0 False True True copy.copy(k) 0 False True True copy.deepcopy(k) 4 False False False kaynaktaki yaprak değiştirildi -> ic[0][0] = Oge(99) dilim[0][0] = Oge(99) | derin[0][0] = Oge(1) kaynağa iç liste eklendi -> ic yuva 3 | dilim yuva 2 | derin yuva 2
Dış Kap Yenidir, İçindekiler Eskidir
İlk iki satır bu kursun üçüncü iddiasını tam olarak ödüyor. İç içe listede 4 öğe
üretiliyor; dilimle alınan kopyada iki iç listenin ikisi de paylaşılıyor —
dilim[0] is ic[0] True — ve list() kopyasında da öyle. Kopya bir katman
derinliğindedir.
Alt tablo aynı ölçümü dört kopya biçimine yayıyor ve üçünü aynı yere koyuyor. Bu üçü yüzeysel kopyadır (shallow copy): dilim, liste kurucusu ve kopyalama işlevi. Üçünde de dış kap ayrı, iç liste aynı, yaprak aynı ve ayrı yaprak sayısı 0. Dördüncüsü ayrılıyor: derin kopyada üç sütun da ayrı ve ayrı yaprak sayısı 4. Bu 0’a karşılık 4, iki yazım arasındaki bütün farkı verir.
Son blok farkın anlamını gösteriyor. Kaynaktaki bir yaprağın içi değiştirildiğinde dilim
kopyası bunu görüyor — dilim[0][0] de Oge(99) oldu — derin kopya görmüyor.
Kaynağa yeni bir iç liste eklendiğinde ise ikisi de 2 yuvada kalıyor, kaynak 3’e
çıkıyor. Paylaşılan şey dış kabın yuvalarının gösterdiği nesnelerdir, kabın kendisi
değil; yüzeysel kopya bu yüzden tam olarak bir katman bağımsızdır.
Yazmak Kopyalamak Değildir
Önceki bölüm dilimi okudu ve bir katman kopyaladı. Dilime yazıldığında ölçü tersine döner: kap kurulmaz, kimlik korunur, uzunluk değişir. Bu bölüm o karşıtı ve dersin iddiasının sınırını ölçer.
- KL66 (devam) — Yazma ölçümü tek bir liste üzerinde sırayla ilerler. Silinen öğenin yaşayıp yaşamadığı silmeden önce alınmış ayrı bir adla sınanır; değiştirilemez kap sınaması dokunulmamış üç ayrı kapta yapılır.
"""Dilime yazmak kopya degil degisikliktir; degismez kapta tam dilim yeni kap kurmaz.""" SAYAC = {"uretilen": 0} class Oge: def __init__(self, deger): SAYAC["uretilen"] += 1 self.deger = deger def sifirla(): SAYAC["uretilen"] = 0 def uretilen(): return SAYAC["uretilen"] liste = [Oge(i) for i in range(6)] kimlik = liste for ad, d in (("k[4:1:-1]", liste[4:1:-1]), ("k[1:4:-1]", liste[1:4:-1]), ("k[::-2]", liste[::-2])): print(f"{ad:<10s} yuva {len(d)} -> {[o.deger for o in d]}") print() sifirla() liste[1:4] = [Oge(7)] print(f"k[1:4] = tek öğe: var edilen {uretilen()} | aynı kap {liste is kimlik} | " f"yuva 6 -> {len(liste)} | {[o.deger for o in liste]}") ikinci = liste[1] tutulan = liste[1:3] sifirla() del liste[1:3] print(f"del k[1:3]: var edilen {uretilen()} | aynı kap {liste is kimlik} | " f"yuva {len(liste)} | {[o.deger for o in liste]}") print(f" silinen öğe ayrı adda yaşıyor -> {ikinci is tutulan[0]} | " f"kapta kaldı -> {any(ikinci is x for x in liste)}") print() for ad, kap in (("liste", [1, 2, 3]), ("demet", (1, 2, 3)), ("dizgi", "abc")): print(f"{ad:<7s} k[:] is k -> {str(kap[:] is kap):<6s} k[:2] is k -> {kap[:2] is kap}")
k[4:1:-1] yuva 3 -> [4, 3, 2] k[1:4:-1] yuva 0 -> [] k[::-2] yuva 3 -> [5, 3, 1] k[1:4] = tek öğe: var edilen 1 | aynı kap True | yuva 6 -> 4 | [0, 7, 4, 5] del k[1:3]: var edilen 0 | aynı kap True | yuva 2 | [0, 5] silinen öğe ayrı adda yaşıyor -> True | kapta kaldı -> False liste k[:] is k -> False k[:2] is k -> False demet k[:] is k -> True k[:2] is k -> False dizgi k[:] is k -> True k[:2] is k -> False
Üst üç satır eksi adımın ölçüsü. k[::-1] ana tabloda zaten 0 öğe var edip yeni bir kap
vermişti; buradaki soru boş dilimin eksi adımdaki karşılığı. k[4:1:-1] üç yuva verirken
k[1:4:-1] 0 yuva veriyor: sınırlar aynı, yön ters — geriye giden dilimde başlangıcın
bitişten büyük olması gerekir.
Orta blok yazmanın ölçüsü. k[1:4] = tek öğe 1 nesne var ediyor — onu biz kurduk — ve
liste is kimlik True kalıyor: kap değişmedi, kabın içi değişti. Yuva 6’dan 4’e
düşüyor; üç yuva alınıp bir yuva kondu, yani atama alınan ve konan yuva sayısının
eşitliğini istemez. del k[1:3] aynı ekseni tamamlıyor: 0 öğe var ediliyor, kimlik yine
korunuyor, yuva 2’ye iniyor. Listede k[:] yeni bir kap döndürür; atama ve silme
var olan kabı yerinde değiştirir. Silinen öğe de yok olmuyor: silmeden önce alınan adla
hâlâ aynı nesne (True), artık kapta değil (False) — silme yuvayı kaldırır.
Alt blok iddianın sınırı. Listede k[:] is k False, demette ve dizgide True: tam
dilim orada yeni bir kap kurmuyor, var olanı geri veriyor. Dış kap yenidir cümlesi
değiştirilemez tiplerde geçerli değildir, çünkü içeriği değiştirilemeyen bir kabı
çoğaltmanın ölçülebilir bir karşılığı yoktur. k[:2] is k üç kapta da False; sınır tam
dilime özgü. İddia daralıyor — dilim, değiştirilebilir bir kabı yeniler; içindekileri
hiçbir zaman yenilemez.
Kırpmayı Kim Yapıyor
Üç ölçüm de dilimin ne yaptığını gösterdi; hiçbiri kimin yaptığını göstermedi. Sınır dışı aralığın kırpılması bir kap davranışı gibi göründü, oysa değildir: iki nokta üst üste yazımının arkasında adı belli bir nesne vardır.
- KL69 — Tek bir dilim tarifi nesnesi kurulur ve üç ayrı kapta kullanılır; sonuçlar aynı tarifin yazımla alınan karşılığıyla karşılaştırılır.
- KL70 — Çözülen sınırlar tarifin kendi yönteminden okunur, elle hesaplanmaz; yuva sayısı ayrıca dilimin uzunluğundan alınır ve iki kaynak birbirini doğrular.
"""Dilim tarifi bir nesnedir: ayni tarif uc kapta kullanilir, sinirlar ondan cozulur.""" KAYNAK = list(range(6)) KAPLAR = (("liste", KAYNAK), ("demet", tuple(range(6))), ("dizgi", "kuzeyi")) TARIF = slice(1, 4) print(f"tarif bir nesnedir: {type(TARIF).__name__} | " f"başlangıç {TARIF.start} bitiş {TARIF.stop} adım {TARIF.step}") print(f"{'kap':<10s}{'k[tarif]':<14s}{'k[1:4] ile aynı'}") for ad, kap in KAPLAR: print(f"{ad:<10s}{str(kap[TARIF]):<14s}{kap[TARIF] == kap[1:4]}") print(f"tek tarif nesnesi {len(KAPLAR)} ayrı kapta kullanıldı") print() print(f"{'yazım':<12s}{'çözülen sınırlar':<20s}{'yuva'}") for ad, t in (("k[1:4]", slice(1, 4)), ("k[:]", slice(None, None)), ("k[::-1]", slice(None, None, -1)), ("k[-3:]", slice(-3, None)), ("k[4:100]", slice(4, 100)), ("k[10:20]", slice(10, 20))): print(f"{ad:<12s}{str(t.indices(len(KAYNAK))):<20s}{len(KAYNAK[t])}")
tarif bir nesnedir: slice | başlangıç 1 bitiş 4 adım None kap k[tarif] k[1:4] ile aynı liste [1, 2, 3] True demet (1, 2, 3) True dizgi uze True tek tarif nesnesi 3 ayrı kapta kullanıldı yazım çözülen sınırlar yuva k[1:4] (1, 4, 1) 3 k[:] (0, 6, 1) 6 k[::-1] (5, -1, -1) 6 k[-3:] (3, 6, 1) 3 k[4:100] (4, 6, 1) 2 k[10:20] (6, 6, 1) 0
Üst blok yazımın arkasındaki nesneyi çıkarıyor. k[1:4] yazıldığında kaba geçirilen şey iki
sayı değil, tek bir tariftir; üç alanı vardır ve verilmeyen alan None kalır. Aynı tarif
üç ayrı kapta kullanılıyor ve üçünde de yazımla alınan sonucun aynısını veriyor. Tarif kaba
bağlı değildir: hangi kaba uygulanacağını da, kabın uzunluğunu da bilmez.
Alt blok kırpmanın nerede olduğunu gösteriyor. Tarif, uygulanacağı kabın uzunluğunu
öğrendiğinde kendini somut üç sayıya çözüyor: k[:] (0, 6, 1) oluyor ve verilmeyen
alanlar doluyor; k[-3:] (3, 6, 1) oluyor, eksi sayı uzunluktan çıkarılıyor; k[4:100]
(4, 6, 1) oluyor, 100 sayısı 6’ya kırpılıyor; k[10:20] (6, 6, 1) oluyor ve
dilim boşalıyor.
Sınır dışı aralığın kusur saymaması bir davranış tercihi değil, ölçülmüş bir adımdır —
kırpma, kabın içine girilmeden önce tarifin çözümünde yapılır. Ters adımlı tarifin
(5, -1, -1) vermesi de aynı çözümün ürünüdür ve k[1:4:-1]’in boşluğunu o açıklar.
Özet
- Sekiz dilim biçiminin sekizi de 0 öğe var eder, sekizi de listede yeni bir kap döndürür ve sekizinde de öğeler kaynaktan gelir — tam dilim dahil.
- Sınır dışı dizin
IndexErrorverir, sınır dışı dilim kırpılır,k[1:4:-1]0 yuva verir. Kırpma kabın içine girilmeden, kaba bağlı olmayan tarif nesnesinin çözümünde yapılır:k[10:20]→(6, 6, 1). - Dilime yazmak aynı kapta çalışır ve uzunluğu değiştirir:
k[1:4] = tek öğe1 nesne var edip yuvayı 6’dan 4’e,del k[1:3]0 nesne var edip 2’ye indirir; ikisinde de kimlik korunur ve silinen öğe, başka bir ad tutuyorsa yaşar. - İç içe listede 4 öğe üretilir; dilim kopyasında iki iç listenin ikisi de paylaşılır
(
dilim[0] is ic[0]→ True) velist()kopyasında da öyle — dış kap yenidir, içindekiler eskidir. Yüzeysel kopyanın üç yazımı 0 ayrı yaprak verir, derin kopya 4. - İddianın sınırı:
k[:] is klistede False, demette ve dizgide True. Değiştirilemez bir kapta tam dilim yeni kap kurmaz — dilim değiştirilebilir bir kabı yeniler, içindekileri hiçbir zaman yenilemez.
Sonraki Adım
Bu dersin bütün kopyaları bir katman derinliğindeydi, ve o bir katmanı kurmak bile her yuvaya dokunmayı gerektirdi: altı öğelik bir kaptan dilim almak altı yuvayı okuyup yeni kaba yazmak demektir, bin öğelik kapta bin yuva. Var edilen öğe 0 olsa bile dokunulan yuva sayısı kabın uzunluğu kadardır.
Peki öğelerin üzerinden geçmek için kabı yeniden kurmak zorunlu mu? Sonraki ders bu
soruyu tersinden sorar: bir kabın öğelerine, ne yeni bir kap kurup ne de eldekine dokunup
sırayla ulaşmanın yolu var mı? Python Temelleri kursunda protokolün çağıran tarafı
ölçülmüştü; sonraki ders kuran tarafa geçer ve ilk işi şudur: iter ile next
arasındaki sözleşmeyi kendi elimizle yazmak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.