Fonksiyonlar, kapsam ve modüller

Nesne tabanlı programlama, iyi fonksiyon tasarımının yerine geçmez. Aksine sınıf metotları da fonksiyon mantığı üzerine kurulur. Bu hafta Python’da parametre, dönüş değeri, kapsam, varsayılan değer, docstring ve modül kullanımını hızlı biçimde toparlayacağız; aynı zamanda “tek büyük program” yerine sorumlulukları küçük parçalara ayırma alışkanlığını güçlendireceğiz.

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ı
  • mutable varsayılan parametre 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 işlevi
  • 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; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.

Fonksiyon çağıranı değiştirmeli mi, yoksa sonuç mu üretmeli?

Aynı işi iki biçimde yazabilirsiniz: verilen listeyi yerinde değiştirmek ya da yeni bir sonuç döndürmek. İkisi de çalışır ama çağıran taraf için anlamları farklıdır. Bölümün ayırt edici sorusu budur: bu fonksiyon dışarıya ne vaat ediyor?

Fonksiyon: adlandırılmış davranış

first ve second parametrelerdir; çağrıdaki 70 ve 90 argümanlardır.

Fonksiyon adı, “nasıl yapılıyor?” ayrıntısından önce “ne yapıyor?” sorusuna cevap vermelidir. calculate_average bu açıdan process veya do_it gibi belirsiz adlardan daha açıklayıcıdır.

Varsayılan parametre ve anahtar kelime argümanı

limit=50, çağıran başka değer vermediğinde kullanılacak varsayılandır. score=70 ve limit=60 ise anahtar kelime argümanlarıdır; özellikle birden çok isteğe bağlı parametre olduğunda çağrının niyetini görünür kılar.

Mutable varsayılan parametre tuzağı

Şu kod masum görünür:

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

Ancak varsayılan [] nesnesi her çağrıda yeniden oluşturulmaz; aynı liste sonraki çağrılar arasında paylaşılabilir. Güvenli ve yaygın kalıp:

Bu sorun 7. haftadaki paylaşılan mutable sınıf niteliğiyle aynı temel fikre dayanır: istemeden ortak mutable nesne paylaşmak.

Yerel kapsam, global ad ve UnboundLocalError

tax fonksiyon içinde yereldir. rate dış kapsamdan okunmuştur.

Ancak fonksiyon içinde aynı ada atama yaparsanız Python bu adı o fonksiyonda yerel kabul eder:

count = 10

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

Bu çağrı UnboundLocalError üretir; çünkü count yerel kabul edilmiştir fakat ilk print anında henüz yerel değeri yoktur.

global ile dıştaki ada açıkça atama yapılabilir:

count = 10

def increase():
    global count
    count += 1

Fakat bu mekanizmayı bir tasarım çözümü olarak sık kullanmak programı zor izlenir hâle getirir. Bu derste mümkün olduğunda girdiyi parametreyle alıp sonucu return ile vermeyi tercih edeceğiz.

Değiştirilebilir parametre ve yan etki

Aşağıdaki örneği çalıştırmadan önce numbers listesinin değişip değişmeyeceğini tahmin edin.

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

Çağrı sırasında scores parametre adı, çağıranın verdiği aynı liste nesnesine bağlanır. Python listeyi otomatik olarak kopyalamaz. append() bu ortak liste nesnesinin durumunu değiştirdiği için çağıran taraf da yeni içeriği görür. Bu davranış bazen istenen şeydir; bazen beklenmedik yan etkidir. Fonksiyonun sözleşmesi bunu açık kılmalıdır.

Note“Pass-by-reference” diye ezberlemeyin

Bu davranışı “Python her şeyi referansla geçirir” biçiminde genellemek yanıltıcı olabilir. Bu derste daha güvenli zihinsel model şudur: adlar nesnelere bağlanır; fonksiyon çağrısında parametre adları da verilen nesnelere bağlanır. Mutable nesne yerinde değiştirilirse bu değişiklik aynı nesneyi gören yerlerden fark edilir.

Alternatif olarak yeni liste döndürebiliriz:

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

Fonksiyonun adı her ayrıntıyı anlatmaz. Kısa bir docstring, beklenen girdiyi, dönüş değerini ve önemli yan etki/hata koşullarını görünür kılar:

def calculate_discount(price, rate=0.10):
    """Pozitif fiyat için indirimli tutarı döndürür.

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

Buradaki raise ifadesi bir hata koşulunu çağırana bildirir. 5. haftada raise, try ve except ilişkisini açık biçimde öğreneceğiz.

Fonksiyonları sorumluluğa göre ayırmak

Tek bir fonksiyonun aynı anda veri alma, doğrulama, hesaplama, dosyaya yazma ve çıktı üretme görevlerini üstlenmesi test etmeyi ve değiştirmeyi zorlaştırır.

Örneğin calculate_discount yalnızca hesaplama ve temel kuralı üstlenir. Kullanıcıdan veri alma ayrı yerde tutulabilir.

Modüller: kodu dosyalara ayırmak

Bir Python dosyası modül olarak kullanılabilir. Örneğin:

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

Başka dosyada:

from pricing import final_price

print(final_price(100, 0.20))

Web sürümündeki tek hücrelerde ayrı dosya oluşturmak her zaman uygun değildir; bu nedenle modül örneklerini bu hafta çoğunlukla statik olarak okuyacağız. 1. haftada kurduğunuz yerel Python + VS Code ortamında iki dosyayla deneyin.

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

Bir dosya doğrudan çalıştırıldığında __name__ değeri "__main__" olur. Modül olarak import edildiğinde modül adını alır.

def main():
    print("Program başladı")

if __name__ == "__main__":
    main()

Bu desen, tanımlar ile doğrudan çalıştırılan örnek/ana akışı ayırmaya yardımcı olur. 13. haftada çok dosyalı OOP sisteminde yeniden kullanacağız.

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

  1. Çıktıyı tahmin edin.
  2. Fonksiyonun çağıranın listesini değiştirmesini istemeseydik hangi yaklaşımı kullanabilirdik?
  3. Bu davranışın fonksiyon adından anlaşılması yeterli mi, yoksa docstring/sözleşme de gerekli mi?

Alıştırma — Mutable varsayılanı düzelt

Aşağıdaki fonksiyonu iki çağrının birbirinden bağımsız liste döndürmesini sağlayacak biçimde düzeltin:

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

None sentinel kalıbını kullanın ve neden gerekli olduğunu bir cümleyle açıklayın.

Küçük üretim görevi — Not yardımcı modülü

Yerel ortamınızda iki dosya oluşturun:

grades.py içinde:

  • average(scores)
  • is_successful(score, limit=50)

fonksiyonlarını tanımlayın. Her fonksiyona kısa docstring ekleyin.

app.py içinde bu fonksiyonları import edip hem konumsal hem anahtar kelime argümanıyla birkaç örnek veri üzerinde kullanın. Ardından grades.py dosyasını doğrudan çalıştırdığınızda küçük bir demo gösteren main() fonksiyonu ekleyin.

Çalışır kod teslimi: py app.py / python3 app.py ile programı çalıştırın; en az üç assert ile average ve is_successful için normal ve sınır durumlarını doğrulayın.

Sınav provası

Önce kendi cevabını seç; sonra cevap anahtarında her şıkkın neden doğru veya yanlış olduğunu oku. Ç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. Yalnızca first: 25
  • C. Yalnızca first: None
  • D. Hata verir; dönüş değeri olmayan bir çağrı bir değişkene atanamaz
  • E. Önce 25, sonra first: None

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 False
  • B. True ve True
  • C. True ve None
  • D. False ve False
  • E. Hata verir; varsayılanı olan parametre çağrıda verilemez

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. ['A'] ve ['A', 'B']; varsayılan liste bir kez oluşturulur ve çağrılar arasında paylaşılır
  • C. İki çağrı da [] döndürür
  • D. ['A'] ve ['B']; her çağrıda yeni bir liste oluşturulur
  • E. İkinci çağrı TypeError verir

Madde 4. Mutable varsayılan parametre 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. 11 yazdırır
  • B. Sessizce çalışır; dıştaki count değeri 11 olur
  • C. 10 yazdırır; count dıştan okunur ve sonra yerel olur
  • D. UnboundLocalError; gövdede counta atama olduğu için ad yerel kabul edilir ama print anında henüz değeri yoktur
  • 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. [90]
  • C. Hata verir; fonksiyon dışındaki liste değiştirilemez
  • D. [70, 80]; fonksiyon kendi kopyası üzerinde çalışır
  • E. [70, 80, 90]; parametre adı çağıranın verdiği aynı liste nesnesine bağlanır

Madde 7. Çağıranın listesini değiştirmeden yeni bir sonuç üretmek için hangi yazım uygundur?

  • 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. Beklenen girdiyi, dönüş değerini ve önemli yan etki ile hata koşullarını
  • C. Fonksiyonun hangi modülde durduğunu
  • 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. Dosya doğrudan çalıştırıldığında ana akışı başlatır; modül olarak import edildiğinde bu blok çalışmaz
  • D. Programın main adlı bir fonksiyon içermesini zorunlu kılar
  • E. Dosyanın çalıştırılabilir olmasını sağlar

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

  • A yanlış: Ekrana yazmak değer döndürmek değildir; taşınan bir sonuç yoktur.
  • B yanlış: Fonksiyon gövdesindeki print() de çalışır.
  • C yanlış: Çağrı sırasında gövdedeki print() çıktısı da görünür.
  • D yanlış: Atama geçerlidir; atanan değer Nonedır.

Madde 2 — Doğru: A. İ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.

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

Madde 3 — Doğru: B. Varsayılan değer fonksiyon tanımlanırken bir kez üretilir. Aynı mutable nesne her çağrıda yeniden kullanılır.

  • A yanlış: İlk çağrının çıktısı o andaki içeriği gösterir; B henüz eklenmemiştir.
  • C yanlış: append çağrısı listeye gerçekten eklemektedir.
  • D yanlış: Beklenen budur ama gerçekleşen bu değildir; varsayılan her çağrıda yeniden değerlendirilmez.
  • E yanlış: Çağrı geçerlidir; sorun sessiz bir paylaşımdı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 üretir.
  • D yanlış: Uygulanabilir bir çözüm değildir; asıl sorun varsayılanın nerede üretildiğidir.
  • E yanlış: Paylaşım sorununu büyütür ve fonksiyonu izlenemez kılar.

Madde 5 — Doğru: D. Python fonksiyonun tamamına bakarak ad kararını verir. Atama varsa ad o fonksiyonda yereldir.

  • A yanlış: print satırı atamadan öncedir ve zaten çalışmaz.
  • B yanlış: Dış adı değiştirmek için global bildirimi gerekirdi.
  • C yanlış: Ad kararı satır satır değil, fonksiyon derlenirken verilir.
  • E yanlış: Ad dış kapsamda tanımlıdır; hata türü yerel bağın henüz kurulmamış olmasıdır.

Madde 6 — Doğru: E. Python argümanı kopyalamaz. append ortak nesnenin durumunu değiştirdiği için çağıran da yeni içeriği görür.

  • A yanlış: Yazdırılan liste adıdır; fonksiyonun dönüşü değil.
  • B yanlış: append listeyi sıfırlamaz.
  • C yanlış: Böyle bir kısıt yoktur; bu yüzden yan etki bir tasarım kararıdır.
  • D yanlış: Kopya oluşturulmaz; bu C’deki değer kopyası sezgisidir.

Madde 7 — Doğru: A. + yeni bir liste üretir; kaynak nesne olduğu gibi kalır.

  • B yanlış: Mevcut değerleri atar ve tek değeri listeye çevirmeye çalışır.
  • C yanlış: Sözleşmeyi çağırana yıkar; fonksiyon hâlâ aynı nesneyi paylaşır.
  • D yanlış: Kaynağı yerinde değiştirir; yan etki sürer.
  • E yanlış: Listelerde += yerinde genişletme yapar; kaynağı değiştirir.

Madde 8 — Doğru: B. Docstring davranış sözleşmesidir; çağıranın bilmesi gerekenleri yazar.

  • A yanlış: Kodu tekrar etmek sözleşme yazmak değildir; kod değişince de eskir.
  • C yanlış: Bu bilgi zaten dosya yapısından okunur.
  • D yanlış: Çağıranın davranışı anlaması için gerekli bilgi değildir.
  • E yanlış: Yararlı olabilir ama sözleşmenin temel parçası değildir.

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

  • A yanlış: Hata yönetimiyle ilgisi yoktur; try/except ayrı bir konudur.
  • B yanlış: Import önbelleği bundan bağımsız çalışır.
  • D yanlış: Böyle bir zorunluluk yoktur; blok içine herhangi bir kod yazılabilir.
  • E yanlış: Python dosyaları bu blok olmadan da çalıştırılabilir.

Tek sayfa özet

  • Fonksiyon, yalnızca kod tekrarını azaltan değil, sorumluluğu adlandıran bir yapıdır.
  • return sonucu çağırana verir; print yalnızca çıktı üretir.
  • Varsayılan parametreler ve anahtar kelime argümanları arayüzü sadeleştirir; mutable varsayılanlardan kaçının.
  • Kapsam kurallarında “okuma” ile “atama” aynı değildir; UnboundLocalError bu farkı görünür kılabilir.
  • Fonksiyon çağrısında parametre adları verilen nesnelere bağlanır; mutable nesnelerde yerinde değişiklik ortak nesneyi etkileyebilir.
  • Docstring, davranış sözleşmesini kodun yanında taşır.
  • Modüller programı fiziksel dosyalara ayırırken sorumluluk sınırlarını da görünür hâle getirir.

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ı yerinde kullanır.
  • Mutable varsayılan parametre tuzağını tanır ve None nöbetçisiyle kaçınır.
  • 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 tanım ile ana akışı neden ayırdığını açıklar.
Back to top