Fonksiyon tasarımı ve doğrulama

Fonksiyon yazarken def satırını doğru yazmak yetmez. Fonksiyonun hangi girdileri beklediğini, hangi sonucu döndürdüğünü ve yanlış kullanıldığında ne olacağını da düşünmek gerekir.

Bu hafta her fonksiyon için ne alacağını ve ne döndüreceğini baştan yazacağız. Yerel ve küresel kapsamı, varsayılan parametreleri göreceğiz. Beklenen sonucu assert ile kontrol etmeyi ve sınır değerlerini atlamadan denemeyi öğreneceğiz.

Bu sayfadaki kod hücreleri kendiliğinden çalışmaz. Her hücrede önce çıktıyı tahmin edin, sonra Run Code düğmesine basın. Kodu değiştirip yeniden çalıştırabilir, Start Over ile ilk hâline döndürebilirsiniz.

Bu bölümün kapsamı

NoteSınavda sorulur
  • Fonksiyon sözleşmesi ve ön koşullar
  • Yerel değişkenin kapsamı ve dış bağımlılığı parametreye çevirme
  • Varsayılan parametrenin davranışı
  • assert ile beklenen davranışı doğrulama
  • float sonuçlarda tam eşitlik yerine toleranslı karşılaştırma
  • Sınır ve uç durumların seçilerek sınanması
  • Saf fonksiyonun özellikleri ve global kullanımına temkin
  • unittest ve pytest gibi test çerçeveleri
  • Tip ipuçları (type hints)

Bu başlıklar konunun devamıdır. İleride karşınıza çıkar, ama bu derste ezberlemeniz beklenmiyor.

Hesabı paylaştıran tarifi nasıl denersiniz?

Arkadaş grubunuz yemekten sonra hesabı eşit paylaştırıyor. Bir arkadaşınız bunun için bir tarif yazdı:

BAŞLA
  hesabı al
  kişi sayısını al
  hesaba yüzde 10 bahşiş ekle
  sonucu kişi sayısına böl
  her kişinin payını göster
BİTİR

Tarif bir telefon uygulamasına dönüşecek ve her masada kullanılacak. Arkadaşınız şöyle diyor: “Dört kişiyle, 400 TL’lik hesapla denedim. Herkes 110 TL ödedi. Tarif doğru.”

Uygulama yapılmadan önce tarifi sizin denemeniz isteniyor. Şu soruların cevabını sözde kodla, Türkçe ve madde madde yazın:

  1. Tarif hangi bilgileri alıyor? Bu bilgiler hangi değerleri alamaz?
  2. Tarifi başka hangi masalarla denersiniz? Her masa için payın ne çıkması gerektiğini denemeden önce yazın.
  3. Bahşiş oranı tarifte sabit: yüzde 10. Bahşiş bırakmayan ya da yüzde 15 bırakan masa ne yapacak? Bir arkadaşınız “oranı kasadaki tabeladan okusun” diyor. Tabela değişirse bugün yaptığınız denemeler yarın da geçerli olur mu?
  4. Üç kişi 100 TL’lik hesabı bahşişsiz paylaşıyor. Herkes kaç lira öder? Üç payı toplayınca 100 TL’yi buluyor musunuz?

Python bilmeniz gerekmiyor: “Kişi sayısı 0 olamaz” da bir cevap. Cevaplarınızı bir kenara yazın. Bölümde her birinin Python’da nasıl yazıldığını göreceğiz. Son alıştırmadan önce listenize döneceğiz.

Fonksiyon sözleşmesi

Bir fonksiyonun nasıl kullanılacağını düşünmek için üç basit soru sorabiliriz:

  1. Ne alıyor?
  2. Ne yapıyor?
  3. Ne döndürüyor?

Bu üç sorunun yazılı cevabına fonksiyonun sözleşmesi denir. Aşağıdaki fonksiyon bahşişsiz hesabı kişi sayısına bölüyor. Sözleşmesi şöyle yazılabilir:

Girdi:
- hesap: 0 ya da daha büyük sayı
- kisi: 0'dan büyük tam sayı

Davranış:
- hesap değerini kisi değerine böler

Çıktı:
- kişi başına düşen pay

Hücre ne yazar? Sonra kisi_basi(400, 4) çağrısındaki 4’ü 0 yapıp yeniden çalıştırın.

Sözleşmedeki “kisi 0’dan büyük olmalı” şartı bir ön koşuldur, yani fonksiyon çağrılmadan önce girdinin sağlaması gereken bir şart. Bir sayı sıfıra bölünemediği için bu şart gerekir. Şart sağlanmazsa Python ZeroDivisionError hatası verir.

Note

Sözleşmeyi Python çalıştırmaz. Fonksiyonu yazan ve kullanan kişi için tutulan bir nottur: fonksiyon ne bekler, ne döndürür.

Ön koşul ve sonuç

Bir fonksiyonun doğru çalışması için bazı varsayımlar gerekebilir. Aşağıdaki yüzde hesabında oran 3 iken hücre hata verir mi?

Fonksiyon oran = 3 ile de hata vermeden çalışır. Ama problemimizde oran 0 ile 1 arasında olmalıysa bunu sözleşmeye yazmalıyız:

Ön koşul:
0 <= oran <= 1

Beklenen sonuç da şöyle yazılır:

Sonuç:
tutar * oran değeri döndürülür

Ön koşulu ve sonucu yazdığınızda test yazmak da kolaylaşır, çünkü neyi deneyeceğiniz bellidir.

Kapsam (scope): ad nerede geçerlidir?

6. haftada fonksiyon içindeki değişkenlerin çoğunlukla fonksiyonun kendi çalışma alanında kaldığını söylemiştik. Bir adın kullanılabildiği kod bölgesine kapsam (scope) denir. Şimdi bunu biraz daha açalım.

Hücre ne yazar? Sonra son satırın başındaki # işaretini silip yeniden çalıştırın. Ne olur?

sonuc, fonksiyonun içinde oluşturulan yerel (local) bir değişkendir. Fonksiyonun dışındaki kod bu yerel adı kullanamaz.

Fonksiyon dışında oluşturulan bir ad ise küresel (global) kapsamdadır ve fonksiyonun içinden okunabilir. Aşağıdaki hücrede kdv_hesapla(100) iki kez çağrılıyor. İki satır aynı mı yazar?

İlk çağrıda dışarıdaki oran 0.20’dir ve fonksiyon 20.0 döndürür. Sonra oran 0.10 yapılır. Aynı kdv_hesapla(100) çağrısı bu kez 10.0 döndürür. Fonksiyonun sonucu yalnız argümanına değil, dışarıdaki bir değişkene de bağlıdır.

Kod çalışır, fonksiyon dışarıdaki oran değerini okuyabilir. Ama fonksiyonun sonucunu anlamak için artık yalnızca parametrelerine değil, dışarıdaki bir değere de bakmamız gerekir. Aynı çağrı programın neresinde yapıldığına göre farklı sonuç verebilir.

Çoğu hesaplamada oran gibi dışarıdan gelen bir değeri parametreyle vermeyi tercih ederiz:

Bu sürümde fonksiyonun hangi bilgiye ihtiyaç duyduğu imzasından, yani def satırından okunur.

Important

Aynı ad fonksiyonun içinde ve dışında kullanılabilir. Bu iki adın her zaman aynı değişken olduğunu varsaymayın; adın nerede oluşturulduğuna ve nerede kullanıldığına bakın. Python’ın bir adı hangi sırayla aradığını anlatan LEGB kuralları sınavda sorulmaz. Yerel ile küresel arasındaki farkı ve dışarıdan gelen değeri parametreyle alma alışkanlığını bilmeniz yeterlidir.

Fonksiyon içinde oluşturulan yerel değişkenin yalnızca o fonksiyon çağrısının kapsamında bulunması.

Yerel kapsam

Varsayılan parametreler

Bazen bir parametre çoğu çağrıda aynı değeri alır. Böyle bir parametreye tanımda varsayılan değer (default value) verebiliriz. Aşağıdaki fonksiyonda oran verilmezse 0.10 kullanılır. İki satır ne yazar?

İkinci argüman verilmezse varsayılan değer kullanılır. Çağrıda verilen argüman varsayılanın yerine geçer.

Varsayılan değer sözleşmeye de yazılır:

fiyat: indirim uygulanacak tutar
oran: indirim oranı; verilmezse 0.10
Note

Zorunlu parametreler, başlangıç düzeyinde varsayılan değerli parametrelerden önce yazılır: def f(a, b=1): .... def f(a=1, b): ... geçerli değildir.

Varsayılan değeri, çağrıların çoğunda gerçekten o değer kullanılıyorsa verin. Çağıranın mutlaka bilmesi gereken bir kuralı varsayılan değerin arkasına saklamayın.

assert ile beklentiyi kontrol etmek

assert, doğru olmasını beklediğimiz bir koşulu kontrol eder. Bu hücre ekrana bir şey yazar mı? Sonra 5’i 6 yapıp yeniden çalıştırın.

Koşul True ise program hiçbir şey yazmadan devam eder. Koşul False ise program AssertionError hatasıyla durur.

Bir fonksiyonu seçilmiş girdilerle çalıştırıp sonucu önceden yazılmış beklenen değerle karşılaştırmaya test denir. assert ile küçük testler yazabiliriz:

Bu üç kontrol pozitif sayıyı, sıfırı ve negatif sayıyı ayrı ayrı dener.

Important

Kullanıcının yanlış girdiği değerleri assert ile yakalamaya çalışmayın. Burada assert’ü, programcı olarak “bu koşul doğru olmalı” dediğiniz yerleri denetlemek için kullanıyoruz. Program çalışırken geçersiz bir durumu bildirmek için istisna yükseltmeyi (raise) 13. haftada göreceğiz.

Fonksiyon sözleşmesi ile normal, sınır ve geçersiz durum testlerinin ilişkisi.

Fonksiyon sözleşmesi ve test

float sonuçlarını toleransla karşılaştırın

2. haftada bazı ondalık sayıların float olarak tam değil yaklaşık tutulduğunu görmüştük. İki satır ne yazar?

