İçeriğe geç
academia.sh

Ders 10 / 18

Fonksiyon Tanımı ve Çağrı

Parametre ve argüman, dönüş değeri, çağrı yığıtı, koruma cümlesi ve iyi fonksiyonun ölçütleri.

İçindekiler

Önceki konunun son dersi, problemi alt problemlere bölmenin çözüm tasarımının parçası olduğunu söyledi ve bu bölmenin koddaki karşılığının fonksiyonlar olduğunu belirtti. Bu ders o karşılığı tanımlar.

Fonksiyon, bir işi adlandırılmış ve yeniden çağrılabilir bir birime kapatır. Bu birim iki yönlü bir sözleşmedir: çağıran ne vereceğini, fonksiyon ne döndüreceğini bilir.

Tanım ve Çağrı

Bir fonksiyon tanımı üç şey belirtir: adı, aldığı değerler ve gövdesi.

def ortalama(olcumler: list[int]) -> float:
    """Ölçümlerin aritmetik ortalamasını döndürür."""
    return sum(olcumler) / len(olcumler)


print(ortalama([12, 18, 7]))        # 12.333333333333334
print(ortalama([10, 20]))           # 15.0

Tanımdaki olcumler bir parametredir: gövde içinde kullanılan, çağrıda değer alacak ad. Çağrıdaki [12, 18, 7] ise bir argümandır: parametreye bağlanan gerçek değer. İki terim sık karıştırılır; ayrım, “beklenen” ile “verilen” arasındadır.

Tanım yürütülmez, yalnızca fonksiyonu tanımlar. Gövdedeki deyimler ancak çağrıda çalışır. Bu nedenle tanım içindeki bir hata, fonksiyon çağrılmadıkça ortaya çıkmayabilir.

Dönüş Değeri

return deyimi iki iş yapar: fonksiyonu sonlandırır ve bir değer üretir. Değer, çağrının yerine geçer; bu nedenle çağrı bir ifadedir ve başka ifadelerin içine yerleştirilebilir.

Bir fonksiyon birden çok değer döndürebilir; çoğu dilde bu, değerleri tek bir bileşik değerde toplayarak yapılır:

def ozetle(olcumler: list[int]) -> tuple[float, int]:
    """Ortalama ve en büyük değeri birlikte döndürür."""
    en_buyuk = olcumler[0]
    for olcum in olcumler:
        if olcum > en_buyuk:
            en_buyuk = olcum
    return ortalama(olcumler), en_buyuk


ort, enb = ozetle([12, 18, 7, 25, 14])
print(ort, enb)                     # 15.2 25

Hiçbir değer döndürmeyen fonksiyonlar da vardır; bunlar iş yapmak için çağrılır — ekrana yazmak, dosyaya kaydetmek gibi. Bu ayrım, yazılım tasarımı müfredatında komut–sorgu ayrımı olarak adlandırılır: bir fonksiyon ya durumu değiştirir ya değer döndürür, ikisini birden yapması karışıklık üretir.

Varsayılan ve Adlandırılmış Argümanlar

Parametrelere varsayılan değer verilebilir; çağrıda belirtilmezse varsayılan kullanılır.

def sinirlari_asanlar(olcumler: list[int], sinir: int = 20) -> list[int]:
    """Sınırı aşan ölçümleri döndürür."""
    return [olcum for olcum in olcumler if olcum > sinir]


print(sinirlari_asanlar([12, 18, 7, 25, 14]))            # [25]
print(sinirlari_asanlar([12, 18, 7, 25, 14], 10))        # [12, 18, 25, 14]
print(sinirlari_asanlar([12, 18, 7, 25, 14], sinir=15))  # [18, 25]

Son satırdaki yazım, argümanı adıyla verir. Bunun iki yararı vardır: sıra hatası olanaksızlaşır ve çağrı okunduğunda değerin ne anlama geldiği görünür. Sayıların ve mantıksal değerlerin argüman olarak geçtiği çağrılarda bu, okunabilirlik açısından belirgin bir fark yaratır.

Bir uyarı: varsayılan değerin değiştirilebilir bir nesne olması, birçok dilde şaşırtıcı davranışa yol açar; varsayılan yalnızca bir kez oluşturulur ve çağrılar arasında paylaşılır. Değiştirilemez değerlerin varsayılan olarak seçilmesi bu tuzağı önler.

Çağrı Yığıtı

Önceki kursun son dersi, her çağrının yığıta bir çerçeve eklediğini kurmuştu. Bu ders o mekanizmanın programcı tarafından görülen yüzüdür.

Bir çağrı yapıldığında akış fonksiyonun gövdesine geçer; dönüş adresi, argümanlar ve yerel değişkenler çerçevede tutulur. return çalıştığında çerçeve bırakılır ve akış çağrının yapıldığı yerden devam eder.

Çağrılar iç içe olduğunda çerçeveler üst üste birikir. ozetle fonksiyonu ortalama fonksiyonunu çağırdığında, ikinci çerçeve birincinin üzerine eklenir; ortalama döndüğünde yalnızca kendi çerçevesi bırakılır.

