Fonksiyonlar, kapsam ve modüller

C’de programı fonksiyonlara bölmeyi öğrendiniz. Nesne tabanlı programlamada da bu beceriye ihtiyacınız olacak, çünkü ileride yazacağımız sınıfların içindeki metotlar da birer fonksiyondur. Bu hafta Python’da fonksiyon yazacak ve kodu dosyalara böleceğiz. Örneklerin çoğu bir not programından: ortalamayı hesaplayan, geçme sınırına bakan ve listeye not ekleyen fonksiyonlar yazacak, sonra bunları ayrı bir dosyaya taşıyacağız. Söz dizimi C’ye yakın. Bölümde en çok C’den farklı davranan yerlerde duracağız: fonksiyonun dışındaki bir ada atama, fonksiyona verilen listenin değişmesi ve parametrenin varsayılan değeri.

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ı

NotSınavda sorulur
  • print() ile return farkı ve örtük None
  • Varsayılan parametre, konumsal argüman ve anahtar kelime argümanı
  • Yerel ad ile dış kapsamdaki ad, UnboundLocalError ve global
  • Değiştirilebilir parametrede yan etki, int parametrenin yeniden bağlanması
  • Değiştirilebilir (mutable) varsayılan değer tuzağı ve None nöbetçisi
  • Docstring ile davranış sözleşmesi
  • import ile modül kullanmak ve __name__ == "__main__" kalıbının ne işe yaradığı
  • Birden çok değer döndürme (return low, high)
  • Değişken sayıda argüman (*args, **kwargs)
  • Lambda ifadeleri ve closure
  • Paket yayımlama (pip install, PyPI)

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

Fonksiyon tanımlamak: def

Ortalamayı programın birkaç yerinde hesaplayacaksanız bu kodu bir fonksiyona koyarsınız. Python’da fonksiyon def ile tanımlanır. C’deki gibi dönüş türü ve parametre türleri yazılmaz. def satırı : ile biter, gövdeyi girinti belirler. Hücre ne yazar: 80 mi, 80.0 mı?

first ve second parametrelerdir, çağrıdaki 70 ve 90 argümanlardır. 1. haftada gördüğünüz gibi / her zaman float döndürür.

C’de bir fonksiyonu çağırmadan önce onu tanımlamanız ya da prototipini yazmanız gerekirdi. Python’da prototip yoktur, ama def satırı fonksiyonu çağıran satırdan önce çalışmış olmalıdır. Python dosyayı yukarıdan aşağı çalıştırır. Bu yüzden fonksiyon tanımları çoğu zaman dosyanın başına, onları çağıran satırların üstüne yazılır.

Fonksiyonun adı ne yaptığını söylemeli. calculate_average (ortalama hesapla) adını okuyan, gövdeye bakmadan işi anlar. process ya da do_it gibi adlar bunu söylemez.

Varsayılan parametre ve anahtar kelime argümanı

C’de bir parametreye varsayılan değer veremezdiniz: geçme sınırını her çağrıda yazar ya da iki ayrı fonksiyon tanımlardınız. Python’da parametreye tanımda bir değer verebilirsiniz. Üç satır ne yazar?

limit=50 bir varsayılan değerdir: çağrıda limit verilmezse 50 kullanılır. İlk iki çağrıdaki argümanlar konumsal argümandır, yani sırasına göre parametreye gider: ilk değer score’a, ikincisi limit’e. Üçüncü çağrıdaki score=70 ve limit=60 ise anahtar kelime argümanıdır: değer parametrenin adıyla birlikte verilir. Birden çok isteğe bağlı parametre olduğunda hangi değerin hangi parametreye gittiği böylece çağrı satırından okunur.

İki biçimi aynı çağrıda birlikte kullanabilirsiniz: is_successful(70, limit=60). Ama iki sıra kuralı var. Çağrıda konumsal argümanlar önce, anahtar kelime argümanları sonra gelir. Tanımda da varsayılan değeri olan parametreler, varsayılanı olmayanlardan sonra yazılır. Aşağıdaki iki satırın ikisi de çalışmadan SyntaxError verir:

is_successful(score=70, 60)          # konumsal argüman, anahtar kelime argümanından sonra

def is_successful(limit=50, score):  # varsayılanı olmayan parametre, varsayılanlıdan sonra
    return score >= limit

Yerel kapsam, global ad ve UnboundLocalError

Kaç öğrencinin ortalamasının hesaplandığını saymak için C’de çoğu zaman global bir değişken tutar, fonksiyonun içinde count++ yazardınız. Python’da da fonksiyon dışarıdaki bir adı okuyabilir. Hücre ne yazar? Son satırın başındaki # işaretini silip yeniden çalıştırırsanız ne olur?

