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. Söz dizimi C’ye yakın. Bölümde en çok C’den farklı davranan yerlerde duracağız: parametrenin varsayılan değeri, fonksiyonun dışındaki bir ada atama ve fonksiyona verilen listenin değişmesi.

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
  • print() ile return farkı ve örtük None
  • Varsayılan parametre ve anahtar kelime argümanı
  • Değiştirilebilir (mutable) varsayılan değer tuzağı ve None nöbetçisi
  • Yerel ad ile dış kapsamdaki ad, UnboundLocalError
  • Değiştirilebilir parametrede yan etki
  • Docstring ile davranış sözleşmesi
  • __name__ == "__main__" kalıbının ne işe yaradığı
  • 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.

Not programının beş işini C’de nasıl yapardınız?

Bir not programını fonksiyonlara ve dosyalara bölüyorsunuz. Program şu beş işi yapacak:

  1. Bir öğrencinin ortalamasını hesaplamak ve sonucu başka bir hesapta kullanmak
  2. Geçme sınırını çoğu derste 50 almak, bazı derslerde 60 verebilmek
  3. Programın kaç öğrencinin ortalamasını hesapladığını saymak
  4. Bir öğrencinin not listesine yeni not ekleyen bir fonksiyon yazmak
  5. Bu fonksiyonları ayrı bir dosyaya koyup başka bir programda da kullanmak

Programlama Temelleri’nde C ile yazdığınız programları düşünün. Bu işlerin her birini C’de nasıl yapardınız? Okumaya devam etmeden önce her iş için bir satır yazın. Python bilgisi gerekmiyor: “iki ayrı fonksiyon yazarım” da bir cevap.

Bölümde bu beş işin Python’da nasıl yazıldığını göreceğiz. Bölümün sonunda listenize döneceğiz.

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ırdığı için fonksiyonlar dosyanın başına yazılır.

Fonksiyonun adına bakan kişi, işin nasıl yapıldığından önce ne yapıldığını anlamalıdır. Bu yüzden calculate_average (ortalama hesapla), process ya da do_it gibi belirsiz adlardan daha açıklayıcıdır.

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. Üçüncü çağrıdaki score=70 ve limit=60 ise anahtar kelime argümanıdır, yani 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.

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

Varsayılan değer bir liste olursa ne olur? Aşağıdaki fonksiyon, liste verilmediğinde boş bir listeyle başlayacak gibi görünüyor. İ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 ve sonraki çağrılar aynı listeyi paylaşı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.

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 her çağrı kendi listesini alır.

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.

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 olduğu için count baştan yerel sayılır. print satırı çalıştığında bu yerel count’a henüz değer verilmemiştir ve Python UnboundLocalError verir.

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.

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. Bir fonksiyonun, dönüş değeri dışında dışarıda bıraktığı böyle bir değişikliğe 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?

Dışarıdaki sayı C’deki gibi değişmez, ama nedeni farklıdır. int değiştirilemez bir nesnedir. number += 1 o nesneyi değiştirmez, yerel number adını yeni bir sayı nesnesine bağlar. total hâlâ eski nesneyi gösterir. Bir adı başka bir nesneye bağlamaya 6. haftada ayrıntılı olarak döneceğiz.

Note“Pass-by-reference” diye ezberlemeyin

Bu davranışı “Python her şeyi referansla geçirir” diye genellemek yanıltıcı olabilir. 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.

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. İki liste + ile birleştirilince yeni bir liste oluşur, iki taraf da değişmez. İki satır ne yazar?

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.

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:

def calculate_discount(price, rate=0.10):
    """Pozitif fiyat için indirimli tutarı 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)

help(calculate_discount) yazarsanız Python bu metni ekranda gösterir. Buradaki raise satırı, fiyat negatifse hatayı çağırana bildirir. raise, try ve except arasındaki ilişkiyi 5. haftada ayrıntılı olarak göreceğiz.

Fonksiyonları sorumluluğa göre ayırmak

Bir fonksiyon hem veri alır, hem doğrular, hem hesaplar, hem dosyaya yazar, hem de ekrana yazarsa onu test etmek ve değiştirmek zorlaşır.

Örneğin calculate_discount yalnızca indirimi hesaplar ve fiyatın negatif olmadığını denetler. Kullanıcıdan fiyatı alan kod başka bir fonksiyonda durabilir.

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. Ö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ı 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ücreleri tek dosyayla çalıştığı için modül örneklerini 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 dizin belirir. Python, içe aktardığı modülün bytecode’unu (1. haftada gördüğümüz ara kodu) orada saklar. Bu dizini 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 import, modülün bütün satırlarını bir kez çalıştırır. pricing.py dosyasının en dışına print(final_price(100, 0.20)) gibi bir deneme satırı yazarsanız, bu satır modülü içe aktaran her programda da çalışır.

Bunu önlemek için Python’ın her modülde tuttuğu __name__ adı kullanılır. 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").

# 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ıpla fonksiyon tanımları bir yanda, yalnızca dosya doğrudan çalıştırıldığında işleyen kod öbür yanda durur. py pricing.py deneme satırını çalıştırı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.

Soruya dönelim: beş iş Python’da

Bölümün başında not programının beş işini C’de nasıl yapacağınızı sormuştuk. Python’daki karşılıkları şöyle:

İş 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 global count ya da sayıyı parametreyle alıp return ile vermek 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 import modülün bütün satırlarını çalıştırır. Deneme kodunu if __name__ == "__main__": altına yazın

Listenizi tabloyla karşılaştırı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. Sonraki haftalarda fonksiyonlara listelerin yerine kendi yazdığımız nesneleri vereceğiz. Dördüncü satırdaki kural onlar için de geçerli.

Alıştırma: return ekle

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?

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

Aşağıdaki fonksiyonu, her çağrı kendi ayrı listesini döndürecek biçimde düzeltin:

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

None nöbetçisini kullanın ve buna neden gerek olduğunu bir cümleyle açıklayın.

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)
  • is_successful(score, limit=50)

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 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. Örneğin assert is_successful(50) satırı sınır değerini denetler. average ve is_successful için hem olağan değerleri hem sınır değerlerini deneyin.

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 maddeleri de bu mantıkla yazılır.

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

def show_square(x):
    print(x * x)

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

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

def is_successful(score, limit=50):
    return score >= limit

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

Madde 3. Aşağıdaki fonksiyon iki kez çağrıldığında ne olur?

def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item("A"))
print(add_item("B"))
  • A. ['A', 'B'] ve ['A', 'B']
  • B. İki çağrı da [] döndürür
  • C. ['A'] ve ['B']; her çağrıda yeni bir liste oluşturulur
  • D. ['A'] ve ['A', 'B']; varsayılan liste paylaşılır
  • E. İkinci çağrı TypeError verir

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

  • A. Varsayılanı () yapmak
  • B. Varsayılanı [] bırakıp gövdenin başında items.clear() çağırmak
  • C. Varsayılanı None yapmak ve gövdede if items is None: items = [] yazmak
  • D. Fonksiyonu her çağrıdan önce yeniden tanımlamak
  • E. Parametreyi tümüyle kaldırıp global bir liste kullanmak

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

count = 10

def increase():
    print(count)
    count = count + 1

increase()
  • A. UnboundLocalError; atama yüzünden count yerel sayılır
  • B. 11 yazdırır
  • C. Hata vermez; dıştaki count değeri 11 olur
  • D. 10 yazdırır; count dıştan okunur ve sonra yerel olur
  • E. NameError; count tanımlı değildir

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

def add_score(scores, value):
    scores.append(value)

numbers = [70, 80]
add_score(numbers, 90)
print(numbers)
  • A. None
  • B. [70, 80, 90]; scores aynı listeye bağlanır
  • C. [90]
  • D. Hata verir; fonksiyon dışındaki liste değiştirilemez
  • E. [70, 80]; fonksiyon kendi kopyası üzerinde çalışır

Madde 7. Çağıranın listesini değiştirmeden yeni bir liste döndürmek için gövdeye hangisi yazılmalıdır?

  • 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

Madde 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. Beklenen girdiyi, dönüş değerini ve önemli yan etki ile hata koşullarını
  • D. Fonksiyonun kaç satır olduğunu ve kim tarafından yazıldığını
  • E. Kullanılan algoritmanın karmaşıklık analizini

Madde 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

Madde 1. Doğru: B. İlk satırı fonksiyonun içindeki print() çağrısı yazdırır. Fonksiyon return içermediği için dönüş değeri Nonedır.

  • A yanlış: Fonksiyon 25 değerini ekrana yazar ama döndürmez; first adına bir sonuç gelmez.
  • C yanlış: Fonksiyon gövdesindeki print() de çalışır.
  • D yanlış: Çağrı sırasında gövdedeki print() çıktısı da görünür.
  • E yanlış: Atama geçerlidir; atanan değer Nonedır.

Madde 2. Doğru: E. İlk çağrıda varsayılan 50 kullanılır ve 70 >= 50 doğrudur. İkincide sınır 80 olur ve 70 >= 80 yanlıştır.

  • A yanlış: İkinci çağrıda verilen argüman varsayılanın yerini alır.
  • B yanlış: Fonksiyon her iki çağrıda da bir karşılaştırma sonucu döndürür.
  • C yanlış: İlk çağrıda sınır 50’dir ve koşul sağlanır.
  • D yanlış: Çağrı geçerlidir; varsayılan yalnızca değer verilmediğinde kullanılır.

Madde 3. Doğru: D. Varsayılan değer fonksiyon tanımlanırken bir kez oluşturulur ve çağrılar arasında paylaşılır. Her çağrı aynı listeyi kullanır.

  • A yanlış: Paylaşımı fark eden öğrenci iki çıktının da son durumu göstereceğini düşünebilir. Ama ilk çağrının çıktısı o andaki içeriği gösterir; B henüz eklenmemiştir.
  • B yanlış: Varsayılan değer [] olduğu için sonucun da boş liste olacağı sanılabilir. Ama append listeye gerçekten eleman ekler.
  • 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ış: Aynı listeye ikinci kez dokunmanın bir çakışma yaratacağı düşünülebilir. Çağrı geçerlidir; sorun, aynı listenin hata vermeden paylaşılmasıdır.

Madde 4. Doğru: C. Böylece liste her çağrıda yeniden oluşur; paylaşım ortadan kalkar.

  • A yanlış: Demet değiştirilemez olduğu için append çağrısı hata verir.
  • B yanlış: Çağıranın gönderdiği listeyi de boşaltır; yeni bir yan etki yaratır.
  • D yanlış: Böyle bir çözüm uygulanamaz; sorun, varsayılan listenin ne zaman oluşturulduğudur.
  • E yanlış: Paylaşımı daha da büyütür; hangi kodun listeyi değiştirdiğini izlemek zorlaşır.

Madde 5. Doğru: A. Python bir adın yerel olup olmadığına fonksiyonun tamamına bakarak karar verir. Gövdede counta atama olduğu için ad yerel kabul edilir, ama print anında henüz değeri yoktur.

  • B yanlış: Fonksiyon sayacı artırdığı için sonuç 11 gibi görünür. Ama print satırı atamadan önce gelir; üstelik hata verdiği için hiçbir şey yazdıramaz.
  • C yanlış: C’de küresel 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 gerekirdi.
  • D yanlış: Satırlar sırayla çalıştığı için adın önce dıştan okunduğu düşünülür. Ama Python bu kararı satır satır vermez; fonksiyon derlenirken bir kez verir.
  • E yanlış: Ad bulunamayınca akla ilk gelen hata budur. Ama count dışarıda tanımlıdır; sorun, yerel counta henüz değer verilmemiş olmasıdır.

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

  • A yanlış: add_score bir değer döndürmediği için None beklenebilir. Ama yazdırılan, fonksiyonun dönüş değeri değil, numbers listesidir.
  • C yanlış: Fonksiyona yalnız 90 gönderildiği için sonucun o olduğu sanılabilir. Ama append listeyi sıfırlamaz; sona ekler.
  • D yanlış: Dışarıdaki verinin fonksiyon içinden korunduğu sanılabilir. Böyle bir kısıt yoktur. Fonksiyon dışarıdaki listeyi değiştirebildiği için yan etki olup olmayacağına fonksiyonu yazan karar verir.
  • E yanlış: Kopya oluşturulmaz. Bu beklenti, C’deki “fonksiyona değerin kopyası gider” alışkanlığından gelir.

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

  • B yanlış: scores içindeki değerleri yok sayar ve tek değeri listeye çevirmeye çalışır.
  • C yanlış: Kopya almayı çağırana bırakır; fonksiyon hâlâ aynı listeyi döndürür.
  • D yanlış: scores yerinde değişir; yan etki ortadan kalkmaz.
  • E yanlış: Listelerde += mevcut listeyi yerinde uzatır; scores değişir.

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

  • A yanlış: Bu, kodu bir kez daha anlatmak olur; üstelik kod değişince eskir.
  • B yanlış: Bu bilgi zaten dosya yapısından okunur.
  • D yanlış: Fonksiyonu çağıran kişi davranışı anlamak için bu bilgiye ihtiyaç duymaz.
  • E yanlış: Yararlı olabilir ama sözleşmenin temel parçası değildir.

Madde 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 yönetimiyle ilgisi yoktur; try/except ayrı bir konudur.
  • B yanlış: Blok import ile ilgili olduğu için import sayısını denetlediği düşünülebilir. Python bir modülü zaten bir kez yükler; 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.
  • Varsayılan değeri olan parametre çağrıda yazılmayabilir. Anahtar kelime argümanı, değeri parametrenin adıyla verir.
  • 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.
  • Fonksiyon dıştaki bir adı okuyabilir, ama gövdede o ada atama yaparsa Python adı yerel sayar. UnboundLocalError çoğu zaman bundan çıkar. Dıştaki adı değiştirmek için global gerekir.
  • Fonksiyon çağrılınca parametre adı verilen nesneye bağlanır. Fonksiyon listeyi yerinde değiştirirse ç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.
  • Docstring, fonksiyonun ne alıp ne döndürdüğünü ve neyi değiştirdiğini kodun hemen yanında anlatır.
  • Her .py dosyası bir modüldür. import modülün bütün satırlarını bir kez çalıştırır. Deneme kodu if __name__ == "__main__": altına yazılır.

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

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

  • print() ile return farkını gösterir ve değer döndürmeyen çağrının None verdiğini bilir.
  • Varsayılan parametre ve anahtar kelime argümanını uygun durumda kullanı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ırır ve UnboundLocalError nedenini açıklar.
  • Fonksiyona verilen değiştirilebilir nesnenin yan etkiyle değişebileceğini gösterir.
  • Docstring ile bir fonksiyonun davranış sözleşmesini yazar.
  • __name__ == "__main__" kalıbının fonksiyon tanımlarını ana akıştan neden ayırdığını açıklar.
Back to top