İçeriğe geç
academia.sh

Ders 09 / 16

Fonksiyon Tanımı

Varsayılan değer her çağrıda değil, tanım okunduğunda bir kez değerlendirilir: beş çağrı bir tek değerlendirme yapar, değiştirilebilir bir varsayılan beş çağrıda beş öğe biriktirir ve altı parametrenin dördü tek bir yolla verilebilir.

İçindekiler

Önceki ders döngünün bir protokol olduğunu ölçtü ve turların bitişine bir istisnanın karar verdiğini gösterdi. Döngü gövdesi çoğu zaman bir işlev çağırır; her turda ona başka değerler geçer.

Programlama Temelleri kursunun fonksiyon dersi parametre ile argüman ayrımını, dönüş değerini ve çağrı çerçevesini kurmuştu; o ders fonksiyonun ne olduğunu anlatıyordu ve argüman geçirmeyi tek bir soruya indirgemişti — hangi değer hangi parametreye gider. Python’da bu sorunun yanıtı tek satırlık değildir: bir parametre yalnız konumundan, yalnız adından ya da ikisinden de verilebilir; artan argümanlar toplanabilir; parametrelere varsayılan değer bağlanabilir. Bunların içinde bir tanesi değer hakkında değil, zamanlama hakkındadır ve bu dersin ölçtüğü şey odur: bir varsayılan değer ne zaman hesaplanır — her çağrıda mı, yoksa bir kez mi?

Varsayılan Bir İfadedir

def bir deyimdir ve çalıştırıldığında bir fonksiyon nesnesi üretir. Üretim sırasında gövde çalıştırılmaz — o ders kurulmuştu — ama imzadaki varsayılan değer ifadeleri çalıştırılır. Sonuçları hesaplanır ve fonksiyon nesnesinin üzerine yazılır.

Bunun anlamı şudur: varsayılan değer, çağrının değil tanımın parçasıdır. Çağrı sırasında yeniden hesaplanmaz; tanım okunduğunda bir kez hesaplanmış olan nesne her çağrıda yeniden bağlanır. Beş çağrı, beş ayrı varsayılan üretmez; beş kez aynı nesneyi kullanır.

“Bir kez” ifadesi programın ömrü boyunca bir kez demek değildir; tanım deyimi her çalıştığında bir kez demektir. En dış düzeydeki bir tanım dosya okunurken bir kez çalışır, dolayısıyla varsayılanı da bir kez hesaplanır. Başka bir fonksiyonun gövdesine yazılmış bir tanım ise dış fonksiyonun her çağrısında yeniden çalışır ve varsayılanını o çağrıya özel olarak yeniden hesaplar. Ölçülen kural değişmiyor — varsayılan hâlâ tanımın parçası — ama tanımın kaç kez çalıştığı değişiyor.

Değer değiştirilemez bir nesneyse bu ayrım görünmez. Bir sayının ya da bir dizginin her çağrıda aynı nesne olması hiçbir sonuç doğurmaz, çünkü onu değiştirmenin yolu yoktur. Ayrım, varsayılan değiştirilebilir olduğunda ortaya çıkar: gövde onu değiştirirse, değişiklik bir sonraki çağrıya taşınır.

Nöbetçi Değer

Bu davranışın karşısına konan kalıp, varsayılanı None yapıp gerçek değeri gövdede üretmektir. None değiştirilemez, dolayısıyla paylaşılması bir sonuç doğurmaz; gövde her çağrıda taze bir nesne kurar. Bu role nöbetçi değer denir: kendisi kullanılmaz, yalnız “argüman verilmedi” bilgisini taşır.

Nöbetçinin None olması zorunlu değildir ama gerekçesi vardır. Bir parametrenin geçerli değerleri arasında None da bulunabiliyorsa nöbetçi olarak None seçilemez — o zaman “verilmedi” ile “None verildi” ayrılamaz. Bu durumda ayrı bir nesne nöbetçi yapılır.

Bir Argüman Kaç Yolla Verilir

İkinci ölçü, argüman biçimlerinin sayısıdır. Python imzada iki ayraç tanır. Eğik çizgi, solundaki parametrelerin yalnız konumsal argümanla verilebileceğini bildirir. Yıldız, sağındaki parametrelerin yalnız anahtarlı argümanla verilebileceğini bildirir. İkisinin arasında kalanlar her iki yolla da verilebilir.