Bu yığının pratik karşılığı, hata iletileriyle birlikte gelen izleme dökümüdür: hangi fonksiyonun hangi fonksiyondan çağrıldığı, en dıştan en içe doğru listelenir. Hata ayıklarken okunacak ilk şey bu listedir; hatanın oluştuğu yer en alttaki satır, oraya nasıl gelindiği ise yukarısıdır.

Ön Koşullar ve Koruma Cümlesi

Bir fonksiyonun düzgün çalışması için girdisinin sağlaması gereken koşullara ön koşul denir. ortalama fonksiyonunun ön koşulu, listenin boş olmamasıdır; boş listede bölme sıfıra bölmeye dönüşür.

Ön koşullar fonksiyonun başında ele alınır. Bu yazıma koruma cümlesi (guard clause) denir:

def ortalama(olcumler: list[int]) -> float:
    """Ölçümlerin ortalamasını döndürür. Liste boş olmamalıdır."""
    if not olcumler:                          # koruma cümlesi
        raise ValueError("ölçüm listesi boş")
    return sum(olcumler) / len(olcumler)

Koruma cümlesi, geçersiz durumu hemen eleyip asıl işi girintisiz bırakır. Alternatifi, tüm gövdeyi bir else bloğuna almaktır; sonuç aynı, okunabilirlik farklıdır. Koşullu dallanma dersinde söz verilen sadeleştirme budur.

İkinci karar, geçersiz girdide ne yapılacağıdır: hata fırlatmak, özel bir değer döndürmek veya varsayılan bir sonuç üretmek. Seçim çağıranın ihtiyacına bağlıdır; kritik olan, sözleşmenin belgelenmesi ve tutarlı uygulanmasıdır.

Sözleşmeyi Yazmak

Bir fonksiyonun sözleşmesi yalnızca imzasında değil, belgesinde de durur. Gövdenin hemen başına yazılan açıklama, çağıranın bilmesi gereken her şeyi içerir:

  • Ne yapar — tek cümle, eylem fiiliyle.
  • Parametreler — anlamları, birimleri ve geçerli aralıkları.
  • Dönüş değeri — neyi temsil ettiği.
  • Hata durumları — hangi koşulda hangi hatanın üretildiği.
  • Yan etkiler — varsa, açıkça.
def sinirlari_asanlar(olcumler: list[int], sinir: int = 20) -> list[int]:
    """Sınırı aşan ölçümleri yeni bir listede döndürür.

    olcumler: ölçüm değerleri; değiştirilmez.
    sinir: karşılaştırma eşiği; varsayılan 20.
    dönüş: sınırdan büyük ölçümler, özgün sıralarıyla.
    """
    return [olcum for olcum in olcumler if olcum > sinir]

Belgenin kapsamı, fonksiyonun görünürlüğüyle orantılıdır: yalnızca kendi modülünde kullanılan yardımcı bir fonksiyon tek satırla yetinebilirken, dışarıya açılan bir arayüz tam sözleşmeyi hak eder.

Belgede yazılmayan her varsayım, çağıranın tahmin etmesi gereken bir ayrıntıya dönüşür. Fonksiyonun listeyi değiştirip değiştirmediği bunun tipik örneğidir ve sonraki dersin konusudur.

İyi Bir Fonksiyon

Fonksiyonun teknik tanımı basittir; iyi bir fonksiyonun ölçütleri ise deneyimle oluşmuştur:

  • Tek bir iş yapar. Adı “ve” ile kuruluyorsa (hesapla_ve_yazdir) muhtemelen ikiye bölünmelidir.
  • Adı ne yaptığını söyler. Çağrıyı okuyan, gövdeyi okumadan ne olacağını bilmelidir.
  • Kısadır. Uzunluk kuralı keskin değildir; ölçüt, gövdenin tek ekranda ve tek soluk hâlde okunabilmesidir.
  • Az sayıda parametre alır. Parametre sayısı arttıkça çağrının doğru yazılma olasılığı düşer.
  • Sürprizsizdir. Adının söylemediği bir yan etki üretmez.

Bu ölçütlerin tamamı, Yazılım Tasarımı ve Mimari İlkeler müfredatında gerekçeleriyle ele alınır. Bu kurs için yeterli olan, ölçütlerin varlığını bilmek ve fonksiyon yazarken sormaktır.

Özet

  • Fonksiyon, bir işi adlandırılmış ve yeniden çağrılabilir bir birime kapatır; parametre beklenen adı, argüman verilen değeri belirtir.
  • return fonksiyonu sonlandırır ve değer üretir; çağrı bir ifadedir ve başka ifadelerin içinde kullanılabilir.
  • Varsayılan ve adlandırılmış argümanlar çağrıyı kısaltır ve okunur kılar; değiştirilebilir varsayılan değerler tuzaklıdır.
  • Her çağrı yığıta bir çerçeve ekler; hata iletisindeki izleme dökümü bu yığının görüntüsüdür.
  • Ön koşullar fonksiyonun başında koruma cümleleriyle ele alınır; geçersiz girdide ne yapılacağı sözleşmenin parçasıdır.

Sonraki Adım

Fonksiyona bir liste verildiğinde, fonksiyon içinde yapılan değişiklik çağıran tarafta görünür mü? Yanıt dile ve değerin tipine bağlıdır ve bu, değişkenler dersinde tanıtılan iki modelin doğrudan sonucudur. Sonraki ders argüman aktarımını ele alacak.

İ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