Ana içeriğe geç
Kesit Python
Temel · Fonksiyonlar ve modüller

Fonksiyonlar

def ile kendi fonksiyonunu yazar; parametre, varsayılan değer, anahtar sözcüklü argüman, *args/**kwargs ve çoklu return ile Anadolu Market KDV ve indirim hesaplarını tek yerden yönetirsin.

Uygulamada aç, 21 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

Neden fonksiyon?

Anadolu Market'in fiyat ekibinde çalıştığını düşün. Ürün listesi, sepet özeti, fatura, iade ekranı: hepsinde KDV hesaplanıyor. Bu hesabı her ekranda ayrı ayrı yazarsan bir gün KDV oranı değiştiğinde dört yeri de bulup düzeltmen gerekir; birini unutursan fatura yanlış kesilir. Fonksiyon (function), "bu hesabı bir kere yaz, her yerden çağır" demenin yoludur. Şimdiye kadar print, len, round gibi hazır fonksiyonları kullandın; bu derste kendi fonksiyonlarını yazacaksın.

Fonksiyonun üç kazancı var: tekrarı önler (aynı kodu bir kere yazarsın), ad verir (kdv_hesapla(fiyat) yazısı, formülün kendisinden daha anlaşılırdır) ve test edilebilir küçük parçalar üretir.

def, parametre ve return

Bir fonksiyon def anahtar sözcüğü, bir ad, parantez içinde parametreler (parameter) ve iki nokta ile tanımlanır. Gövde girintilidir. return, fonksiyonun ürettiği değeri çağıran yere geri gönderir.

def kdv_hesapla(fiyat, oran=0.20):
    """Fiyata KDV ekler ve KDV dahil tutarı döndürür."""
    return round(fiyat * (1 + oran), 2)

print(kdv_hesapla(100))        # varsayılan oran %20
print(kdv_hesapla(100, 0.10))  # gıda ürünü, %10
print(kdv_hesapla(fiyat=250, oran=0.01))

Burada fiyat ve oran parametre, çağrıda verdiğin 100 ve 0.10 ise argüman (argument). Parametre tanımdaki isimdir, argüman o isme gerçekten gönderilen değer. Üçlü tırnak içindeki ilk metin docstring: fonksiyonun ne yaptığını anlatır, help(kdv_hesapla) yazınca görünür. İşyerinde her fonksiyona bir satırlık docstring yazma alışkanlığı, kodunu okuyan ekip arkadaşlarına bırakacağın en kıymetli nottur.

return olmayan fonksiyonBir fonksiyonda return yoksa ya da yalnızca return yazılmışsa fonksiyon None döndürür. sonuc = print("x") yazarsan sonuc None olur; print ekrana yazar ama değer döndürmez.

Varsayılan değer ve anahtar sözcüklü argüman

oran=0.20 yazımı varsayılan değer (default value) verir: çağıran oran belirtmezse 0.20 kullanılır. Varsayılan değerli parametreler, değersiz olanlardan sonra gelmek zorundadır; def f(oran=0.2, fiyat) SyntaxError verir.

Çağrıda fiyat=250 gibi ad belirtmeye anahtar sözcüklü argüman (keyword argument) denir. Konumsal (positional) argümanlar sırayla eşleşir; anahtar sözcüklüler ise adla eşleşir, bu yüzden sırası önemsizdir. İkisini karıştırabilirsin ama konumsallar her zaman önce gelir.

def indirim_uygula(fiyat, yuzde=0, kupon=0, uye=False):
    tutar = fiyat * (1 - yuzde / 100)
    tutar -= kupon
    if uye:
        tutar *= 0.95          # üyelere ek %5
    return round(max(tutar, 0), 2)

print(indirim_uygula(1000))
print(indirim_uygula(1000, 10))
print(indirim_uygula(1000, kupon=50))
print(indirim_uygula(1000, 10, uye=True))
print(indirim_uygula(fiyat=40, kupon=100))   # eksiye düşmez

Dört parametreli bir fonksiyonda indirim_uygula(1000, 0, 0, True) yazmak okunmaz; indirim_uygula(1000, uye=True) ise kendini anlatır. Çok parametreli fonksiyonlarda anahtar sözcük kullanmak, PEP 8 kadar önemli bir ekip kuralıdır.

Çoklu dönüş: demet açma

Python'da bir fonksiyon birden fazla değer "döndürüyor" gibi görünür; aslında tek bir demet (tuple) döndürür ve sen onu açarsın (unpacking). Fatura hesabında ara toplam, KDV ve genel toplamı birlikte döndürmek doğal bir örnektir:

def fatura_hesapla(sepet, kdv_orani=0.20):
    """sepet: (urun, adet, birim_fiyat) demetlerinin listesi"""
    ara_toplam = 0
    for urun, adet, fiyat in sepet:
        ara_toplam += adet * fiyat
    kdv = round(ara_toplam * kdv_orani, 2)
    return ara_toplam, kdv, round(ara_toplam + kdv, 2)

sepet = [("Kahve Makinesi", 1, 1500), ("Filtre", 3, 45.5), ("Fincan", 6, 30)]
ara, kdv, toplam = fatura_hesapla(sepet)
print("Ara toplam:", ara)
print("KDV:", kdv)
print("Genel toplam:", toplam)
print(type(fatura_hesapla(sepet)))

Son satır <class 'tuple'> basar: döndürülen şey gerçekten bir demettir. Üç değişkene açmak için sol tarafa üç ad yazarsın; sayı tutmazsa ValueError alırsın.

*args ve **kwargs

Kaç argüman geleceğini önceden bilmediğin durumlar olur. Sepete 2 ürün de eklenebilir 20 ürün de. *args fazladan gelen konumsal argümanları bir demette, **kwargs fazladan gelen anahtar sözcüklü argümanları bir sözlükte toplar. args ve kwargs adları gelenektir; yıldızlar asıl işi yapar.

def sepet_toplami(*fiyatlar, **secenekler):
    toplam = sum(fiyatlar)
    print("gelen fiyatlar:", fiyatlar)
    print("gelen seçenekler:", secenekler)
    if secenekler.get("kargo_bedava", False) is False and toplam < 500:
        toplam += secenekler.get("kargo", 49.9)
    return round(toplam, 2)

print(sepet_toplami(120, 85.5, 60))
print(sepet_toplami(120, 85.5, 60, kargo=29.9))
print(sepet_toplami(900, 250, kargo_bedava=True))

Yıldızın tersi de çalışır: elindeki listeyi sepet_toplami(*liste), sözlüğü f(**sozluk) diyerek açıp argüman olarak gönderebilirsin.

YazımNe alırFonksiyon içinde türüÖrnek çağrı
def f(a, b)Tam iki konumsal argümantekil değerlerf(1, 2)
def f(a, b=5)Bir zorunlu, bir isteğe bağlıtekil değerlerf(1), f(1, b=9)
def f(*args)Sıfır veya daha çok konumsaltuplef(), f(1, 2, 3)
def f(**kwargs)Sıfır veya daha çok anahtar sözcüklüdictf(x=1, y=2)
def f(a, *args, k=0, **kwargs)Hepsi birden, bu sıraylakarışıkf(1, 2, 3, k=4, z=5)

Saf fonksiyon

Aynı girdiye her zaman aynı çıktıyı veren ve dış dünyayı değiştirmeyen fonksiyona saf fonksiyon (pure function) denir. kdv_hesapla(100) her çağrıda 120.0 verir; ekrana yazmaz, dosyaya dokunmaz, global değişken bozmaz. Saf fonksiyonlar test etmesi en kolay, hata ayıklaması (debugging) en rahat parçalardır. Hesabı yapan fonksiyonla sonucu yazdıran kodu ayır: hesap saf kalsın, print dışarıda olsun. Ders boyunca bu kalıbı göreceksin: fatura_hesapla hesaplar, çağıran yer yazdırır.

Değişebilir varsayılan tuzağı

Python'da en meşhur tuzaklardan biri: varsayılan değer, fonksiyon tanımlandığında bir kez oluşturulur, her çağrıda yeniden değil. Varsayılan bir liste ya da sözlükse, tüm çağrılar aynı nesneyi paylaşır.

def sepete_ekle(urun, sepet=[]):      # TUZAK
    sepet.append(urun)
    return sepet

print(sepete_ekle("Kahve"))
print(sepete_ekle("Çay"))              # Kahve hâlâ içeride!

def sepete_ekle_dogru(urun, sepet=None):
    if sepet is None:
        sepet = []
    sepet.append(urun)
    return sepet

print(sepete_ekle_dogru("Kahve"))
print(sepete_ekle_dogru("Çay"))
KuralVarsayılan değer olarak asla [], {} ya da set() yazma. None ver, gövdede if sepet is None: sepet = [] ile oluştur. Sayı, metin, demet ve None gibi değişmez (immutable) varsayılanlar güvenlidir.
İyi fonksiyon nasıl olur?Tek bir iş yapar, adı fiille başlar (hesapla, bul, filtrele), 3-5 parametreyi geçmez, docstring taşır ve mümkünse saftır. 40 satırı geçen bir fonksiyon genelde ikiye bölünmek ister.
Derinleş

Docstring: fonksiyonun kullanım kılavuzu

Fonksiyon gövdesinin ilk satırına yazılan üç tırnaklı metne belge dizgisi (docstring) denir. Yorumdan farkı, çalışma anında erişilebilir olmasıdır: help(fonksiyon) ve fonksiyon.__doc__ onu gösterir, VS Code fareyle üzerine gelince açar. Ekip kodunda herkese açık her fonksiyonun bir docstring'i olur: ne yaptığı, parametreleri, dönüş değeri ve hangi durumda hata verdiği.

def taksit(tutar: float, ay: int, faiz: float = 0.0) -> float:
    """Aylık taksit tutarını hesaplar.

    Args:
        tutar: Kredi anaparası (TL).
        ay: Taksit sayısı, 1 veya daha büyük.
        faiz: Aylık faiz oranı (0.02 = %2).

    Returns:
        2 ondalığa yuvarlanmış aylık taksit.

    Raises:
        ValueError: ay 1'den küçükse.
    """
    if ay < 1:
        raise ValueError("ay en az 1 olmalı")
    if faiz == 0:
        return round(tutar / ay, 2)
    return round(tutar * faiz / (1 - (1 + faiz) ** -ay), 2)

print(taksit(12000, 12), taksit(12000, 12, 0.02))
print(taksit.__doc__.splitlines()[0])
print(taksit.__annotations__)

Tip ipuçları (tutar: float, -> float) da __annotations__ içinde saklanır ama Python çalışma anında onları denetlemez; taksit("abc", 3) yine çağrılır ve içeride hata verir. Denetimi mypy gibi araçlar ve editörün yapar. "Değer ya da None" dönen fonksiyonlar için -> float | None, liste için list[float], sözlük için dict[str, int] yazılır.

Parametrelerin tam sırası

Bir fonksiyon tanımında parametre türlerinin sırası sabittir. Hepsini bir arada pek görmezsin ama kütüphane belgelerini okurken bu kalıbı tanıman gerekir.

SıraTürYazımNasıl çağrılır?
1Yalnız konumsala, /Yalnızca sırayla: f(1)
2Normalb, c=2Sırayla ya da adla
3Değişken konumsal*argsFazla konumsal argümanlar demet olur
4Yalnız anahtar sözcüklü* ya da *args sonrası: dYalnızca adla: d=4
5Değişken anahtar sözcüklü**kwargsFazla adlı argümanlar sözlük olur
def ornek(a, /, b, c=2, *args, d, **kwargs):
    print(a, b, c, args, d, kwargs)

ornek(1, 2, d=4)
ornek(1, 2, 3, 9, 9, d=4, rapor=True)
try:
    ornek(a=1, b=2, d=4)
except TypeError as h:
    print("Hata:", h)

Son çağrının hata mesajı ilginçtir: a yalnız konumsal olduğu için a=1 onun yerine geçmez, **kwargs sözlüğüne düşer; Python da "a eksik" der. Yalnız konumsal parametreler bu yüzden kullanılır: parametre adını ileride değiştirmek, çağıranların kodunu bozmaz. len(obj) gibi yerleşik fonksiyonlar da böyle tanımlıdır.

Çağrı tarafında açma: * ve **

*args ve **kwargs'ın tersi de var: çağrı sırasında bir listeyi * ile konumsal argümanlara, bir sözlüğü ** ile adlı argümanlara dağıtabilirsin. Ayarları bir yapılandırma sözlüğünde tutup fonksiyona tek hamlede vermek böyle yapılır.

def fiyat(tutar, kdv=0.20, indirim=0):
    return round(tutar * (1 + kdv) - indirim, 2)

kampanya = {"kdv": 0.10, "indirim": 5}
print(fiyat(100, **kampanya))          # fiyat(100, kdv=0.10, indirim=5)
arg = [250, 0.01]
print(fiyat(*arg))                     # fiyat(250, 0.01)
try:
    fiyat(100, **{"kdv": 0.1, "kargo": 30})
except TypeError as h:
    print("Bilinmeyen ayar:", h)

Sözlükte fonksiyonun tanımadığı bir anahtar varsa TypeError alırsın; bu iyi bir şeydir, yazım hatalı ayar adlarını hemen yakalar.

Argüman nasıl aktarılır: kopya mı, referans mı?

Python argümanı ne kopyalar ne de "değişkenin kendisini" verir: parametre, çağıranın nesnesine bağlanan yeni bir addır (nesne referansıyla aktarım). Sonuç iki kurala iner: parametre üzerinden nesneyi değiştirirsen (append, sözlüğe atama) çağıran da görür; parametreye yeni bir değer atarsan yalnız yerel ad değişir, çağıran etkilenmez.

def ekle(sepet, urun):
    sepet.append(urun)        # nesneyi değiştirir: çağıran görür

def sifirla(sepet):
    sepet = []                # yalnız yerel adı yeniden bağlar

def bosalt(sepet):
    sepet.clear()             # nesneyi değiştirir

s = ["çay"]
ekle(s, "kahve"); print(s)
sifirla(s); print(s)          # değişmedi!
bosalt(s); print(s)
Gizli yan etkiBir fonksiyonun aldığı listeyi ya da sözlüğü sessizce değiştirmesi en zor bulunan hatalardandır. Kural: ya girdiyi değiştirme ve yeni nesne döndür (saf fonksiyon), ya da değiştiriyorsan adında ve docstring'inde belirt ve None döndür (list.sort() gibi).

Fonksiyon alan fonksiyonlar

Fonksiyonlar değer olduğu için başka bir fonksiyona argüman olarak verilebilir. sorted(..., key=...) bunun en bilinen örneği. Kendi fonksiyonların da "nasıl" kısmını dışarıdan alabilir; böylece aynı döngüyü farklı kurallarla yeniden kullanırsın. Bu tür fonksiyonlara üst düzey fonksiyon (higher-order function) denir.

def uygula(degerler, islem):
    sonuc = []
    for d in degerler:
        sonuc.append(islem(d))
    return sonuc

def kdv_ekle(f):
    return round(f * 1.2, 2)

print(uygula([100, 49.9], kdv_ekle))
print(uygula(["  izmir ", "ANKARA"], str.strip))
print(uygula([-3, 4], abs))

Tek satırlık küçük fonksiyonları yerinde tanımlamak için lambda, hazır map/filter ve fonksiyon döndüren fonksiyonlar (kapanış, closure) Fonksiyonel programlama ve Kapanışlar derslerinde; kendini çağıran fonksiyonlar ise Özyineleme (recursion) dersinde.

Gerçek işteKod incelemelerinde (code review) fonksiyonlara bakılan ilk şeyler: tek bir iş yapıyor mu, adı fiil mi (hesapla_, getir_, kaydet_), girdisini gizlice değiştiriyor mu, docstring'i ve tip ipuçları var mı, hata durumunda ne yapıyor? Küçük, saf ve belgelenmiş fonksiyonlar birim testle kolayca sınanır; Test yazma dersinde göreceğin assert tabanlı testler tam olarak bu fonksiyonlar üzerine kurulur.
Uzmanlaş

Yalnızca anahtar sözcüklü ve yalnızca konumsal parametreler

Python 3'te parametre listesine tek başına * koyarsan sonrasındaki parametreler yalnızca adla verilebilir: def indirim(fiyat, *, yuzde=0, kupon=0). Artık indirim(1000, 10) hata verir; indirim(1000, yuzde=10) yazmak zorunludur. Bu, yanlışlıkla yüzde yerine kupon göndermeyi imkânsız kılar. Tersi için / kullanılır: def f(a, b, /) yazımında a ve b yalnızca konumsaldır; len gibi yerleşik fonksiyonlar böyledir.

Tip ipuçları

Kurumsal kodda fonksiyon imzasına tip ipucu (type hint) eklenir: def kdv_hesapla(fiyat: float, oran: float = 0.20) -> float:. Python bunları zorlamaz ama editör ve mypy gibi araçlar hataları daha yazarken yakalar. Dataclass ve typing dersinde ayrıntıya gireceğiz; şimdilik imzaya tip yazmayı deneyebilirsin, zararı yoktur.

Fonksiyon bir değerdir

Python'da fonksiyon da bir nesnedir: değişkene atanır, listeye konur, başka fonksiyona argüman olarak verilir. hesaplar = {"kdv": kdv_hesapla, "indirim": indirim_uygula} gibi bir sözlükle "işlem adı → fonksiyon" tablosu kurabilirsin. Orta seviyedeki fonksiyonel programlama ve dekoratör (decorator) dersleri tamamen bu fikrin üstüne kurulur.

Alıştırmalara geç (21)