0.1 + 0.2 işleminin sonucu 0.30000000000000004 olur. Bu değer 0.3’e çok yakındır ama bellekte 0.3 ile aynı biçimde tutulmaz. Bu yüzden == karşılaştırması False verir ve assert 0.1 + 0.2 == 0.3 satırı başarısız olur.

Hesapta küçük bir fark kabul edilebiliyorsa sonuç ile beklenen değer arasındaki farkın, kabul ettiğimiz küçük bir sınırdan (tolerans) az olup olmadığına bakarız. abs() bir sayının mutlak değerini, yani işaretsiz büyüklüğünü döndürür:

Örneğin parayla çalışırken bir kuruştan küçük farkları önemsemiyorsak toleransı 0.01 seçebiliriz:

assert abs(sonuc - beklenen) < 0.01
Warning

Tolerans için “her zaman 0.01 kullanın” gibi bir kural yok. Ne kadar farkın kabul edilebileceğine problemin kendisi karar verir. Ayrıca sonucu ekranda iki ondalık basamakla göstermek, hesabın kendisini kesinleştirmez.

Tahmin et: hangi assert başarısız olur?

Aşağıdaki kodda hangi satır hata verir? Önce tahmin edin, sonra çalıştırın.

Şimdi ilk if satırını if sayi <= 0: yapın. Çalıştırmadan önce düşünün: davranış değişir mi?

İlk sürümde üç kontrol de geçer ve son satır yazdırılır. if sayi <= 0: yazıldığında 0 için -0 döndürülür. -0 ile 0 aynı değerdir, bu yüzden üç kontrol yine geçer. İki sürüm yalnız 0 değerinde farklı satırdan geçer, ama sonuç aynıdır.

Sınır değerlerini fonksiyonlarda kullanmak

Koşul içeren fonksiyonlarda en çok sınır değerlerini, yani kararın değiştiği değerleri denemek gerekir.

Bir ücretsiz kargo fonksiyonu düşünelim. 100 ve 1000 gibi sınırdan uzak değerleri denemek yetmez. Sınırın hemen altını, kendisini ve hemen üstünü deneyin. Hücredeki üç kontrol geçer mi?

Karar tam 500 değerinde değişir. >= yerine > yazılsaydı yalnız ortadaki kontrol başarısız olurdu.

Alıştırma: sınır testini düzeltin

Aşağıdaki fonksiyonda geçme sınırı 60 olmalıdır. Şu anki kod sınır değerinde yanlış sonuç veriyor.

Fonksiyonu üç assert de geçecek biçimde düzeltin.

> yerine >= kullanmayı düşünün.

def gecti_mi(puan):
    return puan >= 60

assert gecti_mi(59) == False
assert gecti_mi(60) == True
assert gecti_mi(61) == True

print("Kontroller tamamlandı")

Tek test yalnız bir durumu sınar

Aşağıdaki fonksiyon 1000 TL ve üstündeki fiyatlara yüzde 10 indirim uygulamalı. Kodda bir hata var ama tek test bunu göstermiyor. Bu test geçer mi?

Bu test yalnız 1500 TL’lik fiyatı sınar. Fonksiyonun 1000’in altındaki ve tam 1000’deki davranışı hakkında bir şey söylemez.

Daha iyi bir test kümesi dört farklı durumu dener. Hangi assert başarısız olur?

Bu dört kontrol sırasıyla şunları dener:

  • Sıradan bir fiyat (500)
  • Sınırın hemen altı (999)
  • Sınırın kendisi (1000)
  • Sınırın üstü (1500)

Üçüncü kontrol AssertionError verir. 1000 > 1000 yanlış olduğu için tam 1000 TL’lik fiyata indirim uygulanmaz ve fonksiyon 1000 döndürür. > yerine >= yazılınca dört test de geçer.

Uç durum (edge case)

Kararın değiştiği sayısal eşikler dışında da dikkat isteyen girdiler vardır. Fonksiyonun sıradan girdilerden farklı davranabileceği özel girdilere uç durum (edge case) denir.

Metnin uzunluğunu döndüren şu fonksiyon için boş metin bir uç durumdur. İki kontrol de geçer mi?

Bir liste fonksiyonunda boş liste, bir dosya işleminde boş dosya ya da bir arama probleminde hiç eşleşme bulunmaması da uç durum olabilir. Hesap paylaştıran bir fonksiyon için tek kişilik masa da bir uç durumdur.

Saf fonksiyon fikri

Aşağıdaki fonksiyon yalnızca argümanlarını kullanır ve bir değer döndürür:

Aynı argümanlarla çağrıldığında aynı sonucu verir ve dışarıdaki bir değeri değiştirmez. Bu tür fonksiyonlara saf fonksiyon (pure function) denir. Saf fonksiyonları test etmek kolaydır: sonucu yalnız argümanlar belirler.

Şimdi şu fonksiyona bakın. ekle(5) iki kez çağrılıyor. Son satır ne yazar?

Bu fonksiyon dışarıdaki toplam değişkenini değiştirir. Bu bir yan etki (side effect) örneğidir. ekle(5) her çağrıda aynı işi yapar ama programın durumu her seferinde değişir.

global toplam satırı olmasaydı Python, fonksiyonun içinde atanan toplam adını yerel sayardı. Sağ taraftaki toplam o anda henüz değer almamış olacağı için de UnboundLocalError hatası verirdi. Hücrede global toplam satırını silip çalıştırarak bunu görebilirsiniz. global toplam satırı sayesinde atama dışarıdaki küresel toplam değişkenini değiştirir. Bu örnekte global’i önermek için değil, yan etkiyi göstermek için kullandık.

Warning

Hesaplama yapan fonksiyonlarda global’i ilk çözüm olarak kullanmayacağız. Fonksiyon bir bilgiye ihtiyaç duyuyorsa onu parametreyle alsın, bir sonuç hesaplıyorsa return ile döndürsün. Böylece fonksiyonun neye bağlı olduğu def satırından okunur ve fonksiyonu test etmek kolaylaşır.

Her fonksiyonu saf yazmak zorunda değiliz. Bu kural hesaplama yapan fonksiyonlar içindir.

Tip

Bir fonksiyonun davranışını anlamak için programın başka yerlerindeki değişkenleri takip etmek zorunda kalıyorsanız fonksiyon dışarıdaki değişkenlere gereğinden fazla bağlı olabilir.

Alıştırma: saf hesaplama fonksiyonu

Aşağıdaki fonksiyon, kilometre cinsinden mesafeyi metreye çevirmelidir.

Fonksiyonu tamamlayın.

return km * 1000 yeterlidir.

def metreye_cevir(km):
    return km * 1000

assert metreye_cevir(1) == 1000
assert metreye_cevir(0) == 0
assert metreye_cevir(2.5) == 2500

print("Kontroller tamamlandı")

Fonksiyon tek bir işi mi yapıyor?

Aşağıdaki fonksiyon birbirinden farklı işleri bir arada yapıyor. Hücre kaç satır yazar?

Kod çalışır, ama hesaplama ile ekrana yazma iç içe geçmiştir. Kargo hesabını ayrıca sınamak isteseniz bile fonksiyon bir değer döndürmediği için assert yazamazsınız.

Bunu daha küçük parçalara ayırabiliriz. Programın geri kalanı bu fonksiyonları çağırır:

Bu tasarımda iki hesaplamayı ayrı ayrı doğrulayabiliriz.

İncele ve düzelt: yanlış formül

Aşağıdaki fonksiyonun Celsius sıcaklığını Fahrenheit’a çevirmesi gerekiyor. Düzeltmeden önce çalıştırın: hangi assert önce başarısız olur?

Formülde eksik olan kısmı bulun ve düzeltin.

Formül: celsius * 9 / 5 + 32.

def fahrenheit(celsius):
    return celsius * 9 / 5 + 32

assert fahrenheit(0) == 32
assert fahrenheit(100) == 212

Soruya dönelim: hesap paylaştırma Python’da

Bölümün başında arkadaşınızın hesap paylaştırma tarifini dört soruyla denemiştiniz. Python’daki karşılıkları şöyle:

Soru Sözde kodda Python’da Dikkat
Hangi bilgiler, hangi değerler olmaz? “Kişi sayısı 0 olamaz” Sözleşmede ön koşul: kisi 0’dan büyük Python ön koşulu kendisi denetlemez. kisi 0 olursa ZeroDivisionError verir
Başka hangi masalar? “Bir kişi, sıfır kişi, bahşişsiz masa; payı önceden yaz” Her masa için bir assert: assert abs(kisi_basi_pay(250, 1) - 275) < 0.01 Arkadaşınızın tek denemesi yalnız bir masayı sınar. Tek kişilik masa uç durumdur
Bahşiş oranı “Oran verilmezse yüzde 10” def kisi_basi_pay(hesap, kisi, bahsis=0.10): Oran fonksiyonun dışındaki bir değişkenden (tabeladan) okunursa sonuç dışarıya bağlı kalır ve dünkü testler bugün başka sonuç verebilir. Oranı parametreyle alın
100 TL, üç kişi “Herkes 33,33 TL, toplam bir kuruş eksik” abs(pay - 33.33) < 0.01 Pay 33.333... çıkar, == 33.33 başarısız olur. Formül hesap * (1 + bahsis) / kisi ise 400 TL’lik dört kişilik masada bile 110.00000000000001 çıkar ve == 110 başarısız olur

Listenizi tabloyla karşılaştırın. Payı denemeden önce yazdıysanız bir test yazmışsınız demektir: assert bu yazdığınızı Python’a denetletir. Arkadaşınızın “Herkes 110 TL ödedi, tarif doğru” cümlesi tek bir test. Sıfır kişilik masayı, tek kişilik masayı ve kuruşları denemediyseniz tarif o masalarda da sınanmamış demektir.

Üret: hesabı paylaştıran fonksiyon

Bölümün başındaki tarifi fonksiyona çevirin. kisi_basi_pay(hesap, kisi, bahsis) adlı fonksiyon şu kurallara uymalı:

  • bahsis verilmezse 0.10 kullanılmalı.
  • Hesaba bahşiş eklenmeli, sonuç kişi sayısına bölünmeli.
  • Fonksiyon yalnızca hesaplama yapıp sonucu döndürmeli; içinde print() kullanılmamalı.
  • Fonksiyonun üstüne, yorum satırı olarak sözleşmesini ve ön koşulunu yazın.

