İçeriğe geç
academia.sh

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 is ile 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 is ile 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ıyordilim[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üyordilim[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 IndexError verir, 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 öğe 1 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) ve list() 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 k listede 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat