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 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; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.

Bu fonksiyonun doğru çalıştığını nereden biliyorum?

Fonksiyonu bir kez çalıştırıp doğru sonuç görmeniz, başka girdilerde de doğru çalışacağını göstermez. Örneğin 60 ve üstünü geçer sayması gereken bir fonksiyon 75 için doğru, 60 için yanlış cevap verebilir. Bu bölümde fonksiyonun hangi girdide ne döndürmesi gerektiğini önceden yazacağız, sonra bunu özenle seçilmiş birkaç örnekle sınayacağız.

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?

Örneğin:

def saat_ucreti(toplam_ucret, saat):
    return toplam_ucret / saat

Bu üç sorunun yazılı cevabına fonksiyonun sözleşmesi denir. saat_ucreti için sözleşme şöyle yazılabilir:

Girdi:
- toplam_ucret: 0 veya daha büyük sayı
- saat: 0'dan büyük sayı

Davranış:
- toplam_ucret değerini saat değerine böler

Çıktı:
- saat başına ücret

Buradaki saat > 0 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.

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.

Örneğin yüzde hesabı:

def yuzde_hesapla(tutar, oran):
    return tutar * oran

oran = 3 verildiğinde de fonksiyon 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.

def cevre_hesapla(kenar):
    sonuc = kenar * 4
    return sonuc

print(cevre_hesapla(5))
# print(sonuc)  # NameError

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) kapsamda bulunabilir:

oran = 0.20

def kdv_hesapla(tutar):
    return tutar * oran

Bu kod çalışır; fonksiyon dışarıdaki oran değerini okuyabilir. Fakat fonksiyonun sonucunu anlamak için artık yalnızca parametrelerine değil, dışarıdaki bir değere de bakmamız gerekir.

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

def kdv_hesapla(tutar, oran):
    return tutar * oran

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:

def indirimli_fiyat(fiyat, oran=0.10):
    return fiyat - fiyat * oran

İkinci argüman verilmezse varsayılan %10 kullanılır:

print(indirimli_fiyat(1000))       # 900.0

Gerektiğinde çağrıda verilen argüman varsayılanın yerine geçer:

print(indirimli_fiyat(1000, 0.25)) # 750.0

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.

sonuc = 2 + 3
assert sonuc == 5

Koşul True ise program sessizce devam eder. Koşul False ise program AssertionError hatasıyla durur.

Fonksiyon davranışını küçük örneklerle doğrulayabiliriz:

def kare(sayi):
    return sayi * sayi

assert kare(3) == 9
assert kare(0) == 0
assert kare(-2) == 4

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ında tam eşitlik her zaman uygun değildir

2. haftada bazı ondalık sayıların float olarak tam değil yaklaşık tutulduğunu görmüştük. Bu yüzden şu kontrol başarısız olabilir:

# assert 0.1 + 0.2 == 0.3

0.1 + 0.2 işleminin sonucu 0.30000000000000004 değerine çok yakındır ve bellekte 0.3 ile aynı biçimde tutulmayabilir.

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:

sonuc = 0.1 + 0.2
beklenen = 0.3

assert abs(sonuc - beklenen) < 0.000001

Ö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 kodu çalıştırmadan önce hangi satırın hata vereceğini tahmin edin:

Bu sürümde üç kontrol de geçer. Şimdi ilk if satırını if sayi <= 0: yaparsanız davranış değişir mi? Tahmin edin ve deneyin.

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:

def kargo_ucreti(sepet):
    if sepet >= 500:
        return 0
    return 39.90

Burada 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:

assert kargo_ucreti(499) == 39.90
assert kargo_ucreti(500) == 0
assert kargo_ucreti(501) == 0

Karar tam 500 değerinde değişir.

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

Şu fonksiyona bakalım:

def indirim(fiyat):
    if fiyat >= 1000:
        return fiyat * 0.90
    return fiyat

Sadece şu testi yazarsak:

assert indirim(1500) == 1350

fonksiyonun 1000’in altındaki fiyatlarda doğru çalışıp çalışmadığını bilemeyiz.

Daha iyi bir test kümesi:

assert indirim(500) == 500
assert indirim(999) == 999
assert indirim(1000) == 900
assert indirim(1500) == 1350

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)

Uç durum (edge case)

Dikkat edilmesi gereken her girdi sayısal bir eşik değildir. Fonksiyonun sıradan girdilerden farklı davranabileceği özel girdilere uç durum (edge case) denir.

Metnin uzunluğunu döndüren şu fonksiyona bakalım:

def karakter_sayisi(metin):
    return len(metin)

Bu fonksiyon için boş metin bir uç durumdur:

assert karakter_sayisi("") == 0
assert karakter_sayisi("Python") == 6

Bir liste fonksiyonunda boş liste, bir dosya işleminde boş dosya veya bir arama probleminde hiç eşleşme bulunmaması benzer uç durumlar olabilir.

Saf fonksiyon fikri

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

def cevre(uzunluk, genislik):
    return 2 * (uzunluk + genislik)

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:

assert cevre(4, 6) == 20
assert cevre(1, 1) == 4

Şimdi şu fonksiyona bakın:

toplam = 0

def ekle(deger):
    global toplam
    toplam = toplam + deger

Bu fonksiyon dışarıdaki toplam değişkenini değiştirir. Bu bir yan etki (side effect) örneğidir.

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

def siparis_isle(fiyat, adet):
    toplam = fiyat * adet
    if toplam >= 500:
        kargo = 0
    else:
        kargo = 39.90
    print("Ürün toplamı:", toplam)
    print("Kargo:", kargo)
    print("Genel toplam:", toplam + kargo)

Kod çalışır, ama hesaplama ile ekrana yazma iç içe geçmiştir.

Bunu daha küçük parçalara ayırabiliriz:

def urun_toplami(fiyat, adet):
    return fiyat * adet

def kargo_ucreti(tutar):
    if tutar >= 500:
        return 0
    return 39.90

Programın geri kalanı bu fonksiyonları çağırır:

toplam = urun_toplami(120, 3)
kargo = kargo_ucreti(toplam)
print("Genel toplam:", toplam + kargo)

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:

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

Üret: aidat gecikme hesabı

Bir apartmanda aylık aidat geç ödendiğinde geciken her gün için 2.5 TL gecikme bedeli eklendiğini düşünelim.

gecikmeli_tutar(aidat, geciken_gun) adlı bir fonksiyon yazın.

Kurallar:

  • geciken_gun 0 ise yalnızca aidat döndürülmeli.
  • Her gecikme günü için 2.5 TL eklenmeli.
  • Fonksiyon yalnızca hesaplama yapıp sonuç döndürmeli; içinde print() kullanılmamalı.

Başlangıç kodu:

Toplam gecikme bedeli geciken_gun * 2.5 olur.

def gecikmeli_tutar(aidat, geciken_gun):
    return aidat + geciken_gun * 2.5

assert gecikmeli_tutar(500, 0) == 500
assert gecikmeli_tutar(500, 1) == 502.5
assert gecikmeli_tutar(500, 4) == 510

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ı?
  • Parametrelerin neyi temsil ettiği anlaşılır mı?
  • 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ı?
  • Normal 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 veya 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ış: Bunlar belgeleme bilgileridir, davranış sözleşmesi değil.
  • B yanlış: Hız ve bellek kullanımı başka bir konudur; sözleşme fonksiyonun ne yaptığını anlatır.
  • D yanlış: Bunlar yararlı ayrıntılar olabilir ama sözleşmenin özü girdi, davranış ve sonuçtur.
  • E yanlış: Bunlar gövdenin iç ayrıntılarıdır; çağıranı ilgilendirmez.

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

  • A yanlış: Çağrı sırası ayrı bir konudur; ön koşul girdinin taşıması gereken özelliktir.
  • B yanlış: Parametre sayısı imzada belirlenir; ön koşul değerlerin niteliğiyle ilgilidir.
  • C yanlış: Ön koşulu yazmak yararlıdır ama ilk satırda zorunlu bir açıklama yoktur; ön koşul girdiye konan bir şarttır.
  • E yanlış: Bu sonuçla ilgilidir; ön koşul girdiye ilişkindir.

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ış: Varsayılan tanımlıysa None atanmaz.
  • B yanlış: 200 ilk parametreye, yani fiyata gider.
  • C yanlış: Varsayılan açıkça 0.10 yazılmış; sıfırlanmaz.
  • D yanlış: Varsayılanı olan parametre isteğe bağlıdır; ç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ış: Bu, 60’ı puan yapar; sınır yine 50 kalır.
  • B yanlış: Dışarıdaki bir ad, varsayılan değeri değiştirmez.
  • D yanlış: puan zorunlu bir parametredir; verilmezse TypeError çıkar.
  • E yanlış: Fonksiyon en fazla iki argüman alır; üçüncüsü hata 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ış durumu 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.
  • 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.
  • 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ı bilir ve dış bağımlılığı parametreye çevirmeyi gerekçelendirir.
  • Varsayılan parametrenin davranışını uygular.
  • 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 fonksiyonun özelliklerini tanır ve global kullanımına neden temkinli yaklaşıldığını açıklar.
Back to top