Ders 11 / 16
Lambda ve Yüksek Mertebeden Fonksiyonlar
İşlev bir nesnedir ve argüman olarak geçirilebilir: iki yüz öğede anahtar işlevi sorted, min ve max için öğe başına tam bir kez çağrılır — aynı işi elle yazan sıralama onu 39800 kez çağırır.
İçindekiler
Önceki derste süzme koşulu da öğe ifadesi de kavramanın içine gömülü kaldı. İkisi de bir kural anlatıyordu — “şunu geçir”, “şunu şuna dönüştür” — ama kural yazıldığı yerin dışında kullanılamıyordu. Aynı süzgeci ikinci bir kavramada tekrarlamak, onu tekrar yazmak demekti.
Kurallar adlandırılabilir. Bir işlev tam olarak budur: adlandırılmış bir kural. Bu dersin sorusu, o işlevin bir argüman olarak geçirilip geçirilemeyeceği ve geçirildiğinde kaç kez çağrıldığıdır. Sıralamada anahtar hesabı öğe başına bir kez mi yapılır, yoksa her karşılaştırmada yeniden mi? Aradaki fark iki yüz öğede iki sayı üretecek ve ikisi aynı büyüklükte olmayacak.
İşlev Bir Nesnedir
Python’da def bir bildirim değil, bir deyimdir: çalıştığında bir işlev nesnesi üretir
ve onu bir ada bağlar. Bağlama başka her bağlama gibidir — nesne bir listeye konabilir, bir
sözlükte değer olabilir, bir işleve argüman olarak geçirilebilir, bir işlevden döndürülebilir.
lambda aynı nesneyi ad bağlamadan üretir. Tek bir ifade taşır, return yazılmaz, sonucu
ifadenin değeridir. Üretilen nesne def ile üretilenle aynı türdendir; ayrıldığı tek
yer taşıdığı addır.
Bir işlevi argüman olarak alan ya da işlev döndüren işleve yüksek mertebeden fonksiyon (higher-order function) denir. Programlama Temelleri kursu bu kavramı fonksiyonel programlamaya girişte kurdu; burada kavram tekrarlanmaz. Ölçülen şey Python’un yerleşik yüksek mertebeden fonksiyonlarının verilen işlevi kaç kez çağırdığı ve kaç nesne var ettiğidir.
Anahtar İşlevinin Sözleşmesi
sorted, min ve max bir key argümanı kabul eder: her öğe için hesaplanacak
karşılaştırma değerini veren işlev. Sözleşmenin ölçülebilir yanı şudur — bu işlev öğe başına
kaç kez çağrılır?
İki yanıt akla gelir. Sıralama öğeleri birbiriyle karşılaştırır ve karşılaştırma sayısı öğe sayısından fazladır; anahtar her karşılaştırmada hesaplanıyorsa çağrı sayısı da öyle olur. Ya da anahtar bir kez hesaplanıp saklanır ve karşılaştırmalar saklanan değerler üzerinde yapılır. İkinci yol çağrı sayısını düşürür ama tutmayı artırır: her öğe için bir anahtar değeri canlı tutulmalıdır. Ölçüm hangisinin geçerli olduğunu söyler.
Ölçümün varsayımları:
- YN36 — Ortak tanımın
Ogesınıfı sayma düzeniyle aynen kullanılır;Ogeyalnız üretim sayar, anahtar çağrısı ayrı bir sayaçta tutulur. - YN37 — Anahtar işlevi her çağrılışını kendi sayar; “kaç kez çağrıldı” sorusunun ölçüsü bu sayaçtır ve kâhin kurgunun kendisidir.
- YN38 — Kaynak 200 öğelidir ve dört işlemin dördü de aynı kaynak üzerinde çalışır. Sayaç her işlemden önce sıfırlanır.
- YN39 — Anahtar değeri
-o.deger % 97ile hesaplanır; sayı keyfîdir ve yalnız sıralamanın kaynak sırasından farklı bir sıra üretmesi için seçilmiştir. - YN40 — Elle yazılan seçmeli sıralama karşılaştırma başına anahtarı iki kez hesaplar; bu, anahtarı saklamayan bir yazımın davranışıdır ve ölçümün karşı ucudur. Sıralama bu derste bir algoritma olarak değil, çağrı sayısı üreten bir kurgu olarak kullanılır.
- YN41 —
sortedileliste.sortayrımı kimlikle ölçülür; kimlikisile gösterilir, sayısı basılmaz. - YN42 — Eşleme ve süzme ölçümünde kurulum satırından hemen sonra sayaç okunur; öğe
üreten işlev kasıtlı olarak yeni
Ogeüretir, böylece tembellik üretim üzerinden görünür. - YN43 — Üç işlevin ayrı nesneler olduğu ikişerli
iskarşılaştırmalarıyla gösterilir; kimlik sayısı basılmaz. - YN44 — Bağlama ölçümünde üç yazım da aynı üç işlevi kurar ve hepsi aynı öğeyle çağrılır; değişen tek şey çarpanın nereye yazıldığıdır.
Ölçüm
"""Islev bir nesnedir: anahtar islevi kac kez cagrilir, akis ne zaman var eder.""" import functools SAYAC = {"uretilen": 0, "anahtar": 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 SAYAC["anahtar"] = 0 def uretilen(): return SAYAC["uretilen"] def anahtar(o): """Ogenin siralama anahtari; her cagrilisini sayar.""" SAYAC["anahtar"] += 1 return -o.deger % 97 N = 200 KAYNAK = [Oge(i) for i in range(N)] def elle_sec(ogeler, islev): """Her karsilastirmada anahtari yeniden hesaplayan secmeli siralama.""" kalan, sirali = list(ogeler), [] while kalan: enk = 0 for i in range(1, len(kalan)): if islev(kalan[i]) < islev(kalan[enk]): enk = i sirali.append(kalan.pop(enk)) return sirali ISLEMLER = (("sorted(n, key=f)", lambda n: len(sorted(n, key=anahtar))), ("min(n, key=f)", lambda n: min(n, key=anahtar)), ("max(n, key=f)", lambda n: max(n, key=anahtar)), ("elle seçmeli sıralama", lambda n: len(elle_sec(n, anahtar)))) print(f"{'işlem':<24s} {'anahtar çağrısı':>16s} {'öğe başına':>11s}") for ad, islem in ISLEMLER: sifirla() islem(KAYNAK) print(f" {ad:<22s} {SAYAC['anahtar']:16d} {SAYAC['anahtar'] / N:11.1f}") sifirla() sirali = sorted(KAYNAK, key=anahtar) print() print(f"sorted(n) is n -> {sirali is KAYNAK}, var edilen {uretilen()}," f" paylaşılan {sum(1 for o in sirali if any(o is k for k in KAYNAK))}") kopya = list(KAYNAK) once = kopya sonuc = kopya.sort(key=anahtar) print(f"liste.sort dönen değer {sonuc!r}, kap aynı kaldı -> {once is kopya}") def adli(o): return o.deger adsiz = lambda o: o.deger print() print(f"{'işlev':<12s} {'çağrılabilir':>13s} {'__name__':>12s} {'tür aynı mı':>12s}") for ad, f in (("def ile", adli), ("lambda ile", adsiz)): print(f" {ad:<10s} {str(callable(f)):>13s} {f.__name__:>12s}" f" {str(type(f) is type(adli)):>12s}") DEFTER = {"artir": lambda o: o.deger + 1, "iki_kat": lambda o: o.deger * 2} print(f"sözlükte tutulan işlevler: {sorted(DEFTER)}," f" DEFTER['iki_kat'](Oge(21)) -> {DEFTER['iki_kat'](Oge(21))}") sifirla() esleme = map(lambda o: Oge(o.deger * 2), KAYNAK) suzme = filter(lambda o: o.deger % 2 == 0, KAYNAK) kurulumda = uretilen() ilk = next(esleme) print() print(f"map ve filter kurulumda {kurulumda} nesne var etti," f" ilk öğe istendiğinde {uretilen()}") print(f"tümü gezildiğinde: map {len(list(esleme)) + 1} öğe," f" filter {len(list(suzme))} öğe, üretilen {uretilen()}") sifirla() toplam = functools.reduce(lambda a, o: a + o.deger, KAYNAK, 0) print(f"reduce ile toplam {toplam}, var edilen {uretilen()}") def carpan_ureten(k): def carp(o): return o.deger * k return carp uc = [carpan_ureten(k) for k in (1, 2, 3)] ayri = all(a is not b for i, a in enumerate(uc) for b in uc[i + 1:]) print() print(f"üç işlev birbirinden ayrı nesne -> {ayri}," f" sonuçlar {[f(Oge(10)) for f in uc]}") print(f"carpan_ureten(2) is carpan_ureten(2) -> " f"{carpan_ureten(2) is carpan_ureten(2)}") gec = [lambda o: o.deger * k for k in (1, 2, 3)] erken = [lambda o, k=k: o.deger * k for k in (1, 2, 3)] kismi = [functools.partial(lambda o, k: o.deger * k, k=k) for k in (1, 2, 3)] print() print(f"{'bağlama biçimi':<26s} {'nesne':>6s} {'sonuçlar':>16s}") for ad, kap in (("gövdede serbest ad", gec), ("varsayılan argüman", erken), ("partial ile", kismi)): print(f" {ad:<24s} {len(kap):6d} {str([f(Oge(10)) for f in kap]):>16s}")
işlem anahtar çağrısı öğe başına sorted(n, key=f) 200 1.0 min(n, key=f) 200 1.0 max(n, key=f) 200 1.0 elle seçmeli sıralama 39800 199.0 sorted(n) is n -> False, var edilen 0, paylaşılan 200 liste.sort dönen değer None, kap aynı kaldı -> True işlev çağrılabilir __name__ tür aynı mı def ile True adli True lambda ile True <lambda> True sözlükte tutulan işlevler: ['artir', 'iki_kat'], DEFTER['iki_kat'](Oge(21)) -> 42 map ve filter kurulumda 0 nesne var etti, ilk öğe istendiğinde 1 tümü gezildiğinde: map 200 öğe, filter 100 öğe, üretilen 200 reduce ile toplam 19900, var edilen 0 üç işlev birbirinden ayrı nesne -> True, sonuçlar [10, 20, 30] carpan_ureten(2) is carpan_ureten(2) -> False bağlama biçimi nesne sonuçlar gövdede serbest ad 3 [30, 30, 30] varsayılan argüman 3 [10, 20, 30] partial ile 3 [10, 20, 30]
Öğe Başına Bir Çağrı
Üst tablonun ilk üç satırı aynı sayıyı veriyor: 200 çağrı, öğe başına tam 1.0. Sıralama, en küçüğü bulma ve en büyüğü bulma — üçünde de anahtar işlevi her öğe için bir kez çağrılıyor, daha fazla değil.
min ve max için bu beklenendir; her öğeye bir kez bakmak yeter. sorted için değildir.
Sıralama öğeleri birbiriyle karşılaştırmak zorundadır ve karşılaştırma sayısı öğe sayısından
fazladır. Yine de anahtar çağrısı 200’de kalıyor: demek ki anahtarlar bir kez hesaplanıp
saklanıyor ve bütün karşılaştırmalar saklanan değerler üzerinde yapılıyor.
Dördüncü satır bu kararın karşı ucunu gösteriyor. Aynı işi yapan, anahtarı hiç saklamayan ve her karşılaştırmada yeniden hesaplayan bir sıralama, aynı iki yüz öğede anahtarı 39800 kez çağırıyor — öğe başına 199.0. İki sayı arasındaki oran iki yüzdür ve bu oran öğe sayısı büyüdükçe büyür.
Kursun ekseninden okunuşu şudur: yerleşik sorted bir takas yapıyor. Anahtar değerlerini
saklamak öğe sayısı kadar başvuru tutmak demektir; karşılığında çağrı sayısı öğe sayısına
iner. Tutmadan kaçınan yazım — dördüncü satır — hiçbir ara değer saklamıyor ve bedelini
çağrı sayısıyla ödüyor. Bu, ikinci dersin tersine çevrilmiş hâlidir: orada tutmayan biçim
kazanıyordu, burada tutan biçim kazanıyor. Neyin tutulduğu kadar, tutmanın neyi ortadan
kaldırdığı da sayılmalıdır.
Bu ölçüm bir sıralama algoritması karşılaştırması değildir; algoritmalar ve karmaşıklık çözümlemesi Algoritmalar kursunun konusudur. Buradaki dördüncü satır yalnız anahtarı saklamayan bir yazımın kaç çağrı ürettiğini göstermek için elle kuruldu.
Yeni Kap mı, Yerinde mi
Sonraki iki satır aynı işin iki yazımını ayırıyor. sorted yeni bir liste üretiyor:
sorted(n) is n yanlış, var edilen Oge 0 ve paylaşılan 200. Sıralama öğeleri
çoğaltmıyor, yalnız yeni bir kaba başka sırayla yazıyor — birinci dersin kap kurma ölçümünün
aynısı.
liste.sort yeni kap kurmuyor: kap aynı kalıyor ve dönen değer None. Bu, Python’da
yerinde çalışan yöntemlerin ortak imzasıdır ve bir sonucu vardır — n = n.sort() yazımı
listeyi None ile değiştirir. Ayrım tek satırda görünür: sonuç isteniyorsa sorted, kabı
korumak isteniyorsa sort.
İşlevin Nesne Olduğunun Kanıtı
Orta tablo iki işlevi karşılaştırıyor ve üç sütunun ikisinde aynı yanıtı veriyor. def ile
tanımlanan ve lambda ile üretilen nesnelerin ikisi de çağrılabilir, ikisi de aynı
türden. Ayrıldıkları tek sütun __name__: biri kendi adını taşıyor, öbürü <lambda>.
Bu tek fark önemsiz görünür ama bir sonraki dersin ölçtüğü şeyin habercisidir. Bir işlev nesnesi kendi adını bir öznitelikte taşır ve o öznitelik hata izlerinde, günlük kayıtlarında ve belge üretiminde kullanılır. Adı olmayan bir nesne bu bilgiyi veremez.
Sonraki satır nesne olmanın pratikteki karşılığını gösteriyor: iki işlev bir sözlükte değer olarak duruyor ve anahtarla çağrılıyor. Kural artık veriyle aynı yerde yaşıyor — adlandırılabilir, saklanabilir, geçirilebilir. Önceki dersin kavramaya gömülü kalan süzgeci burada bir sözlük değeri olabilirdi.
Tembel Yüksek Mertebeden Yerleşikler
Son üç satır map ve filter yerleşiklerini ölçüyor ve tanıdık bir sayı çıkıyor: kurulumda
0 nesne. İkisi de birer akış döndürüyor; verilen işlev kurulum anında hiç çağrılmıyor.
İlk öğe istendiğinde üretilen 1 oluyor, tümü gezildiğinde 200.
Bu, üçüncü dersin iddiasının yerleşikler üzerindeki karşılığıdır: map üretimden kaçınmaz,
onu geciktirir. İki yüz öğe istendiğinde iki yüz nesne doğar. Süzme de aynı düzendedir ve
iki yüz öğeden 100’ünü geçirir; geçmeyenler için yeni nesne kurulmadığı için üretim
sayısı yine iki yüzde kalır.
map ile bir kavrama arasındaki seçim bu yüzden bir maliyet seçimi değil, bir yazım
seçimidir — biri akış verir, öbürü kap. İkisinin sayıları önceki iki derste ölçüldü ve
burada değişmiyor.
Son satır üçüncü bir yüksek mertebeden aracı gösteriyor: standart kitaplığın reduce
işlevi, iki argümanlı bir işlevi akış boyunca uygulayarak tek bir değere iner. Toplam
19900 ve var edilen 0 — hiçbir ara kap kurulmuyor, her adımda yalnız biriken değer
taşınıyor. Kavrama bir kap, map bir akış, reduce bir değer üretir; üçü de aynı
işlevi argüman olarak alır.
İşlev Döndüren İşlev
Son iki blok yönü tersine çeviriyor: işlev artık argüman değil, dönen değer.
carpan_ureten çağrıldığında gövdesindeki def çalışır ve yeni bir işlev nesnesi üretir.
Üç çağrı üç ayrı nesne veriyor ve üçü ayrı sonuçlar üretiyor — her biri kendi çarpanını
taşıyor. Aynı argümanla yapılan iki çağrı bile ayrı nesne veriyor:
carpan_ureten(2) is carpan_ureten(2) yanlış. İşlev üretmek, her seferinde bir nesne
var etmektir; on ayrı çarpan on ayrı işlev nesnesi demektir.
Programlama Temelleri kursu kapanışı — iç işlevin dış kapsamdaki adı taşıması — bir kavram olarak kurmuştu; burada eklenen tek şey o taşımanın nesne sayısıyla ölçülmesi ve taşınanın bir değer değil, bir ad olmasıdır. Son tablo bu ayrımın sonucunu veriyor.
Üç yazım da üç işlev üretiyor ve üçünde de nesne sayısı 3. Ama sonuçlar aynı değil.
Gövdede serbest ad kullanan biçim [30, 30, 30] veriyor: üç işlevin üçü de aynı k
adına bakıyor ve çağrıldıkları anda o adın son değeri 3. Kapanış k’nin değerini
kopyalamadı, adına bağlandı.
Varsayılan argüman biçimi [10, 20, 30] veriyor. Python Temelleri kursunun fonksiyon
tanımı dersi varsayılan değerin bir kez, tanım anında değerlendirildiğini ölçmüştü;
orada bu bir tuzaktı, burada çözümdür. Değer tanım anında hesaplandığı için her işlev kendi
çarpanını yanında taşır. Standart kitaplığın partial aracı aynı sonucu argümanı önceden
bağlayarak verir.
Üç satır aynı yazımın üç okunuşudur ve aralarındaki fark ad ile değer arasındaki farktır. Kavramada döngü adı kavrama bitince kaybolur; ona bağlanan işlev kaybolmaz ve son değeri görür. Bir ada bağlanmak, bir değeri almak değildir.
Özet
defbir deyimdir ve bir işlev nesnesi üretir;lambdaaynı türden nesneyi ad bağlamadan verir. İkisi de çağrılabilir, ikisi de aynı türdendir ve ayrıldıkları tek yer__name__’dir.- İşlev nesnesi bir kapta değer olarak tutulabilir ve argüman olarak geçirilebilir; bir işlevi argüman alan ya da döndüren işlev yüksek mertebeden fonksiyondur.
- İki yüz öğede anahtar işlevi
sorted,minvemaxiçin 200 kez — öğe başına tam bir kez — çağrılır. Anahtarı saklamayan elle yazılmış sıralama aynı işlevi 39800 kez çağırır. sortedyeni kap kurar ve 200 öğeyi paylaşır, hiçbirini çoğaltmaz;liste.sortyerinde çalışır veNonedöndürür.mapvefilterkurulumda 0 nesne var eder, tümü gezildiğinde 200; süzme iki yüz öğeden 100’ünü geçirir.reduceise hiç kap kurmadan tek değere iner.
Sonraki Adım
Bu derste işlevler hep argüman olarak geçti; hiçbiri değiştirilmedi. Oysa bir işlev
döndürülebiliyorsa, bir işlevi alıp yerine geçecek başka bir işlev döndürmek de
mümkündür — özgün davranışı koruyup çevresine bir şeyler ekleyen bir sarmalayıcı. Sonraki
ders bu kalıbı ölçer: bir işlevi sarmalamak kaç nesne var eder, özgün nesneye ne olur, ve
sarmalanmış işlevin __name__ özniteliği ne söyler? Bu dersin önemsiz görünen tek sütunu
orada bir sonuç üretecek.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.