Başlangıç kodundaki def satırında bahsis parametresi henüz yok. Onu siz ekleyeceksiniz. Testler payı toleransla karşılaştırıyor.

Bahşişli hesap hesap + hesap * bahsis olur. Bunu kisi değerine bölün.

# Girdi: hesap 0 ya da daha büyük sayı, kisi 0'dan büyük tam sayı,
#        bahsis 0 ile 1 arası oran (verilmezse 0.10)
# Çıktı: bahşiş eklenmiş hesabın kişi başına düşen payı
# Ön koşul: kisi > 0
def kisi_basi_pay(hesap, kisi, bahsis=0.10):
    return (hesap + hesap * bahsis) / kisi

assert abs(kisi_basi_pay(400, 4) - 110) < 0.01
assert abs(kisi_basi_pay(400, 4, 0) - 100) < 0.01
assert abs(kisi_basi_pay(250, 1) - 275) < 0.01
assert abs(kisi_basi_pay(100, 3, 0) - 33.33) < 0.01

print("Kontroller tamamlandı")

Doğrulama için küçük bir kontrol listesi

Bir fonksiyonu bitirdiğinizde kendinize şunları sorun:

  • Fonksiyonun tek ve açık bir işi var mı?
  • Parametre adları neyi tuttuğunu söylüyor mu?
  • Varsayılan parametre varsa neden o değerin seçildiği açık mı?
  • Hangi girdilerin geçerli olduğu belli mi?
  • Dönüş değerinin anlamı açık mı?
  • Sıradan bir örnek denendi mi?
  • Sınır değeri denendi mi?
  • Uç durum varsa denendi mi?
  • float sonucu karşılaştırırken tam eşitlik gerçekten uygun mu?
  • Hesaplama fonksiyonu gereksiz print(), global ya da dış değişken kullanıyor mu?

Her soru her fonksiyon için aynı ölçüde önemli olmayabilir. Yine de bir hata çıktığında rastgele değer denemek yerine bu listeyi sırayla izleyebilirsiniz.

Sınav provası

Önce kendi cevabınızı seçin, sonra cevap anahtarında her şıkkın neden doğru ya da yanlış olduğunu okuyun. Yanlış şıklar rastgele yazılmadı; bu konuda öğrencilerin gerçekten yaptığı hatalardan seçildi. Sınav soruları da aynı yolla hazırlanır.

Madde 1. Bir fonksiyonun sözleşmesi hangi üç soruya cevap verir?

  • A. Hangi dosyada yer alır, kim yazmıştır ve ne zaman yazılmıştır
  • B. Ne kadar hızlı çalışır, ne kadar bellek kullanır ve kaç kez çağrılır
  • C. Hangi girdileri kabul eder, ne yapar ve ne döndürür
  • D. Hangi hataları verir, hangi sürümde çalışır ve hangi modülü kullanır
  • E. Kaç satır uzunluktadır, hangi döngüyü kullanır ve kaç değişken tanımlar

Madde 2. Ön koşul nedir?

  • A. Fonksiyondan önce çalıştırılması gereken başka bir fonksiyondur
  • B. Fonksiyonun kaç parametre alacağını belirleyen kuraldır
  • C. Fonksiyonun ilk satırında yazılması zorunlu bir açıklamadır
  • D. Fonksiyonun doğru çalışması için girdilerin sağlaması gereken şarttır
  • E. Fonksiyonun döndüreceği değerin türüdür

Madde 3. Aşağıdaki program neden hata verir?

def hesapla():
    sonuc = 42

hesapla()
print(sonuc)
  • A. sonuc ayrılmış bir sözcüktür, ad olarak kullanılamaz
  • B. 42 sayısı bir değişkene atanamaz
  • C. Fonksiyon çağrılmadığı için sonuc hiç oluşmamıştır
  • D. print() fonksiyon dışında kullanılamaz
  • E. sonuc yerel değişkendir, dışarıdan erişilemez

Madde 4. def indirim(fiyat, oran=0.10): tanımında indirim(200) çağrısında oran hangi değeri alır?

  • A. None
  • B. 200
  • C. 0
  • D. Hata verir; iki parametreli fonksiyon tek argümanla çağrılamaz
  • E. 0.10

Madde 5. def basarili_mi(puan, sinir=50): tanımıyla geçme sınırını 60 yapmak için hangi çağrı doğrudur?

  • A. basarili_mi(60)
  • B. sinir = 60 satırını fonksiyonun dışında yazmak
  • C. basarili_mi(55, sinir=60)
  • D. basarili_mi(sinir=60)
  • E. basarili_mi(55, 50, 60)

Madde 6. assert ortalama([60, 80]) == 70 satırı ne yapar?

  • A. Fonksiyonun sonucunu ekrana yazar
  • B. Sonuç 70 değilse AssertionError verir
  • C. Fonksiyonu iki kez çalıştırıp sonuçları karşılaştırır
  • D. Yanlış sonuç veren fonksiyonu düzeltir
  • E. Fonksiyonun sonucunu 70 olarak zorlar