Buna iki toplayıcı eklenir. Yıldızlı bir parametre, karşılanmamış konumsal argümanları bir demette toplar; çift yıldızlı bir parametre, karşılanmamış anahtarlı argümanları bir sözlükte toplar. İkisine birlikte değişken sayıda argüman denir ve imzanın kaç argüman alabileceğini üst sınırdan kurtarır.

İmzadaki bu parçaların sırası serbest değildir: önce yalnız konumsal olanlar, sonra her iki yolla verilebilenler, sonra konumsal toplayıcı, sonra yalnız anahtarlı olanlar, en sonda anahtarlı toplayıcı gelir. Sıra, bağlanmayı tek anlamlı kılmak için vardır. Çağrıdaki argümanlar soldan sağa okunur; konumsal olanlar imzadaki konumsal parametreleri sırayla doldurur, dolduramadıkları toplayıcıya girer, anahtarlı olanlar adlarıyla eşleşir, eşleşemeyenler anahtarlı toplayıcıya düşer. Bu sıra bozulsaydı aynı çağrının birden çok geçerli bağlanması olurdu ve hangisinin seçileceği yazılana bakılarak söylenemezdi.

Aynı gerekçe varsayılan taşıyan parametrelerin taşımayanlardan sonra gelmesini de zorunlu kılar — konumsal tarafta. Yalnız anahtarlı tarafta bu kısıt yoktur: orada eşleşme ada göre yapıldığı için varsayılanı olan ve olmayan parametreler karışık sırayla yazılabilir.

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

  • PA16 — Kâhin kurgunun kendisidir: varsayılan ifadesi bir işlev çağrısıdır ve her değerlendirilişini kaydeder; “kaç kez değerlendirildi” sorusunun ölçüsü bu kayıttır.
  • PA17 — Değerlendirme sayısı iki noktada okunur: tanım deyimi çalıştıktan hemen sonra ve beş çağrı yapıldıktan sonra. İkisinin farkı, çağrıların kattığı değerlendirmedir.
  • PA18 — İki fonksiyon aynı işi yapar ve yalnız varsayılanında ayrılır: biri boş bir liste, öbürü None nöbetçisi. Gövdelerin geri kalanı birebir aynıdır.
  • PA19 — Paylaşım, art arda iki çağrının döndürdüğü nesne is ile karşılaştırılarak gösterilir; hiçbir kimlik sayısı basılmaz. Karşılaştırılan nesne bir listedir; değiştirilemez küçük değerlerin önbelleklenmesi ölçüme karışmaz.
  • PA20 — İmza altı parametre ve iki toplayıcı taşır; sayılar bu imzaya özgüdür ve başka bir imzaya taşınmaz.
  • PA21 — Bağlanamayan çağrılarda yalnız istisnanın sınıfı yazılır, iletisi yazılmaz; ileti metni ölçünün parçası değildir.
  • PA22 — Varsayılanın nerede durduğu, fonksiyon nesnesinin varsayılanları taşıyan alanı okunarak gösterilir; okunan şey bir bellek bilgisi değil, dilin kendi tanımladığı bir alandır.
  • PA23 — Çağrı yerindeki açma biçimi, imzadaki toplayıcılarla aynı ayraçları kullanır ama ters yönde çalışır; ölçümde ikisi aynı imza üzerinde karşılaştırılır.

Ölçüm

"""Varsayilan deger ne zaman degerlendirilir ve argumanlar nasil baglanir."""

DEGERLENDIRME = []


def damga(ad):
    """Her cagrildiginda kaydeder; varsayilan ifadesi olarak kullanilir."""
    DEGERLENDIRME.append(ad)
    return len(DEGERLENDIRME)


def sirala(x, d=damga("varsayılan ifade")):
    return d


print(f"tanımdan hemen sonra değerlendirme: {len(DEGERLENDIRME)}")
sonuclar = [sirala(i) for i in range(5)]
print(f"beş çağrıdan sonra değerlendirme: {len(DEGERLENDIRME)}")
print(f"beş çağrının döndürdüğü: {sonuclar}")


def biriktir(x, kova=[]):
    kova.append(x)
    return kova


def nobetci(x, kova=None):
    if kova is None:
        kova = []
    kova.append(x)
    return kova


print()
print(f"{'çağrı':>6s} {'kova=[] uzunluk':>16s} {'kova=None uzunluk':>18s}")
onceki_a = onceki_b = None
for i in range(1, 6):
    a, b = biriktir(i), nobetci(i)
    print(f"  {i:4d} {len(a):16d} {len(b):18d}")
    if i > 1:
        ayni_a, ayni_b = a is onceki_a, b is onceki_b
    onceki_a, onceki_b = a, b
print(f"art arda iki çağrının kovası aynı nesne mi: "
      f"kova=[] {ayni_a}, kova=None {ayni_b}")
print(f"fonksiyon nesnesinde duran varsayılan: "
      f"biriktir {biriktir.__defaults__}, nobetci {nobetci.__defaults__}")
print(f"dönen liste, fonksiyonda duran varsayılanın kendisi mi: "
      f"{biriktir(6) is biriktir.__defaults__[0]}")


def kaydol(a, b, /, c, d=40, *ek, e, f=60, **kalan):
    """a,b yalnız konumsal; e,f yalnız anahtarlı; ek ve kalan artanı toplar."""
    return {"a": a, "b": b, "c": c, "d": d, "ek": ek, "e": e, "f": f,
            "kalan": kalan}


CAGRILAR = (
    "kaydol(1, 2, 3, e=5)",
    "kaydol(1, 2, c=3, e=5)",
    "kaydol(1, 2, 3, 4, 7, 8, e=5, f=9, g=0)",
    "kaydol(1, 2, 3)",
    "kaydol(a=1, b=2, c=3, e=5)",
)

print()
print(f"{'çağrı':<42s} bağlanma")
for metin in CAGRILAR:
    try:
        s = eval(metin)
        b = (f"a={s['a']} b={s['b']} c={s['c']} d={s['d']} ek={s['ek']} "
             f"e={s['e']} f={s['f']} kalan={s['kalan']}")
    except TypeError as hata:
        b = type(hata).__name__
    print(f"  {metin:<40s} {b}")

KONUMSAL = (1, 2, 3)
ANAHTARLI = {"e": 5, "g": 0}
acilan = kaydol(*KONUMSAL, **ANAHTARLI)
print(f"  {'kaydol(*KONUMSAL, **ANAHTARLI)':<40s} "
      f"a={acilan['a']} b={acilan['b']} c={acilan['c']} d={acilan['d']} "
      f"ek={acilan['ek']} e={acilan['e']} f={acilan['f']} "
      f"kalan={acilan['kalan']}")

print()
print("parametre  konumsal verilebilir  anahtarlı verilebilir")
for ad, konumsal, anahtarli in (("a", True, False), ("b", True, False),
                                ("c", True, True), ("d", True, True),
                                ("e", False, True), ("f", False, True)):
    print(f"  {ad:<8s} {('evet' if konumsal else 'hayır'):>18s}"
          f" {('evet' if anahtarli else 'hayır'):>21s}")
tanımdan hemen sonra değerlendirme: 1
beş çağrıdan sonra değerlendirme: 1
beş çağrının döndürdüğü: [1, 1, 1, 1, 1]

 çağrı  kova=[] uzunluk  kova=None uzunluk
     1                1                  1
     2                2                  1
     3                3                  1
     4                4                  1
     5                5                  1
art arda iki çağrının kovası aynı nesne mi: kova=[] True, kova=None False
fonksiyon nesnesinde duran varsayılan: biriktir ([1, 2, 3, 4, 5],), nobetci (None,)
dönen liste, fonksiyonda duran varsayılanın kendisi mi: True

çağrı                                      bağlanma
  kaydol(1, 2, 3, e=5)                     a=1 b=2 c=3 d=40 ek=() e=5 f=60 kalan={}
  kaydol(1, 2, c=3, e=5)                   a=1 b=2 c=3 d=40 ek=() e=5 f=60 kalan={}
  kaydol(1, 2, 3, 4, 7, 8, e=5, f=9, g=0)  a=1 b=2 c=3 d=4 ek=(7, 8) e=5 f=9 kalan={'g': 0}
  kaydol(1, 2, 3)                          TypeError
  kaydol(a=1, b=2, c=3, e=5)               TypeError
  kaydol(*KONUMSAL, **ANAHTARLI)           a=1 b=2 c=3 d=40 ek=() e=5 f=60 kalan={'g': 0}

parametre  konumsal verilebilir  anahtarlı verilebilir
  a                      evet                 hayır
  b                      evet                 hayır
  c                      evet                  evet
  d                      evet                  evet
  e                     hayır                  evet
  f                     hayır                  evet