tax fonksiyonun içinde oluşturulduğu için yerel bir addır. C’deki yerel değişkenler gibi yalnızca o fonksiyonun içinde kullanılabilir, dışarıdan okumaya çalışırsanız NameError alırsınız. rate ise fonksiyonun dışında tanımlıdır ve fonksiyon onu dış kapsamdan (scope) okur. Kapsam, bir adın programın hangi bölümünde geçerli olduğudur.

Okumak sorun çıkarmıyor. Şimdi sayacı fonksiyonun içinde artıralım. increase() çağrılınca ilk satırdaki print(count) ne yazar?

Python bir adın yerel olup olmadığına satır satır değil, fonksiyonun tamamına bakarak karar verir. Gövdede count’a atama varsa count fonksiyonun her satırında yerel sayılır, atamadan önceki satırlarda da. print satırı çalıştığında bu yerel count’a henüz değer verilmemiştir. Python bu duruma UnboundLocalError der.

Dıştaki count’u değiştirmek istiyorsanız bunu global ile açıkça söylemeniz gerekir:

count = 10

def increase():
    global count
    count += 1

Ama her sorunu global ile çözerseniz hangi fonksiyonun hangi değişkeni değiştirdiğini izlemek zorlaşır. Bu derste mümkün olduğunca girdiyi parametreyle alıp sonucu return ile vereceğiz: count = increase(count) gibi bir çağrıda fonksiyon yeni değeri döndürür, atamayı çağıran yapar.

Değiştirilebilir parametre ve yan etki

C’de not dizisine eleman ekleyen bir fonksiyona diziyi ve eleman sayısının adresini verirdiniz. Dizi fonksiyona adresiyle gittiği için fonksiyon asıl diziyi değiştirirdi. Python’da listeyi fonksiyona verince ne olur? add_score hiçbir şey döndürmüyor. numbers değişir mi?

Çağrı sırasında scores parametresi, çağıranın verdiği listenin kendisine bağlanır. Python listeyi kendiliğinden kopyalamaz. append() bu ortak listeyi değiştirdiği için numbers da yeni içeriği gösterir. 3. haftadaki second = first deneyiyle aynı durum: iki ad, bir liste. Fonksiyonun değer döndürmek dışında çağıranın da gördüğü bir şeyi değiştirmesine yan etki (side effect) denir.

Bir fonksiyona verilen değiştirilebilir nesnenin fonksiyon içinde değiştirilmesinin çağıran tarafta yan etki oluşturmasını gösteren diyagram.

Değiştirilebilir parametre ve yan etki

C’de int parametre kopyalanırdı ve fonksiyondaki değişiklik dışarıya yansımazdı. Python’da fonksiyona bir sayı verelim. add_one gövdesinde number += 1 yazıyor. Son satır ne yazar?

Son satır 5 yazar. Sonuç C’deki gibi, ama nedeni farklı. int değiştirilemez bir nesnedir. number += 1 o nesneyi değiştirmez, yerel number adını 6 değerindeki yeni bir nesneye bağlar. total ise 5’e bağlı kalır. Bir adı başka bir nesneye bağlamaya 6. haftada ayrıntısıyla döneceğiz.

ÖnemliParametreyi C’deki işaretçi gibi düşünmeyin

“Python her şeyi adresle geçirir” diye düşünürseniz add_one örneğini açıklayamazsınız: adres gitseydi total 6 olurdu. Bu derste şöyle düşüneceğiz: her ad bir nesneye bağlanır, fonksiyon çağrılınca parametre adı da verilen nesneye bağlanır. Değiştirilebilir bir nesne yerinde değiştirilirse, o nesneye bağlı bütün adlar değişikliği görür. Parametre adını başka bir nesneye bağlamak ise çağıranı etkilemez.

Yan etki bazen istenir, bazen beklenmedik bir hataya yol açar. Çağıranın listesi değişmesin istiyorsak yeni bir liste döndürürüz. with_score iki listeyi + ile birleştiriyor. İki satır ne yazar?

İki liste + ile birleştirilince yeni bir liste oluşur, numbers olduğu gibi kalır. Peki gövdede scores += [value] yazsaydık? int için += adı yeni bir nesneye bağlıyordu. Listede de öyle mi?

[70, 80, 90] yazar. Listede += yeni liste oluşturmaz, aynı listeyi yerinde uzatır, tıpkı extend() gibi. numbers da aynı listeye bağlı olduğu için değişikliği görür. += değiştirilemez bir nesnede (int, str) yeni nesne oluşturur, listede ise var olan listeyi değiştirir. Çağıranın listesini korumak istiyorsanız scores + [value] yazıp sonucu döndürün.

Fonksiyonu çağıran kişi, listesinin değişip değişmeyeceğini nereden bilir? Bunu fonksiyonun sözleşmesi söyler: fonksiyonun ne aldığını, ne döndürdüğünü ve neyi değiştirdiğini anlatan tanım. Sözleşmeyi koda yazmanın yolunu birazdan göreceğiz.

Değiştirilebilir varsayılan değer tuzağı

Varsayılan değer bir liste olursa ne olur? Aşağıdaki fonksiyonda liste verilmezse items için varsayılan [] kullanılır. İki çağrı ne yazar?

Varsayılan [] her çağrıda yeniden oluşturulmaz. Python onu def satırı çalışırken bir kez oluşturur. Liste verilmeyen bütün çağrılar bu tek listeyi kullanır. C’deki yerel dizi her çağrıda yeniden kurulurdu. Burada öyle olmaz.

İlk çağrı varsayılan listeye "A" ekler ve ['A'] yazar. İkinci çağrı yeni bir liste almaz, aynı listeye "B" ekler ve ['A', 'B'] yazar. Bir önceki başlıktaki durumun aynısı: iki çağrı, bir liste.

Sorunu önlemek için şu kalıp kullanılır. Bu kez iki çağrı ne yazar?

Burada None, “çağıran liste vermedi” anlamına gelen bir işarettir. Bu işi gören değere nöbetçi (sentinel) denir. Liste artık gövdede oluşturulduğu için liste verilmeyen her çağrı yeni bir boş listeyle başlar. Çağıran kendi listesini verirse fonksiyon o listeye ekler.

7. haftada sınıflarla çalışırken benzer bir sorunla karşılaşacağız. Onun nedeni de aynı: değiştirilebilir bir nesneyi farkında olmadan birden çok yerde ortak kullanmak.

Docstring: davranış sözleşmesini koda yazmak

C’de fonksiyonun ne yaptığını prototipin üstüne yazdığınız bir yorumla anlatırdınız. Python’da bu açıklamanın belli bir yeri var. Docstring, fonksiyon tanımının hemen altına üç tırnak içinde yazılan açıklama metnidir. Kısa bir docstring, fonksiyonun ne beklediğini, ne döndürdüğünü, varsa önemli yan etkilerini ve hangi durumda hata verdiğini yazar. add_score listeyi değiştiriyordu. Bunu docstring’e yazalım ve help() ile okuyalım:

help() docstring’i fonksiyonun adı ve parametreleriyle birlikte ekranda gösterir. Fonksiyonu kullanacak kişi gövdeyi okumadan listesinin değişeceğini öğrenir.

Hata koşulu da sözleşmenin parçasıdır:

def calculate_discount(price, rate=0.10):
    """Fiyatın indirimli tutarını döndürür.

    price negatifse ValueError yükseltir. Girdileri değiştirmez.
    """
    if price < 0:
        raise ValueError("Fiyat negatif olamaz")
    return price * (1 - rate)

Buradaki raise satırı, fiyat negatifse hatayı çağırana bildirir. raise, try ve except arasındaki ilişkiyi 5. haftada ayrıntısıyla göreceğiz.

Her fonksiyon tek bir iş yapsın

Bir fonksiyon hem kullanıcıdan fiyatı alır, hem doğrular, hem hesaplar, hem de sonucu yazdırırsa onu denemek ve değiştirmek zorlaşır. Fiyatı her denemede klavyeden girmeniz gerekir, sonucu da ancak ekrandan okursunuz.

calculate_discount yalnızca indirimi hesaplar ve fiyatın negatif olmadığını denetler. Fiyatı kullanıcıdan alan input() satırı da sonucu yazdıran print() satırı da başka bir yerde durur. Böylece calculate_discount(100) çağrısının sonucunu bir satırda deneyebilirsiniz.

Modüller: kodu dosyalara ayırmak

C’de bir fonksiyonu başka bir dosyada kullanmak için prototipini .h dosyasına yazar, onu #include ile ekler ve iki .c dosyasını birlikte derlerdiniz. Python’da başlık dosyası yoktur. Her .py dosyası bir modüldür: başka bir dosya onun içindeki fonksiyonları import ile içe aktarıp çağırabilir.

Python’la birlikte gelen modüller de aynı yolla kullanılır. math modülü matematik fonksiyonlarını tutar. Hücre ne yazar: 4 mü, 4.0 mı?

import math modülün kendisini alır, fonksiyona math.sqrt diye modülün adıyla ulaşırsınız. from math import sqrt ise yalnız sqrt adını alır, fonksiyonu doğrudan sqrt(16) diye çağırırsınız.

Kendi yazdığınız bir dosya da aynı biçimde modül olur. Örneğin:

# pricing.py
def final_price(price, tax_rate):
    return price * (1 + tax_rate)

Aynı klasördeki app.py dosyasında:

from pricing import final_price

print(final_price(100, 0.20))

from pricing import final_price satırı aynı klasördeki pricing.py dosyasını bulur, çalıştırır ve içindeki final_price fonksiyonunu app.py içinde kullanılabilir hâle getirir. import pricing yazarsanız fonksiyonu pricing.final_price(100, 0.20) diye çağırırsınız.

Kod hücresinde ikinci bir .py dosyası açıp düzenleyemediğiniz için bu örneği kendi bilgisayarınızda deneyin. 1. haftada kurduğunuz VS Code’da bir klasör açın, iki dosyayı bu klasöre kaydedin ve app.py dosyasını çalıştırın. Windows’ta terminale py app.py yazabilirsiniz. Programı çalıştırdıktan sonra klasörde __pycache__ adlı bir klasör belirir. Python, içe aktardığı modülün bytecode’unu (1. haftada gördüğümüz ara kodu) orada saklar. Bu klasörü silebilirsiniz, Python gerektiğinde yeniden oluşturur.

__name__ == "__main__" ne işe yarar?

C’de fonksiyonları ayrı bir .c dosyasına taşıdığınızda o dosyaya main() yazmazdınız, çünkü bir programda tek bir main() bulunur. Python’da bir modül ilk kez içe aktarıldığında modülün kodu baştan sona bir kez çalışır: def satırları fonksiyonları tanımlar, girintisiz bir print() satırı da ekrana yazar. Aynı programda modülü ikinci kez içe aktarmak onu yeniden çalıştırmaz. pricing.py dosyasına hiçbir fonksiyonun içinde olmayan, girintisiz bir print(final_price(100, 0.20)) deneme satırı yazarsanız, bu satır modülü içe aktaran her programda da çalışır.

Python her modülde __name__ adında bir değişken tutar. Bir dosya doğrudan çalıştırıldığında __name__ değeri "__main__" olur. Başka bir dosyadan içe aktarıldığında ise modülün adını alır (örneğin "pricing"). Deneme satırını bu değişkene bakarak yalnız dosya doğrudan çalıştırıldığında çalıştırabilirsiniz:

# pricing.py
def final_price(price, tax_rate):
    return price * (1 + tax_rate)

def main():
    print(final_price(100, 0.20))

if __name__ == "__main__":
    main()

Bu kalıba kısaca main guard denir. py pricing.py yazınca __name__ değeri "__main__" olur ve deneme satırı çalışır. app.py ise final_price fonksiyonunu alır, ama main() çalışmaz. 13. haftada birkaç dosyadan oluşan bir OOP programında bu kalıbı yeniden kullanacağız.

C ile Python yan yana

Bu haftaki işlerin C’deki ve Python’daki karşılıkları:

İş C’de Python’da Dikkat
Ortalamayı hesaplayıp başka hesapta kullanmak Fonksiyon yazar, sonucu return ile döndürürsünüz def calculate_average(first, second): ve return Yalnız print() eden fonksiyonun çağrısı None verir
Sınırı çoğu derste 50, bazılarında 60 almak Varsayılan değer yoktur, sınırı her çağrıda yazarsınız def is_successful(score, limit=50): Varsayılan değer olarak liste vermeyin, None nöbetçisini kullanın
Hesaplanan ortalamaları saymak Global bir sayaç tutar, fonksiyonda count++ yazarsınız Sayıyı parametreyle alıp yeni değeri return ile vermek ya da global count global yazmadan dıştaki ada atama yapmak UnboundLocalError verir
Not listesine not eklemek Diziyi ve eleman sayısının adresini verirsiniz, fonksiyon asıl diziyi değiştirir Fonksiyonda scores.append(value) Python listeyi kopyalamaz, çağıranın listesi değişir. Bunu docstring’e yazın
Fonksiyonları ayrı dosyada kullanmak Prototipi .h, gövdeyi .c dosyasına yazar, #include eder ve birlikte derlersiniz pricing.py ve from pricing import final_price İlk import modülün kodunu baştan sona çalıştırır. Deneme kodunu if __name__ == "__main__": altına yazın

return iki dilde de aynı işi görür. Varsayılan değer C’de yoktu, Python’da var. Son üç satırda ise C alışkanlığı sizi yanıltabilir. C’de global değişkeni fonksiyonun içinden doğrudan değiştirebilirdiniz, Python bunun için global ister. C’de dizi fonksiyona adresiyle, int ise kopya olarak giderdi. Python’da her parametre çağıranın verdiği nesneye bağlanır. Listenin değişip sayının değişmemesi, listenin değiştirilebilir, int’in değiştirilemez olmasından gelir. C’deki #include başlık dosyasındaki bildirimleri ekler, Python’daki import ise modülün kodunu çalıştırır. Sonraki haftalarda fonksiyonlara listelerin yerine kendi yazdığımız nesneleri vereceğiz. Listeler için geçerli olan kural onlar için de geçerli: fonksiyon nesneyi değiştirirse çağıran da değişikliği görür.

Alıştırma: return ekle

clamp_zero negatif bir değer için 0, öteki değerler için değerin kendisini döndürsün. Boşlukları doldurun.

Beklenen çıktı 0 ve 8’dir.

def clamp_zero(value):
    if value < 0:
        return 0
    return value

Alıştırma: Yan etkiyi tahmin et

Önce çıktıyı tahmin edin, sonra hücreyi çalıştırın.

  1. Çıktıyı neden böyle tahmin ettiniz? Hangi satır listeyi değiştiriyor?
  2. Fonksiyonun çağıranın listesini değiştirmesini istemeseydik hangi yaklaşımı kullanabilirdik?
  3. Fonksiyonun listeyi değiştirdiği adından anlaşılıyor mu, yoksa bunu docstring’e de yazmak gerekir mi?
  1. ['Ayşe', 'Mehmet'] yazar. items.pop() satırı, names ile aynı listeye bağlı olan items üzerinden listenin son elemanını çıkarır.
  2. Yeni bir liste döndürmek: return items[:-1]. Dilim yeni bir liste oluşturur, names olduğu gibi kalır. Çağıran sonucu shorter = remove_last(names) diye alır.
  3. “remove” (çıkar) sözcüğü listenin değiştiğini düşündürür, ama hangi listenin değiştiğini söylemez. Docstring’e “Çağıranın listesini değiştirir, bir değer döndürmez.” yazmak bu soruyu ortadan kaldırır.

Alıştırma: Varsayılan listeyi düzelt

Aşağıdaki collect fonksiyonunu, liste verilmeyen her çağrı yeni bir liste ile başlayacak biçimde düzeltin. None nöbetçisini kullanın. Çağıran bir liste verirse fonksiyon o listeye eklemeye devam etsin.

Düzeltince iki satır [1] ve [2] yazmalı. Buna neden gerek olduğunu kendinize bir cümleyle açıklayın.

def collect(value, values=None):
    if values is None:
        values = []
    values.append(value)
    return values

Sıra sizde: Not yardımcı modülü

Kendi bilgisayarınızda aynı klasöre iki dosya kaydedin. grades.py dosyasında şu iki fonksiyonu tanımlayın ve her birine kısa bir docstring ekleyin:

  • average(scores): not listesinin ortalamasını döndürür. Listenin boş olmadığını varsayabilirsiniz.
  • is_successful(score, limit=50): not sınıra eşit ya da büyükse True döndürür.

app.py dosyasında bu fonksiyonları import ile içe aktarın ve birkaç örnek veriyle çağırın. Argümanları hem sırasıyla (konumsal argüman) hem de parametre adıyla (anahtar kelime argümanı) verin. Ardından grades.py dosyasına, dosya doğrudan çalıştırıldığında birkaç örnek sonuç yazdıran bir main() fonksiyonu ve main guard ekleyin.

Çalışır kod görevi: app.py dosyasını Windows’ta py app.py, macOS’ta python3 app.py ile çalıştırın. app.py içine en az üç assert satırı ekleyin. assert satırı bir koşulu denetler: koşul doğruysa hiçbir şey yapmaz, yanlışsa programı AssertionError ile durdurur. Olağan değerleri de sınır değerlerini de deneyin. Örnek:

assert average([70, 80]) == 75
assert is_successful(50)
assert not is_successful(49)
assert not is_successful(55, limit=60)

app.py çalışırken grades.py içindeki örnek sonuçlar ekranda görünmemeli. Görünüyorsa main guard’ı denetleyin.

Sınav provası

Önce kendi cevabınızı seçin; sonra cevap anahtarında her şıkkın neden doğru veya yanlış olduğunu okuyun. Çeldiriciler uydurma değil, bu konuda gerçekten yapılan hatalardır. Sınav soruları da bu mantıkla yazılır.

1. Aşağıdaki program ne yazdırır?

def show_total(a, b):
    print(a + b)

total = show_total(3, 4)
print("total:", total)
  • A. Önce 7, sonra total: 7
  • B. Önce 7, sonra total: None
  • C. Yalnızca total: 7
  • D. Yalnızca total: None
  • E. Hata verir; dönüş değeri olmayan bir çağrı bir değişkene atanamaz

2. Aşağıdaki program ne yazdırır?

def needs_retake(score, limit=50):
    return score < limit

print(needs_retake(55))
print(needs_retake(55, 60))
  • A. False ve False
  • B. False ve None
  • C. True ve True
  • D. Hata verir; varsayılanı olan parametre çağrıda verilemez
  • E. False ve True

3. Aşağıdaki program ne yazdırır?

def add_tag(tag, tags=[]):
    tags.append(tag)
    return tags

first = add_tag("yeni")
second = add_tag("acil")
print(first, second)
  • A. Hata verir; varsayılan değer olarak liste verilemez
  • B. ['yeni'] ['yeni', 'acil']
  • C. ['yeni'] ['acil']; her çağrıda yeni bir liste oluşturulur
  • D. ['yeni', 'acil'] ['yeni', 'acil']; iki ad da aynı listeye bağlı
  • E. [] []

4. Değiştirilebilir varsayılan değer tuzağından kaçınmak için hangi kalıp kullanılır?

  • A. Varsayılanı [] yerine () yapmak
  • B. Varsayılanı [] bırakıp gövdenin başında items.clear() çağırmak
  • C. Varsayılanı None yapıp listeyi gövdede oluşturmak
  • D. Varsayılanı [] yerine list() yapmak
  • E. Parametreyi tümüyle kaldırıp global bir liste kullanmak

5. Aşağıdaki fonksiyon çağrıldığında ne olur?

score = 40

def add_bonus():
    score += 5
    print(score)

add_bonus()
  • A. UnboundLocalError verir
  • B. 45 yazdırır ama dıştaki score 40 kalır
  • C. NameError verir
  • D. 45 yazdırır ve dıştaki score da 45 olur
  • E. 5 yazdırır; yerel score sıfırdan başlar

6. Aşağıdaki program ne yazdırır?

def add_first(names, name):
    names.insert(0, name)

students = ["Can", "Ece"]
add_first(students, "Ali")
print(students)
  • A. None
  • B. ['Ali', 'Can', 'Ece']; names aynı listeye bağlanır
  • C. ['Can', 'Ece', 'Ali']
  • D. Hata verir; fonksiyon dışındaki liste değiştirilemez
  • E. ['Can', 'Ece']; fonksiyon kendi kopyası üzerinde çalışır

7. Aşağıdaki fonksiyon çağıranın scores listesini değiştirmeden, sonuna value eklenmiş yeni bir liste döndürmelidir. Gövdeye hangisi yazılmalıdır?

def add_score(scores, value):
    ...  # gövde
  • A. return scores + [value]
  • B. return list(value)
  • C. return scores yazıp çağıranın kopya almasını beklemek
  • D. scores.append(value) ve ardından return scores
  • E. scores += [value] ve ardından return scores

8. Bir fonksiyonun docstring’i öncelikle neyi belirtmelidir?

  • A. Gövdedeki her satırın ne yaptığını sırayla
  • B. Fonksiyonun hangi modülde durduğunu
  • C. Girdiyi, dönüş değerini, yan etkiyi ve hata koşulunu
  • D. Fonksiyonun kaç satır olduğunu ve kimin yazdığını
  • E. Gövdedeki yerel değişkenlerin adlarını

9. if __name__ == "__main__": bloğu ne işe yarar?

  • A. Hataları yakalayıp programın çökmesini engeller
  • B. Modülün yalnızca bir kez import edilmesini sağlar
  • C. Programın main adlı bir fonksiyon içermesini zorunlu kılar
  • D. Ana akışı yalnız dosya doğrudan çalıştırıldığında başlatır
  • E. Dosyanın çalıştırılabilir olmasını sağlar

1. Doğru: B. İlk satırı fonksiyonun içindeki print() çağrısı yazdırır. Fonksiyon return içermediği için çağrının sonucu None’dır ve total adı None’a bağlanır.

  • A yanlış: Ekranda 7 göründüğü için fonksiyonun bu değeri verdiği düşünülür. Ama print() değeri yalnız ekrana yazar, çağırana vermez.
  • C yanlış: Atama satırında fonksiyonun yalnız bir değer hesapladığı düşünülebilir. Ama çağrı sırasında gövdedeki print() de çalışır ve ekrana 7 yazar. Üstelik total adına 7 gelmez.
  • D yanlış: None sonucunu bilen biri gövdedeki print() satırını gözden kaçırabilir. Çağrı sırasında o satır da çalışır.
  • E yanlış: C’de void bir fonksiyonun sonucunu atamak derleme hatası verirdi. Python’da atama geçerlidir, atanan değer None’dır.

2. Doğru: E. İlk çağrıda varsayılan 50 kullanılır ve 55 < 50 yanlıştır. İkincide ikinci konumsal argüman limit parametresine gider, sınır 60 olur ve 55 < 60 doğrudur.

  • A yanlış: Tanımdaki limit=50 yazımı sınırın hep 50 olduğunu düşündürebilir. Ama çağrıda verilen argüman varsayılanın yerini alır.
  • B yanlış: İkinci argümanın fonksiyonu bozduğu düşünülebilir. Fonksiyon iki çağrıda da bir karşılaştırma sonucu döndürür.
  • C yanlış: 55 ile 50 arasındaki karşılaştırmanın yönü karıştırılırsa bu sonuç çıkar. İlk çağrıda 55 < 50 yanlıştır.
  • D yanlış: C’de varsayılan parametre olmadığı için bu kuralın bir sınır koyduğu sanılabilir. Çağrı geçerlidir; varsayılan yalnızca değer verilmediğinde kullanılır.