Madde 7. assert 0.1 + 0.2 == 0.3 satırı neden başarısız olabilir?

  • A. Python assert içinde toplama yapılmasına izin vermez
  • B. float toplam 0.3’e yakın ama tam eşit olmayabilir
  • C. 0.3 bir float değil str olarak yorumlanır
  • D. Toplama işlemi yanlış yazılmıştır
  • E. assert ondalıklı sayılarla çalışmaz

Madde 8. Geçme sınırı 60 olan bir fonksiyon için 59, 60 ve 61 neden iyi bir test kümesidir?

  • A. Sınırın hemen altını, kendisini ve hemen üstünü kapsar
  • B. Bu üç değer bütün olası girdileri temsil eder
  • C. Üç ardışık sayı seçmek her testte iyi bir kuraldır
  • D. Python sınır değerlerini kendiliğinden özel olarak işler
  • E. Küçük sayılarla test etmek daha hızlıdır

Madde 9. Boş metin bazı fonksiyonlarda neden uç durum sayılır?

  • A. metin[0] gibi erişimler boş metinde hata verir
  • B. Boş metin bellekte hiç yer kaplamadığı için
  • C. len() boş metinde çalışmaz, uzunluk ölçülemez
  • D. Boş metin hangi işleme girerse girsin hata verir
  • E. Boş metin Python’da None ile aynıdır

Madde 10. Saf fonksiyonun iki temel özelliği nedir?

  • A. Yalnızca sayılarla çalışır ve döngü içermez
  • B. Yalnızca tek bir return satırından oluşur
  • C. Hiç parametre almaz ve hiç değer döndürmez
  • D. Yan etkisi yoktur, aynı girdiye aynı sonucu verir
  • E. Ekrana yazdırır ve küresel değişkenleri günceller

Madde 1. Doğru: C. Sözleşme, fonksiyonu kullanacak kişinin gövdeyi okumadan bilmesi gereken üç şeydir.

  • A yanlış: “Sözleşme” sözcüğü imza, tarih ve taraf gibi resmî bilgileri çağrıştırır. Ama yazar, dosya ve tarih fonksiyonun ne yaptığını söylemez; sözleşme girdiyi, davranışı ve sonucu anlatır.
  • B yanlış: İyi bir fonksiyonun hızlı da olması beklendiği için bu ölçüler sözleşmeye girecekmiş gibi görünür. Ama hız ve bellek kullanımı başka bir konudur; sözleşme fonksiyonun ne aldığını, ne yaptığını ve ne döndürdüğünü anlatır.
  • D yanlış: Hatalar da fonksiyonun davranışına ait olduğu için bu seçenek eksiksiz görünebilir. Ama sürüm ve modül bilgisi fonksiyonun işini anlatmaz; sözleşmenin üç sorusu girdi, davranış ve sonuçtur.
  • E yanlış: Fonksiyonu anlatırken ilk göze çarpan şey kodun kendisi olduğu için satır, döngü ve değişken sayısı akla gelir. Ama bunlar gövdenin iç ayrıntılarıdır. Fonksiyonu çağıran kişi gövdeyi okumadan da onu kullanabilmelidir.

Madde 2. Doğru: D. Örneğin ortalama hesaplayan bir fonksiyonun ön koşulu listenin boş olmamasıdır.

  • A yanlış: “Ön” sözcüğü bir şeyin önce çalıştırılması gerektiğini çağrıştırır. Ama ön koşul başka bir fonksiyon değildir; girdinin taşıması gereken özelliktir, örneğin “kişi sayısı 0’dan büyük”.
  • B yanlış: Fonksiyonu yanlış sayıda argümanla çağırmak da hata verdiği için bu bir ön koşul gibi görünür. Ama parametre sayısı def satırında belirlenir; ön koşul, verilen değerlerin hangi şartı sağlaması gerektiğini söyler.
  • C yanlış: Ön koşul çoğu zaman fonksiyonun hemen üstüne yorum olarak yazıldığı için zorunlu bir açıklama satırı sanılabilir. Ama Python böyle bir satır istemez; ön koşul girdiye konan bir şarttır, nereye yazıldığı değil.
  • E yanlış: Sözleşmede dönüş değeri de yazıldığı için ön koşul onunla karıştırılabilir. Ama dönüş değeri sonuçla ilgilidir; ön koşul fonksiyon çağrılmadan önce girdinin sağlaması gereken şarttır.

Madde 3. Doğru: E. sonuc fonksiyonun içinde tanımlandığı için yereldir. Yerel adlar yalnızca kendi fonksiyonlarının içinde yaşar ve fonksiyon bitince yok olur. Değeri dışarı taşımanın yolu returndur.

  • A yanlış: Hata bir adla ilgili olduğu için adın kendisinden şüphelenilebilir. Ama sonuc ayrılmış sözcük değildir; hata onun fonksiyon dışında kullanılmasından çıkar.
  • B yanlış: Atama fonksiyonun içinde yapıldığı için orada bir sorun olduğu sanılabilir. Ama atama sorunsuzdur; hata print(sonuc) satırında çıkar.
  • C yanlış: Adın bulunamaması, onu oluşturan kodun hiç çalışmadığı izlenimini verir. Ama fonksiyon hesapla() satırında çağrılıyor; sorun adın kapsamı.
  • D yanlış: Hata print() satırında çıktığı için sorun o fonksiyonda aranabilir. Ama print() her yerde kullanılabilir; bulunamayan ad sonuctur.