Bir Kez Değerlendirilen İfade

İlk üç satır zamanlama sorusunu kapatıyor. Tanım deyimi çalıştıktan hemen sonra, henüz hiçbir çağrı yapılmadan, değerlendirme sayısı 1. Varsayılan ifadesi tanım okunurken çalışmış.

Beş çağrıdan sonra sayı yine 1. Çağrılar hiçbir değerlendirme eklemedi. Üçüncü satır bunu doğrudan gösteriyor: beş çağrının beşi de aynı değeri döndürüyor — hepsi tanım sırasında hesaplanmış tek nesneyi görüyor.

Bir fonksiyonun çağrılma sayısı ile varsayılanının değerlendirilme sayısı arasında bağlantı yoktur. Bu, imzada yazılan ifadenin gövdede yazılmış gibi okunmasının neden yanlış olduğunu açıklar. def satırındaki bir ifade, gövdedeki bir ifadeyle aynı yerde durmuyor: biri bir kez, öbürü her çağrıda çalışıyor.

Paylaşılan Kova

Orta tablo bu tek değerlendirmenin bedelini ödetiyor. Sol sütunda varsayılan boş bir liste; beş çağrı boyunca uzunluk 1, 2, 3, 4, 5 diye büyüyor. Her çağrı bir öncekinin bıraktığı listeyi buluyor.

Sağ sütunda nöbetçi kalıbı; uzunluk beş çağrıda da 1. Her çağrı kendi listesini kuruyor ve bir öncekinden hiçbir şey devralmıyor.

Alt satır nedeni kimlik üzerinden söylüyor. kova=[] biçiminde art arda iki çağrının döndürdüğü nesne is sınamasından doğru çıkıyor: aynı nesne. Nöbetçi kalıbında yanlış: ayrı nesneler. Fonksiyonun her çağrıda “yeni bir liste” aldığı sanısı burada kırılıyor — liste tanım sırasında bir kez kurulmuş, fonksiyon nesnesine bağlanmış ve o tarihten sonra her çağrının değiştirdiği tek bir nesne olarak yaşıyor.

Sondan bir önceki satır varsayılanın nerede durduğunu gösteriyor ve bu, dersin en doğrudan kanıtıdır. Fonksiyon nesnesinin varsayılanları taşıyan alanı okunduğunda içinde [1, 2, 3, 4, 5] duruyor — beş çağrının biriktirdiği liste, çağrılar bittikten sonra fonksiyonun kendisinde. Nöbetçi kalıbında aynı alanda yalnız None var; orada biriken hiçbir şey yok, çünkü biriken listeler gövdede kurulup çağrıyla birlikte bırakıldı.

Son satır halkayı kapatıyor: bir çağrının döndürdüğü liste ile fonksiyon nesnesinde duran varsayılan is sınamasından doğru çıkıyor. İkisi tek bir nesne. “Varsayılan tanımın parçasıdır” cümlesi burada bir yorum olmaktan çıkıyor; fonksiyonun üzerinde durduğu okunabiliyor.

Sonucun kendisi bir hata değil, kuralın doğrudan sonucudur. Programlama Temelleri kursu bu tuzağı bir uyarı olarak yazmıştı; buradaki sayılar uyarının gerekçesini veriyor. Kuralı bilen için davranış şaşırtıcı değildir: gövde varsayılanı değiştiriyorsa, değiştirdiği şey tanımın parçasıdır.

Altı Parametre, İki Ayraç

Alt iki tablo bağlanmayı ölçüyor. İlk iki çağrı aynı sonucu veriyor: c bir kez konumdan, bir kez adından veriliyor ve bağlanma birebir aynı. d, e ve f verilmediği için d ile f varsayılanlarına düşüyor, e ise çağrıda mutlaka yazılmak zorunda — çünkü varsayılanı yok ve yalnız anahtarlı verilebiliyor.

Üçüncü çağrı iki toplayıcıyı da dolduruyor. Altı konumsal argüman veriliyor; ilk dördü a, b, c, d parametrelerini karşılıyor, artan iki tanesi ek demetine giriyor. Anahtarlı tarafta e ile f kendi parametrelerini buluyor, imzada karşılığı olmayan g ise kalan sözlüğüne düşüyor. Toplayıcılar, bağlanamayan argümanı hataya dönüştürmek yerine toplayan parametrelerdir.

