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 hafta neleri yapabilmelisiniz?

  • def ile fonksiyon tanımlayabilmeli,
  • Parametre ile argüman ayrımını açıklayabilmeli,
  • return ile sonuç döndürebilmeli,
  • Varsayılan parametre ve anahtar kelime argümanı kullanabilmeli,
  • Mutable varsayılan parametre tuzağını fark edebilmeli,
  • Yerel ve küresel kapsam farkını gözlemleyebilmeli,
  • Küresel ada fonksiyon içinde atama yapılmasının neden UnboundLocalError üretebileceğini açıklayabilmeli,
  • Fonksiyona değiştirilebilir nesne verildiğinde yan etkileri fark edebilmeli,
  • Kısa docstring ile davranış sözleşmesini görünür kılabilmeli,
  • Kendi modülünüzü oluşturup import edebilmeli,
  • Bir programı anlamlı sorumluluklara göre fonksiyonlara ayırabilmelisiniz.

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.

Kendinizi kontrol edin

  1. print() ile return arasındaki fark nedir?
  2. limit=50 ne anlama gelir?
  3. is_successful(score=70, limit=60) çağrısındaki adlandırılmış argümanların avantajı nedir?
  4. def f(items=[]) neden risklidir?
  5. Küresel adı okumak ile fonksiyon içinde aynı ada atama yapmak neden farklı sonuç verebilir?
  6. global ne yapar ve neden varsayılan tasarım tercihi olmamalıdır?
  7. Liste parametresi üzerinde append() yapmak çağıranı neden etkileyebilir?
  8. Docstring neyi görünür kılar?
  9. if __name__ == "__main__": kalıbının temel amacı nedir?

Bu haftadan akılda kalması gerekenler

  • 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.
Back to top