İçeriğe geç
academia.sh

Ders 09 / 16

Üreteç İfadeleri

Bin öğelik toplamada üreteç de kavrama da 1000 nesne üretir ve ikisi de 499500 verir; ayrıldıkları tek yer listede tutulandır — 0'a karşı 1000. Tembel olan üretimi geciktirir, ondan kaçınmaz.

İçindekiler

Önceki ders yalnız kurulum anını ölçtü ve orada uçurum vardı: kavrama daha ilk satırda 1000 nesne var ediyor, üreteç 0 var ediyordu. Ama bir akış kurulup bırakılmak için yazılmaz; er ya da geç tüketilir. Bin öğe gerçekten toplanacaksa üreteç de o bin nesneyi üretmek zorundadır.

Bu dersin sorusu budur: aynı işi sonuna kadar götüren iki yazım, iş bittiğinde kaç nesne üretmiş olur ve kaçını hâlâ elinde tutar? Yanıtın bir yarısı beklenen, öbür yarısı beklenmedik çıkacak — ve tembelliğin gerçekte neyi azalttığı ancak orada görünecek. Ders aynı zamanda üreteç ifadesinin kendi yazımını kurar: önceki derste tabloda bir satır olarak duran biçim burada kendi başına ölçülür.

Yazım

Üreteç ifadesi bir liste kavramasının köşeli ayraçlarının parantezle değiştirilmiş hâlidir ve ürettiği şey bir liste değil, bir üreteç nesnesidir. Bir işleve tek argüman olarak verildiğinde parantezler ayrıca yazılmaz; sum(o.deger for o in akis) yazımındaki ifade kendi başına bir üreteç ifadesidir.

Ürettiği nesne önceki dersin üreteç işlevinin döndürdüğüyle aynı türdendir: yineleyicidir, tek geçişliktir, __iter__ kendisini döndürür. Fark yalnız yazımdadır. Üreteç işlevi ad alır, birden çok deyim taşıyabilir, dallanabilir; üreteç ifadesi adsızdır ve tek bir dönüşümü anlatır. Aralarındaki seçim maliyet değil, ifade genişliği seçimidir — ölçülen sayılar ikisinde de aynıdır.

Programlama Temelleri kursu tembel değerlendirmeyi fonksiyonel programlamaya girişte bir kavram olarak kurmuştu: değer, kullanıldığı ana kadar hesaplanmaz. Bu ders o kavramı tekrarlamaz; onun Python’daki karşılığının kaç nesneye mal olduğunu sayar. Ve sayı, kavramın adından beklenenden farklı çıkar.

Ölçümün varsayımları:

  • YN19 — Ortak tanımın Oge sınıfı ve N = 1000 sayısı aynen kullanılır; sayma düzeni değiştirilmez. Kâhin kurgunun kendisidir.
  • YN20 — İki tüketim biçimi aynı sonucu hedefler: bin öğenin değer toplamı. Sonuç sütunu ölçümün doğruluk denetimidir; iki biçim ayrı sonuç verseydi karşılaştırma geçersiz olurdu.
  • YN21 — “Listede tutulan”, toplama bittiğinde bir kapta canlı duran öğe sayısıdır; üreteçli biçimde böyle bir kap kurulmadığı için sıfırdır.
  • YN22 — Zincir ölçümünde üç halka vardır: üretme, süzme, dönüştürme. İki biçim aynı halkaları aynı sırada uygular; değişen tek şey her halkanın kap mı akış mı olduğudur.
  • YN23 — Zincirdeki “kaplarda tutulan” sayısı üç listenin uzunluklarının toplamıdır. Bellek iddiası sayılan nesneyle kurulur, ölçülen baytla değil; süre ölçülmez.
  • YN24 — Erken bırakma ölçümünde aranan koşul beşinci öğede sağlanır; koşulun yeri sabittir ve tüketicinin ne zaman durduğunu belirler.
  • YN25 — Tek geçişlilik ölçümü on öğelik küçük bir akışta yapılır; ölçülen şey büyüklük değil, ikinci toplamanın döndürdüğü değerdir.
  • YN26 — Kaynak değerlendirme ölçümünde kaynak_ver çağrılma sayısını kendi sayar; bu sayı, dış yinelenebilirin ne zaman değerlendirildiğinin ölçüsüdür.
  • YN27 — Son tabloda her işlem iki kap üzerinde ayrı ayrı denenir ve kurulum her denemede sıfırlanır; başarısız olan işlemin istisna sınıfı da koşumdan alınır. Yalnız standart kitaplık kullanılır.