Son iki çağrı bağlanamıyor ve ikisi de TypeError veriyor. Dördüncü çağrıda e eksik: varsayılanı olmayan, yalnız anahtarlı bir parametre yazılmadan çağrı kurulamaz. Beşinci çağrıda tam tersi olmuş — a ile b adlarıyla verilmiş, oysa eğik çizginin solundalar ve yalnız konumdan alınabilirler. Adlarıyla verilen a ile b, parametreleri karşılamak yerine kalan sözlüğüne düşecek adlar olarak görülüyor ve iki konumsal parametre boş kalıyor.

Altıncı satır aynı ayraçların çağrı yerinde ne yaptığını gösteriyor. İmzada yıldız argümanları toplarken, çağrıda tam tersini yapar: bir demeti konumsal argümanlara, bir sözlüğü anahtarlı argümanlara açar. Üç öğelik demet a, b, c parametrelerini karşılıyor; sözlükteki e kendi parametresini buluyor, g ise kalan sözlüğüne düşüyor.

İki yönün aynı ayraçları kullanması bir rastlantı değil: toplama ile açma birbirinin tersidir ve bir fonksiyonun aldığı argümanları başka bir fonksiyona olduğu gibi geçirmenin yolu ikisini yan yana kullanmaktır. Bu, sarmalayan fonksiyon yazmanın temel kalıbıdır — sarmalayan, imzayı bilmeden argümanları toplar ve sarmaladığı fonksiyona açar.

Son tablo imzanın tümünü özetliyor. Altı parametrenin dördü tek bir yolla verilebilir — a ile b yalnız konumdan, e ile f yalnız addan. Yalnız ikisi her iki yolu da kabul ediyor. Ayraçların işi budur: bir parametrenin kaç yolla verilebileceğini daraltmak. Daraltmanın karşılığı, çağrı yazımının imzayla birlikte değişmemesidir — konumsal tutulan bir parametrenin adı değiştiğinde hiçbir çağrı bozulmaz, anahtarlı tutulan bir parametrenin sırası değiştiğinde de öyle.

Özet

  • Varsayılan değer ifadesi çağrının değil tanımın parçasıdır: tanım okunurken bir kez değerlendirilir ve beş çağrı bu sayıyı değiştirmez — çağrı sayısıyla değerlendirme sayısı arasında bağlantı yoktur.
  • Değiştirilemez bir varsayılanda bu görünmez; değiştirilebilir bir varsayılanda gövde tanımın parçasını değiştirir ve beş çağrı tek bir listede beş öğe biriktirir.
  • None nöbetçisi kalıbı taze nesneyi gövdede kurar; art arda iki çağrının kovası is sınamasından ayrı nesneler olarak çıkar ve uzunluk her çağrıda 1 kalır.
  • Eğik çizgi solundaki parametreleri yalnız konumsal, yıldız sağındakileri yalnız anahtarlı yapar; ölçülen imzada altı parametrenin dördü tek yolla, ikisi iki yolla verilebilir.
  • Yıldızlı ve çift yıldızlı toplayıcılar karşılanmamış konumsal ve anahtarlı argümanları toplar; aynı ayraçlar çağrı yerinde ters yönde çalışır ve bir demetle bir sözlüğü argümanlara açar. Toplanamayan argüman ile eksik kalan parametre TypeError üretir.
  • Varsayılan, fonksiyon nesnesinin kendi alanında durur: beş çağrıdan sonra o alanda biriken listenin tamamı okunabilir ve dönen liste is sınamasıyla onunla aynı nesne çıkar.

Sonraki Adım

Fonksiyon gövdesi, argümanlarla gelen adların yanında başka adlar da kullanır: gövdede kurulanlar, onu çevreleyen tanımdan gelenler, dosyanın en dışında duranlar ve dilin kendi sağladıkları. Programlama Temelleri kursu bu arama sırasını dilden bağımsız bir kavram olarak kurmuştu. Python’da sıra dört düzeylidir ve düzeylerden hangisinde bulunacağını belirleyen şey adın nerede okunduğu değil, nerede atandığıdır. Sonraki ders aynı adı dört düzeyde birden tanımlayıp hangisinin kazandığını ölçer — ve gövdeye tek bir atama satırı eklemenin aynı adı nasıl başka bir düzeye taşıdığını.

İ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