Madde 4. Doğru: E. Argüman verilmediğinde tanımdaki varsayılan kullanılır.

  • A yanlış: return olmayan fonksiyonun None döndürdüğünü bilen öğrenci, verilmeyen argümanın da None olacağını düşünebilir. Ama parametrenin varsayılanı tanımlıysa None atanmaz; 0.10 kullanılır.
  • B yanlış: Çağrıda tek bir sayı göründüğü için iki parametrenin de o sayıyı alacağı sanılabilir. Ama argümanlar sırayla eşleşir: 200 ilk parametreye, yani fiyata gider.
  • C yanlış: Verilmeyen bir değerin “hiç” yani 0 sayılacağı düşünülebilir. Ama tanımda varsayılan açıkça 0.10 yazılmış; argüman verilmediğinde o kullanılır.
  • D yanlış: Önceki örneklerde her parametreye bir argüman verildiği için eksik argüman hata gibi görünür. Ama varsayılanı olan parametre isteğe bağlıdır; bu çağrı geçerlidir.

Madde 5. Doğru: C. Anahtar kelimeli argüman varsayılanın yerine geçer ve hangi parametreyi değiştirdiğinizi açıkça gösterir.

  • A yanlış: İstenen sayı 60 olduğu için onu tek argüman olarak vermek akla gelir. Ama argümanlar sırayla eşleşir; 60 puan olur, sınır yine 50 kalır.
  • B yanlış: Parametreyle aynı adı taşıyan bir değişken, fonksiyonun içindeki sinir’i değiştirecekmiş gibi görünür. Ama dışarıdaki bu ad ayrı bir değişkendir; varsayılan değeri değiştirmez.
  • D yanlış: Yalnız değiştirmek istenen parametreyi yazmak yeterli gibi görünür. Ama puan zorunlu bir parametredir; verilmezse TypeError çıkar.
  • E yanlış: Eski sınırı da yeni sınırı da yazmak açıklayıcı gibi görünür. Ama fonksiyonun iki parametresi var; üçüncü argüman TypeError verir.

Madde 6. Doğru: B. assert koşulu sınar. Koşul doğruysa hiçbir şey yapmadan geçer; yanlışsa AssertionError vererek programı durdurur. Beklenen davranışı hızlıca sınamak için kullanılır.

  • A yanlış: Bir satırın sonucunu görmeye alışan öğrenci çıktı bekler. Ama assert doğru geçtiğinde hiçbir şey yazmaz.
  • C yanlış: Satırda bir karşılaştırma bulunduğu için iki sonucun kıyaslandığı düşünülebilir. Ama fonksiyon bir kez çağrılır ve sonucu beklenen değerle karşılaştırılır.
  • D yanlış: assert hatayı bulmaya yardım ettiği için onu düzelttiği de sanılabilir. Ama düzeltme yapmaz; yalnızca beklentiyi sınar.
  • E yanlış: Satırda 70 sayısı ve eşittir işareti göründüğü için atama sanılabilir. Ama == atama yapmaz; yalnızca eşitliği sınar.

Madde 7. Doğru: B. float değerler yaklaşık tutulur; 0.1 + 0.2 sonucu 0.30000000000000004 olur. Bu yüzden float sonuçlarda tam eşitlik yerine probleme uygun bir tolerans kullanılır.

  • A yanlış: assert satırlarında çoğu zaman yalnızca bir fonksiyon çağrısı görüldüğü için akla gelir. Ama izin verir; assert her bool ifadeyi sınayabilir.
  • C yanlış: Tuhaf eşitsizliklerin nedeni çoğu zaman tür karışıklığı olduğu için akla gelir. Ama tırnak yok; 0.3 bir floattur.
  • D yanlış: Beklenmedik bir sonuçta ilk şüphe kodun yazımına gider. Ama yazımda sorun yok; fark değerlerin saklanma biçiminden geliyor.
  • E yanlış: Satır başarısız olunca sorun asserte yüklenebilir. Ama assert ondalıklı sayılarla da çalışır; sorun karşılaştırmanın kendisinde.

Madde 8. Doğru: A. > ile >= karışıklığı yalnızca sınırın çevresinde fark edilir. 59, 60 ve 61 bu hatayı yakalar; sınırdan uzak değerler göstermez.

  • B yanlış: Sınırın iki yanı da kapsandığı için küme eksiksiz görünebilir. Ama temsil etmezler; negatif puan gibi geçersiz girdiler ayrıca sınanmalıdır.
  • C yanlış: Seçilen üç değer ardışık olduğu için kuralın ardışıklık olduğu sanılabilir. Ama önemli olan değerlerin sınırın çevresinde olması; 10, 11, 12 bu fonksiyonda sınırı hiç sınamaz.
  • D yanlış: Sınır değerlerinin özel bir adla anılması, Python’ın onları ayrıca işlediği izlenimini verebilir. Ama özel bir işlem yoktur; sınır davranışını programcı belirler.
  • E yanlış: Testlerin çabuk bitmesi istendiği için akla gelebilir. Ama hız bir ölçüt değildir; bu değerler sınıra yakın oldukları için seçildi.