Ölçüm

"""Uretec ifadesi: ayni sonuc, ayni uretim, baska tutma."""

import itertools

SAYAC = {"uretilen": 0}


class Oge:
    """Her uretimi sayan 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"]


N = 1000


def uretec_tuketimi(n=N):
    sifirla()
    akis = (Oge(i) for i in range(n))
    toplam = sum(o.deger for o in akis)
    return uretilen(), toplam, 0


def kavrama_tuketimi(n=N):
    sifirla()
    liste = [Oge(i) for i in range(n)]
    toplam = sum(o.deger for o in liste)
    return uretilen(), toplam, len(liste)


print(f"{'tüketim biçimi':<22s} {'üretilen':>9s} {'toplam':>8s}"
      f" {'listede tutulan':>16s}")
for ad, f in (("üreteçle toplama", uretec_tuketimi),
              ("kavramayla toplama", kavrama_tuketimi)):
    u, t, tut = f()
    print(f"  {ad:<20s} {u:9d} {t:8d} {tut:16d}")


def zincir_tembel(n=N):
    sifirla()
    ciftler = (o for o in (Oge(i) for i in range(n)) if o.deger % 2 == 0)
    kareler = (o.deger * o.deger for o in ciftler)
    kurulumda = uretilen()
    toplam = sum(kareler)
    return kurulumda, uretilen(), toplam, 0


def zincir_liste(n=N):
    sifirla()
    hepsi = [Oge(i) for i in range(n)]
    ciftler = [o for o in hepsi if o.deger % 2 == 0]
    kareler = [o.deger * o.deger for o in ciftler]
    kurulumda = uretilen()
    toplam = sum(kareler)
    return kurulumda, uretilen(), toplam, len(hepsi) + len(ciftler) + len(kareler)


print()
print(f"{'zincir biçimi':<22s} {'kurulumda':>10s} {'üretilen':>9s}"
      f" {'toplam':>10s} {'kaplarda tutulan':>17s}")
for ad, f in (("üç üreteç ifadesi", zincir_tembel), ("üç liste kavraması", zincir_liste)):
    k, u, t, tut = f()
    print(f"  {ad:<20s} {k:10d} {u:9d} {t:10d} {tut:17d}")


def erken_birakma(n=N):
    sifirla()
    akis = (Oge(i) for i in range(n))
    bulundu = any(o.deger > 4 for o in akis)
    return uretilen(), bulundu


def erken_birakma_liste(n=N):
    sifirla()
    liste = [Oge(i) for i in range(n)]
    bulundu = any(o.deger > 4 for o in liste)
    return uretilen(), bulundu


print()
u1, b1 = erken_birakma()
u2, b2 = erken_birakma_liste()
print(f"tüketici beşinci öğede bıraktığında: üreteç ifadesi {u1} nesne üretti"
      f" (yanıt {b1}), liste kavraması {u2} (yanıt {b2})")

sifirla()
akis = (Oge(i) for i in range(10))
birinci = sum(o.deger for o in akis)
ikinci = sum(o.deger for o in akis)
print(f"aynı üreteç ifadesi iki kez toplandığında: {birinci} ve {ikinci},"
      f" üretilen {uretilen()}")

CAGRI = {"kaynak": 0}


def kaynak_ver():
    CAGRI["kaynak"] += 1
    return [1, 2, 3]


akis = (x for x in kaynak_ver())
kurulumda = CAGRI["kaynak"]
list(akis)
print(f"dış yinelenebilir kurulumda değerlendirildi mi: {kurulumda} çağrı,"
      f" gezildikten sonra {CAGRI['kaynak']}")

kalanlar = [5, 6, 7]
akis = (x * 10 for x in kalanlar)
kalanlar.append(8)
print(f"kaynağa sonradan eklenen öğe akışa girdi mi: {list(akis)}")

sifirla()
kume = {o.deger % 3 for o in (Oge(i) for i in range(9))}
print(f"küme kurucusuna verilen üreteç ifadesi: üretilen {uretilen()},"
      f" tutulan {len(kume)}")

ISLEMLER = (("len(n)", lambda n: len(n)),
            ("n[2]", lambda n: n[2].deger),
            ("n[:3]", lambda n: len(n[:3])),
            ("islice(n, 3)", lambda n: len(list(itertools.islice(n, 3)))))

print()
print(f"{'işlem':<14s} {'liste':>22s} {'üreteç ifadesi':>24s}")
for ad, islem in ISLEMLER:
    satir = []
    for kur in (lambda: [Oge(i) for i in range(N)],
                lambda: (Oge(i) for i in range(N))):
        sifirla()
        n = kur()
        try:
            sonuc = f"{islem(n)} ({uretilen()} üretim)"
        except TypeError as e:
            sonuc = f"{type(e).__name__} ({uretilen()} üretim)"
        satir.append(sonuc)
    print(f"  {ad:<12s} {satir[0]:>22s} {satir[1]:>24s}")

sifirla()
akis = (Oge(i) for i in range(N))
var_mi = any(o.deger == 2 for o in akis)
sinamada = uretilen()
kalan = sum(1 for _ in akis)
print()
print(f"akışta üyelik sınaması: yanıt {var_mi}, sınamada üretilen {sinamada},"
      f" akışta kalan {kalan}")
tüketim biçimi          üretilen   toplam  listede tutulan
  üreteçle toplama          1000   499500                0
  kavramayla toplama        1000   499500             1000

zincir biçimi           kurulumda  üretilen     toplam  kaplarda tutulan
  üç üreteç ifadesi             0      1000  166167000                 0
  üç liste kavraması         1000      1000  166167000              2000

tüketici beşinci öğede bıraktığında: üreteç ifadesi 6 nesne üretti (yanıt True), liste kavraması 1000 (yanıt True)
aynı üreteç ifadesi iki kez toplandığında: 45 ve 0, üretilen 10
dış yinelenebilir kurulumda değerlendirildi mi: 1 çağrı, gezildikten sonra 1
kaynağa sonradan eklenen öğe akışa girdi mi: [50, 60, 70, 80]
küme kurucusuna verilen üreteç ifadesi: üretilen 9, tutulan 3

işlem                           liste           üreteç ifadesi
  len(n)           1000 (1000 üretim)     TypeError (0 üretim)
  n[2]                2 (1000 üretim)     TypeError (0 üretim)
  n[:3]               3 (1000 üretim)     TypeError (0 üretim)
  islice(n, 3)        3 (1000 üretim)             3 (3 üretim)

akışta üyelik sınaması: yanıt True, sınamada üretilen 3, akışta kalan 997

Aynı Üretim, Ayrı Tutma

Üst tablo ortak tanımın ikinci iddiasını tamamlıyor ve ilk sütun beklentiyi bozuyor.

Üretilen sayı ikisinde de 1000. Tembel olan biçim daha az nesne üretmiyor; bin öğe istendi, bin öğe doğdu. Toplam sütunu da aynı: ikisi de 499500. Yani iki yazım aynı işi yapıyor, aynı sonucu veriyor ve aynı maliyeti ödüyor.

Ayrıldıkları tek sütun sonuncusudur. Toplama bittiğinde kavramayla yazılan biçimde 1000 öğe hâlâ bir listede duruyor; üreteçle yazılan biçimde 0. Üretilen bin nesnenin her biri doğdu, toplamaya katkısını verdi ve ardından hiçbir kaba yazılmadığı için erişilemez oldu. Kavramada ise hepsi listede kaldı, çünkü listenin işi tam olarak budur.

Buradan ortak tanımın cümlesi çıkar: tembellik üretimi azaltmaz, tutmayı azaltır. Bu, “tembel değerlendirme” adının çağrıştırdığından daha dar bir iddiadır. Ad, işin yapılmayacağını ima eder; ölçüm, işin yapıldığını ve yalnız ne zaman ile sonrasında ne kaldığı sorularının değiştiğini gösterir. Tembel olan üretimi geciktirir, ondan kaçınmaz.

Önceki dersin 0’ı bu tabloyla birlikte okunmalıdır. Kurulumdaki sıfır bir tasarruf vaadi değil, bir erteleme kaydıdır. Vaat tutulup tutulmadığı akışın nasıl tüketildiğine bağlıdır, akışın nasıl yazıldığına değil.

Zincirin Ara Kapları

Orta tablo tek bir dönüşümü değil, üç halkalı bir zinciri ölçüyor: üret, süz, dönüştür. Toplam yine aynı — 166167000 — ve üretilen yine 1000. İki sütun ayrılıyor.

Kurulum sütunu önceki dersin sonucunu tekrarlıyor: tembel zincir 0, liste zinciri 1000. Ama asıl fark son sütundadır. Liste zinciri işini bitirdiğinde 2000 başvuru kaplarda duruyor: bin öğelik kaynak listesi, beş yüz öğelik süzülmüş liste, beş yüz öğelik dönüştürülmüş liste. Tembel zincirde bu sayı 0.

Ara kapların sayısı zincirin uzunluğuyla büyür. Dört halkalı bir liste zinciri dört kap, beş halkalı beş kap kurar; her halka kendinden öncekinin tamamını görmek ister ve gördüğünü saklar. Tembel zincirde halka sayısı arttıkça kap sayısı artmaz, çünkü hiçbir halka kendinden öncekinin tamamını istemez — her seferinde bir öğe ister.

Önceki dersin son satırıyla birlikte bu, zincirin kırılma noktasını verir: araya tek bir list() çağrısı girdiğinde o noktada kap kurulur ve zincirin geri kalanının tembelliği o kapı geri alamaz.

Erken Bırakmanın Kaynağı

Bir sonraki satır, iddiaya yapılabilecek en doğal itirazı ölçüyor. Tüketici beşinci öğede durduğunda üreteç ifadesi 6 nesne üretiyor, liste kavraması 1000. Burada tembellik gerçekten üretimden kaçınmış görünüyor.

Ayrım şurada: kaçınmayı sağlayan şey tembellik değil, tüketicinin erken bırakmasıdır. any aradığını bulunca sormayı keser; tembel akış da sorulmayanı üretmez. Kavramada ise tüketici erken bırakmış olsa bile üretim çoktan bitmiştir — liste, any daha ilk soruyu sormadan doldurulmuştur.

İki durum birlikte iddianın tam biçimini verir: tembellik üretimi tüketiciye bağlar. Tüketici her şeyi isterse her şey üretilir (birinci tablo: 1000); erken bırakırsa üretilmeyen kalır (bu satır: 6). Kavramada üretim tüketiciden bağımsızdır ve her iki durumda da 1000’dir. Tembelliğin kazandırdığı, kaçınma değil, kaçınma olanağıdır.

Altıncı nesnenin neden üretildiği de aynı düzenin parçasıdır: koşul beşinci öğede sağlanır ama any bunu ancak o öğeyi aldıktan sonra bilebilir, ve almak için bir üretim daha gerekir.

Kaynak Ne Zaman Değerlendirilir

Son dört satır üreteç ifadesinin iki ince yanını ölçüyor.

Birincisi tek geçişliliktir. Aynı üreteç ifadesi iki kez toplandığında 45 ve 0 geliyor; üretilen 10’da kalıyor. İkinci toplama boş bir akış üzerinde çalışıyor ve sum boş akışta sıfır döndürüyor. Burada bir hata iletisi yoktur — sessiz bir sıfır vardır. Bu, tek geçişliliğin en sinsi sonucudur: tükenmiş bir akış boş bir akıştan ayırt edilemez.

İkincisi kaynağın ne zaman okunduğudur. kaynak_ver kurulum anında bir kez çağrılıyor ve akış gezildikten sonra sayı yine 1. Yani üreteç ifadesindeki en dıştaki yinelenebilir hemen değerlendirilir; geciken şey öğelerin çekilmesidir, kaynağın bulunmasıdır değil. Kaynak adı yanlışsa ya da çağrı hata veriyorsa, bu kurulumda ortaya çıkar.

Bir sonraki satır bunun sınırını gösteriyor. Kaynak listesi kurulumda değerlendirildi ama sonradan eklenen öğe akışa girdi: sonuç [50, 60, 70, 80]. Değerlendirilen şey liste nesnesiydi, içeriğinin anlık kopyası değil. Önceki konunun görünüm ölçümüyle aynı ayrımdır: bağ kurulmuştur, kopya alınmamıştır. Tembel bir akışın kaynağı akış tüketilmeden önce değişirse, akış değişmiş hâli görür.

Son satır üreteç ifadesinin nereye verilebileceğini gösteriyor: küme kurucusuna verilen akış 9 nesne ürettiriyor ve geriye 3 öğe tutuluyor. Var edilen ile tutulan arasındaki fark burada da açık — kap ne kadarını saklayacağına kendisi karar veriyor.

Akışın Yapamadıkları

Son tablo tutmamanın karşılığında nelerden vazgeçildiğini ölçüyor ve üç satırında aynı istisna duruyor.

len(n) listede 1000 veriyor, akışta TypeError. Bir akış uzunluğunu bilmez, çünkü bilmek için sonuna kadar gitmesi ve gittiğini hatırlaması gerekirdi. n[2] ve n[:3] de aynı yanıtı veriyor: akış dizinlenemez ve dilimlenemez. Önceki konunun dilimleme dersinde ölçülen her şey — dilimin yeni kap kurması, iç öğeleri paylaşması — bir akışta hiç sorulamaz, çünkü dilim geriye dönüp bakmayı gerektirir ve akışın geriye bakacak bir yeri yoktur.

Üç istisnanın da ortak gerekçesi budur: sorulan her soru tutmayı gerektirir. Uzunluk, dizin ve dilim öğelerin bir arada durmasını varsayar; akış tam olarak bunu yapmayı reddettiği için bu üç soruyu yanıtlayamaz. Sıfır tutmanın bedeli, üç yerleşik işlemin kaybıdır.

Dördüncü satır kaybın telafi edilebildiği yeri gösteriyor. Standart kitaplığın islice aracı ilk üç öğeyi akıştan alabiliyor ve akışta yalnız 3 üretim yaptırıyor; listede aynı sonuç 1000 üretimden sonra geliyor. Dilim gibi görünen bir iş, geriye bakmadan tanımlandığında akışta da yapılabilir hâle geliyor — ama sonuç bir dilim değil, yine bir akıştır ve o da tek geçişliktir.

Son satır tek geçişliliğin en dikkat isteyen sonucudur. Akışta üyelik sınaması yanıtı True veriyor ve sınamada yalnız 3 nesne üretiliyor; ama sınamadan sonra akışta 997 öğe kalıyor. Sınama akışı tüketti: sorulan üç öğe geri gelmez. Aynı akış üzerinde ikinci bir sınama yapılırsa üçüncü öğeden itibaren aranır ve baştaki üç öğe hiç görülmez. Listede böyle bir yan etki yoktur; akışta sormak, okumaktır.

Özet

  • Bin öğelik toplamada üreteç de kavrama da 1000 nesne üretir ve ikisi de 499500 verir; ayrıldıkları tek yer listede tutulandır — 0’a karşı 1000. Tembel olan üretimi geciktirir, ondan kaçınmaz.
  • Üç halkalı zincirde üretilen yine 1000’dir ama liste biçimi bitişte 2000 başvuruyu kaplarda tutar, tembel biçim 0. Ara kap sayısı zincir uzadıkça listede büyür, akışta büyümez.
  • Tüketici beşinci öğede bıraktığında üreteç ifadesi 6, kavrama 1000 üretir. Kaçınmayı sağlayan tembellik değil, tüketicinin durmasıdır; tembellik yalnız bunu olanaklı kılar.
  • Üreteç ifadesi tek geçişliktir ve tükendiğinde sessizce boş davranır: ikinci toplama hata değil 0 verir.
  • En dıştaki yinelenebilir kurulumda değerlendirilir (1 çağrı), ama değerlendirilen şey nesnenin kendisidir: kaynağa sonradan eklenen öğe akışa girer.

Sonraki Adım

Bu iki ders köşeli ayraç ile parantez arasındaki farkı ölçtü, ama köşeli ayracın kendisini hiç açmadı. Oysa aynı yazım üç ayrı kap kurabilir — liste, küme ve sözlük — ve üçü aynı kaynaktan aynı öğeleri alıp farklı sayıda öğe tutar. Üstelik yazım iç içe geçebilir ve bir kavramanın içine bir kavrama daha girdiğinde ortaya kurulmamış bir kap çıkar. Sonraki ders bunları sayar: üç kavrama biçimi aynı bin öğeden kaçını tutar, ve süzme koşulunun nereye yazıldığı üretilen nesne sayısını değiştirir 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