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.
returnfonksiyonu 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.