3. Doğru: D. Varsayılan liste def satırı çalışırken bir kez oluşturulur. Liste verilmeyen iki çağrı da bu listeye ekler ve onu döndürür. first ile second aynı listeye bağlı olduğu için print anında ikisi de son içeriği gösterir.

  • A yanlış: Tuzağın adını duyan biri Python’ın bunu yasakladığını düşünebilir. Tanım geçerlidir; sorun, aynı listenin hata vermeden paylaşılmasıdır.
  • B yanlış: Paylaşımı fark eden biri first adının ilk çağrıdaki içeriği sakladığını düşünebilir. Ama first bir kopya değildir; ikinci çağrının değiştirdiği listeye bağlıdır.
  • C yanlış: C’de yerel değişkenler her çağrıda yeniden kurulduğu için bu sonuç beklenir. Ama varsayılan liste her çağrıda yeniden oluşturulmaz; tanım anında bir kez oluşturulur.
  • E yanlış: Varsayılan değer [] olduğu için sonucun da boş liste olacağı sanılabilir. Ama append listeye gerçekten eleman ekler.

4. Doğru: C. Gövdeye if items is None: items = [] yazılır. Liste artık gövdede oluşturulduğu için liste verilmeyen her çağrı yeni bir boş listeyle başlar; çağrılar listeyi paylaşmaz.

  • A yanlış: Demet değiştirilemediği için paylaşım sorunu kalkacak gibi görünür. Ama demette append yoktur, gövdedeki çağrı hata verir.
  • B yanlış: Listeyi her çağrıda boşaltmak paylaşımı çözüyor gibi görünür. Ama çağıranın gönderdiği listeyi de boşaltır, bu da yeni bir yan etkidir.
  • D yanlış: list() bir çağrı olduğu için her seferinde yeni liste vereceği sanılabilir. Ama list() de def satırı çalışırken bir kez çağrılır; çağrılar yine tek listeyi paylaşır.
  • E yanlış: Tek bir liste olacağı için durum açıkça görünüyor sanılabilir. Paylaşımı daha da büyütür; hangi kodun listeyi değiştirdiğini izlemek zorlaşır.

5. Doğru: A. score += 5 bir atamadır. Gövdede score’a atama olduğu için Python bu adı fonksiyonun her satırında yerel sayar. += önce yerel score’u okumaya çalışır, ama ona henüz değer verilmemiştir.

  • B yanlış: Fonksiyonun dıştaki değeri okuyup yerel bir kopya üzerinde çalıştığı düşünülebilir. Ama Python bu kararı satır satır vermez. Fonksiyon çalışmaya başlamadan önce gövdenin tamamına bakar ve score’u baştan yerel sayar.
  • C yanlış: Ad bulunamayınca akla ilk gelen hata budur. Ama score dışarıda tanımlıdır; sorun, yerel score’a henüz değer verilmemiş olmasıdır.
  • D yanlış: C’de global değişken fonksiyon içinden doğrudan değiştirilebildiği için bu beklenir. Python’da dış adı değiştirmek için global bildirimi gerekir.
  • E yanlış: Yerel değişkenin 0 ile başladığı düşünülebilir. Python’da değer verilmemiş bir ad 0 sayılmaz; okunmaya çalışılınca hata verir.

6. Doğru: B. Parametre adı, çağıranın verdiği liste nesnesine bağlanır; Python argümanı kopyalamaz. insert ortak listeyi değiştirdiği için çağıran da yeni içeriği görür.

  • A yanlış: add_first bir değer döndürmediği için None beklenebilir. Ama yazdırılan, fonksiyonun dönüş değeri değil, students listesidir.
  • C yanlış: Listeye eleman eklemek çoğu zaman append ile sona yapıldığı için yeni adın sona gittiği sanılabilir. insert(0, name) adı listenin başına ekler.
  • D yanlış: Dışarıdaki verinin fonksiyon içinden korunduğu sanılabilir. Böyle bir kısıt yoktur; yan etki olup olmayacağına fonksiyonu yazan karar verir.
  • E yanlış: Bu beklenti, C’deki “fonksiyona değerin kopyası gider” alışkanlığından gelir. Python listeyi kopyalamaz.

7. Doğru: A. + yeni bir liste oluşturur; scores olduğu gibi kalır.

  • B yanlış: list() yeni liste oluşturduğu için çözüm gibi görünür. Ama scores içindeki değerleri yok sayar ve tek değeri listeye çevirmeye çalışır.
  • C yanlış: Sorunun çağıranda olduğu düşünülebilir. Ama fonksiyon hâlâ aynı listeyi döndürür; kopya almayı unutan her çağıran yan etkiyle karşılaşır.
  • D yanlış: Fonksiyon bir liste döndürdüğü için yeni liste verdiği sanılabilir. Ama döndürülen, yerinde değişmiş olan çağıranın listesidir.
  • E yanlış: int için += yeni bir nesne oluşturduğundan listede de öyle olacağı sanılabilir. Listelerde += var olan listeyi yerinde uzatır; scores değişir.

8. Doğru: C. Docstring fonksiyonun sözleşmesidir; fonksiyonu çağıran kişinin gövdeyi okumadan bilmesi gerekenleri yazar.

  • A yanlış: Ayrıntılı açıklama daha yararlı gibi görünebilir. Ama bu, kodu bir kez daha anlatmak olur; üstelik kod değişince eskir.
  • B yanlış: Fonksiyonu bulmak için gerekli bir bilgi gibi görünür. Ama bu zaten dosya yapısından ve import satırından okunur.
  • D yanlış: Dosyanın başındaki açıklamalarda bu bilgiler görüldüğü için docstring’e de yazılacağı sanılabilir. Fonksiyonu çağıran kişi davranışı anlamak için bu bilgiye ihtiyaç duymaz.
  • E yanlış: C’de değişken bildirimlerinin başa yazılması bu bilginin önemli olduğunu düşündürebilir. Yerel adlar dışarıdan görünmez; çağıranın işine yaramaz.

9. Doğru: D. Doğrudan çalıştırmada __name__ değeri "__main__" olur; import edildiğinde modül adını alır ve blok çalışmaz. Tanımlar ile ana akış böyle ayrılır.

  • A yanlış: if bloğu programı saran bir koruma gibi görünebilir. Hata yakalamayla ilgisi yoktur; bu iş try ve except ile yapılır.
  • B yanlış: Blok import ile ilgili olduğu için import sayısını denetlediği düşünülebilir. Python bir modülü zaten ilk içe aktarmada bir kez çalıştırır; bunun bu blokla ilgisi yoktur.
  • C yanlış: C’de her programın bir main() fonksiyonu olduğu için bu blok o kuralın Python karşılığı sanılabilir. Böyle bir zorunluluk yoktur; blok içine herhangi bir kod yazılabilir.
  • E yanlış: Blok ana akışı başlattığı için dosyayı çalıştırılabilir yapan şey sanılabilir. Python dosyaları bu blok olmadan da çalıştırılabilir.

Tek sayfa özet

  • Python’da fonksiyon def ile tanımlanır. Parametre ve dönüş türü yazılmaz, gövdeyi girinti belirler.
  • return sonucu çağırana verir, print() yalnızca ekrana yazar. return içermeyen fonksiyonun çağrısı None verir. return çalışınca fonksiyon biter.
  • Varsayılan değeri olan parametre çağrıda yazılmayabilir. Konumsal argüman sırasına göre, anahtar kelime argümanı parametrenin adıyla verilir. Çağrıda konumsal argümanlar önce gelir, tanımda varsayılanı olan parametreler sona yazılır.
  • Fonksiyon dıştaki bir adı okuyabilir, ama gövdede o ada atama yaparsa Python adı fonksiyonun her satırında yerel sayar. UnboundLocalError çoğu zaman bundan çıkar. Dıştaki adı değiştirmek için global gerekir, ama yeni değeri return ile vermek daha kolay izlenir.
  • Fonksiyon çağrılınca parametre adı verilen nesneye bağlanır. Fonksiyon listeyi yerinde değiştirirse (append, pop, +=) çağıran da değişikliği görür. int gibi değiştirilemez bir değerde += yalnız yerel adı yeni bir nesneye bağlar.
  • Varsayılan değer def satırı çalışırken bir kez oluşturulur. Liste gerekiyorsa varsayılanı None yapın, listeyi gövdede oluşturun.
  • Docstring, fonksiyonun ne alıp ne döndürdüğünü, neyi değiştirdiğini ve ne zaman hata verdiğini kodun hemen yanında anlatır. help() onu ekranda gösterir.
  • Her .py dosyası bir modüldür. Modül ilk kez içe aktarıldığında kodu baştan sona bir kez çalışır. Deneme kodu if __name__ == "__main__": (main guard) altına yazılır.

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

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

  • print() ile return farkını açıklar ve return içermeyen bir fonksiyonun çağrısının None verdiğini öngörür.
  • Varsayılan parametre, konumsal argüman ve anahtar kelime argümanıyla yazılmış bir çağrının sonucunu öngörür.
  • Değiştirilebilir varsayılan değer tuzağını tanır ve None nöbetçisiyle önler.
  • Yerel ad ile dış kapsamdaki adı ayırt eder ve UnboundLocalError nedenini açıklar.
  • Fonksiyona verilen nesnenin çağrıdan sonra değişip değişmeyeceğini öngörür.
  • Bir docstring’in fonksiyonun sözleşmesinden neleri yazması gerektiğini açıklar.
  • import’un modülü nasıl çalıştırdığını ve __name__ == "__main__" kalıbının deneme kodunu neden ayırdığını açıklar.
Başa dön