Madde 9. Doğru: A. Boş metnin uzunluğu sıfırdır. metin[0] hata verir, ortalama gibi hesaplar da sıfıra bölmeye varabilir. Bu yüzden boş değer ayrıca düşünülmelidir.

  • B yanlış: Boş metin “hiçbir şey” gibi düşünülünce yer kaplamayacağı sanılır. Ama bellek kullanımı uç durum olmasının nedeni değildir; sorun işlemlerin davranışında.
  • C yanlış: Ölçülecek bir karakter olmadığı için len()in çalışmayacağı düşünülebilir. Ama len("") sonucu 0’dır ve sorunsuz çalışır.
  • D yanlış: Boş metinle ilgili ilk görülen örnek çoğu zaman hata veren bir indeksleme olduğu için genelleme yapılabilir. Ama birçok işlemde sorunsuz çalışır; "" + "a" sonucu "a" olur. Sorun yalnızca belirli işlemlerde çıkar.
  • E yanlış: İkisi de “değer yok” gibi düşünüldüğü için karıştırılır. Ama aynı değildir; "" bir str, None ayrı bir değerdir.

Madde 10. Doğru: D. Saf fonksiyon yalnızca argümanlarını kullanır ve dışarıdaki hiçbir değişkeni değiştirmez; yani yan etkisi yoktur. Aynı girdiyle her zaman aynı sonucu döndürür. Bu iki özellik sayesinde tek başına sınanabilir ve ne döndüreceği önceden bilinir.

  • A yanlış: Saf fonksiyon örnekleri çoğu zaman kısa sayı hesapları olduğu için akla gelir. Ama tür ve iç yapı saflıkla ilgili değildir; metin işleyen, döngü içeren bir fonksiyon da saf olabilir.
  • B yanlış: Kısa fonksiyonlar yan etkisiz göründüğü için akla gelir. Ama uzunlukla ilgisi yoktur; tek satırlık bir fonksiyon da print() yaparak saflığını yitirebilir.
  • C yanlış: Dışarıyla hiç ilişkisi olmayan bir fonksiyon “saf” gibi görünebilir. Ama tam tersi; saf fonksiyon girdisini parametreden alır ve sonucunu döndürür.
  • E yanlış: İki somut davranış saydığı için iki özellik isteyen soruya uyuyor gibi görünür. Ama ikisi de fonksiyonun dışını etkiler; saflığı bozan da budur.

Tek sayfa özet

Bu hafta bir fonksiyonun ne alıp ne döndüreceğini yazmayı ve bunu assert ile sınamayı gördük.

  • Fonksiyon sözleşmesi fonksiyonun ne aldığını, ne yaptığını ve ne döndürdüğünü yazar.
  • Ön koşul, fonksiyonun hangi girdilerle doğru çalıştığını söyler.
  • Yerel değişken yalnızca kendi fonksiyonunun içinde kullanılabilir. Hesaplama fonksiyonu dışarıdan bir değere ihtiyaç duyuyorsa onu parametreyle almak kodu daha anlaşılır yapar.
  • Varsayılan parametre, argüman verilmediğinde hangi değerin kullanılacağını açıkça belirler.
  • global, fonksiyonun dışarıdaki küresel bir değişkeni değiştirmesine izin verir; hesaplama fonksiyonlarında ilk çözüm olarak kullanılmaz.
  • assert, beklediğiniz bir sonucu hızlıca sınar; koşul yanlışsa AssertionError verir.
  • float sonuçlarını karşılaştırırken == yerine probleme uygun bir tolerans gerekebilir: abs(sonuc - beklenen) < 0.01.
  • Koşullu fonksiyonlarda sınırın hemen altı, kendisi ve hemen üstü ayrı ayrı denenir.
  • Boş metin, boş liste ya da hiç eşleşme bulunmaması gibi durumlar uç durum olabilir.
  • Tek bir testin geçmesi yalnız o örneği sınar. Sıradan bir örnek, sınır değeri ve uç durum ayrı ayrı denenir.
  • Saf fonksiyon argümanlarını kullanır, sonuç döndürür ve dışarıdaki hiçbir değeri değiştirmez.
  • Tek iş yapan küçük fonksiyonları ayrı ayrı sınamak daha kolaydır.

Bir sonraki bölümde fonksiyonlarla birlikte sık kullanacağımız metin (str) değerlerini daha ayrıntılı inceleyeceğiz.

Bu bölümün kazanımları

Bu bölümü bitiren öğrenci:

  • Fonksiyon sözleşmesini ve ön koşulları açıklar.
  • Yerel değişkenin kapsamını açıklar ve dış bağımlılığı parametreye çevirmeyi gerekçelendirir.
  • Varsayılan parametreli bir çağrıda parametrenin hangi değeri aldığını belirler.
  • assert ile beklenen davranışı doğrular.
  • float sonuçlarda tam eşitlik yerine toleranslı karşılaştırma seçer.
  • Sınır ve uç durumları seçerek test eder.
  • Saf fonksiyonu yan etkili fonksiyondan ayırt eder ve global kullanımına neden temkinli yaklaşıldığını açıklar.
Back to top