İçeriğe geç
academia.sh

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 is karşı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 is ile 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, sonuncusu AttributeError ile 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 = 9 demette 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat