← Kursa Dön
📄 Text · 15 min

Fonksiyon Tanımlama ve Çağırma

Şimdiye kadar yazdığın tüm kodlar düz bir akışla, yukarıdan aşağı çalışıyordu. Ama gerçek dünyada aynı işlemi defalarca yapman gerekir. Her seferinde aynı 10 satırı mı kopyalayacaksın? Tabii ki hayır. İşte fonksiyonlar tam da bu sorunu çözmek için var.

Bu derste Python'da fonksiyon tanımlamayı, çağırmayı, değer döndürmeyi ve fonksiyonların neden programlamanın en temel yapı taşı olduğunu öğreneceksin.


Fonksiyon Nedir?

Bir fonksiyon, belirli bir işi yapan, isimlendirilmiş kod bloğudur. Bir kere yazarsın, istediğin kadar çağırırsın. Bu kadar basit.

Tarif analojisi düşün: Bir kek tarifi yazarsın — malzemeler, adımlar, sonuç. Her kek yapmak istediğinde tarifi sıfırdan icat etmezsin. Tarifi açar, adımları takip eder, sonucu alırsın. Fonksiyonlar da aynen böyle çalışır.

Tarif = fonksiyon tanımı (def ile yazarsın) Malzemeler = parametreler (fonksiyona verdiğin veriler) Sonuç = return değeri (fonksiyonun sana geri verdiği şey) Pişirme işlemi = fonksiyon çağrısı (tarifi uygulamak)

Her seferinde aynı keki farklı malzemelerle yapabilirsin — çilekli, çikolatalı, muzlu. Fonksiyon aynı kalır, parametreler değişir.

Neden Fonksiyon Kullanırız?

Fonksiyon kullanmanın birkaç kritik nedeni var:

1. Tekrar kullanılabilirlik (Reusability): Aynı kodu bir kere yaz, yüzlerce kere çağır. Kopyala-yapıştır yapmana gerek yok.

2. Okunabilirlik (Readability): hesapla_vergi(gelir) yazan bir satır, 15 satırlık vergi hesaplama kodundan çok daha anlaşılır.

3. Bakım kolaylığı (Maintainability): Vergi oranı değişti mi? Tek bir yerde güncelle, tüm program güncellenir. 47 farklı yerde aynı değişikliği yapmak zorunda kalmazsın.

4. Test edilebilirlik (Testability): Küçük, bağımsız fonksiyonları test etmek, dev bir kod yığınını test etmekten çok daha kolay.


def ile Fonksiyon Tanımlama

Python'da fonksiyon tanımlamak için def anahtar kelimesini kullanırsın. Söz dizimi şöyle:

def fonksiyon_adi(parametreler):
    """Fonksiyonun ne yaptığını açıklayan docstring."""
    # Fonksiyonun gövdesi
    # Burada işlemler yapılır
    return sonuc

Hadi basit bir örnekle başlayalım:

def selamla(isim):
    """Verilen isme özel bir selamlama mesajı döndürür."""
    mesaj = f"Merhaba, {isim}! Hoş geldin."
    return mesaj

# Fonksiyonu çağıralım
sonuc = selamla("Ayşe")
print(sonuc)  # Merhaba, Ayşe! Hoş geldin.

# Farklı parametrelerle tekrar çağıralım
print(selamla("Mehmet"))  # Merhaba, Mehmet! Hoş geldin.
print(selamla("Zeynep"))  # Merhaba, Zeynep! Hoş geldin.

Gördün mü? Bir kere tanımladık, üç kere çağırdık. Her seferinde farklı isimle.

Fonksiyon İsimlendirme Kuralları

Python'da fonksiyon isimlendirmek için bazı kurallar ve gelenekler var:

# ✅ Doğru: snake_case kullan (kelimeler alt çizgi ile ayrılır)
def hesapla_toplam(sayilar):
    pass

def kullanici_olustur(ad, soyad):
    pass

def dosya_oku(dosya_yolu):
    pass

# ❌ Yanlış: camelCase Python'da kullanılmaz (Java değiliz!)
def hesaplaOrtalama(sayilar):  # Çalışır ama Pythonic değil
    pass

# ❌ Yanlış: Büyük harfle başlama (bu class isimleri için)
def HesaplaVergi(gelir):  # Class sanılır
    pass

Fonksiyon isimleri fiil ile başlamalı çünkü fonksiyonlar bir yapar: hesapla_, bul_, olustur_, kontrol_et_, donustur_ gibi.

Fonksiyonun Anatomisi

Bir fonksiyonu parçalarına ayıralım:

def daire_alani(yaricap):          # 1. def + isim + parametreler + :
    """Dairenin alanını hesaplar.   # 2. Docstring (opsiyonel ama önerilir)
    
    Args:
        yaricap: Dairenin yarıçapı
    
    Returns:
        Dairenin alanı (float)
    """
    pi = 3.14159                    # 3. Fonksiyon gövdesi
    alan = pi * yaricap ** 2        # 4. İşlemler
    return alan                     # 5. Return (sonucu döndür)

# Kullanım
sonuc = daire_alani(5)
print(f"Alan: {sonuc:.2f}")  # Alan: 78.54

def satırı "bu fonksiyonu tanımlıyorum" demek. Python bu satırı gördüğünde fonksiyonu çalıştırmaz, sadece hafızaya kaydeder. Fonksiyon ancak çağrıldığında çalışır.


Fonksiyon Çağırma ve return

Fonksiyonu tanımlamak yetmez, bir de çağırman gerekir. Çağırmak için fonksiyon adını yazıp parantez içinde argümanları verirsin.

def kare_al(sayi):
    """Bir sayının karesini döndürür."""
    return sayi ** 2

# Çağırma yolları
sonuc = kare_al(7)       # Sonucu bir değişkene ata
print(sonuc)              # 49

print(kare_al(3))         # Doğrudan print içinde çağır → 9

# Başka bir ifadenin içinde kullan
toplam = kare_al(3) + kare_al(4)
print(toplam)             # 9 + 16 = 25

return Ne İşe Yarar?

return ifadesi fonksiyonun "sonucunu" dışarı gönderir. Tarif analojimize dönersek: return mutfaktan çıkan kektir. Tarifi uyguladın, sonuç ortaya çıktı.

def topla(a, b):
    return a + b  # Sonucu döndür

sonuc = topla(3, 5)  # sonuc = 8

Önemli: return çalıştığı anda fonksiyon biter. return'den sonraki satırlar çalışmaz:

def ornek():
    print("Bu çalışır")
    return 42
    print("Bu ASLA çalışmaz!")  # Dead code — ulaşılamaz

sonuc = ornek()
# Çıktı: Bu çalışır
# sonuc = 42

return Olmadan Ne Olur?

Eğer bir fonksiyonda return yazmazsan, fonksiyon otomatik olarak None döndürür:

def sadece_yazdir(mesaj):
    print(mesaj)
    # return yok!

sonuc = sadece_yazdir("Merhaba")
# Çıktı: Merhaba
print(sonuc)   # None
print(type(sonuc))  # <class 'NoneType'>

Bu fark kritik. print() ve return aynı şey değil:

  • print() ekrana yazar, ama fonksiyon dışına bir değer göndermez.

  • return fonksiyonun çağrıldığı yere bir değer gönderir.

# ❌ Yaygın hata: print ile return karıştırmak
def yanlis_toplama(a, b):
    print(a + b)  # Ekrana yazıyor ama döndürmüyor!

def dogru_toplama(a, b):
    return a + b  # Değeri döndürüyor

# Farkı görelim
sonuc1 = yanlis_toplama(3, 5)  # Ekrana 8 yazar
print(sonuc1)  # None! Çünkü return yok

sonuc2 = dogru_toplama(3, 5)   # Sessiz, ama değer döndü
print(sonuc2)  # 8

💡 İpucu: Fonksiyon bir hesaplama yapıyorsa return kullan. Fonksiyon bir yan etki yapıyorsa (dosyaya yazma, ekrana basma) return olmayabilir. Ama genelde return kullanmak daha iyi bir pratiktir.

Koşullu Return

Fonksiyon içinde birden fazla return olabilir. Hangisine ulaşılırsa fonksiyon orada biter:

def mutlak_deger(sayi):
    """Bir sayının mutlak değerini döndürür."""
    if sayi >= 0:
        return sayi
    else:
        return -sayi

print(mutlak_deger(5))    # 5
print(mutlak_deger(-3))   # 3
print(mutlak_deger(0))    # 0

Daha temiz bir versiyon — early return pattern:

def notu_hesapla(puan):
    """Puana göre harf notu döndürür."""
    if puan >= 90:
        return "AA"
    if puan >= 80:
        return "BA"
    if puan >= 70:
        return "BB"
    if puan >= 60:
        return "CB"
    return "FF"  # Hiçbir koşul tutmadıysa

print(notu_hesapla(95))   # AA
print(notu_hesapla(73))   # BB
print(notu_hesapla(45))   # FF

else yazmaya bile gerek yok çünkü return fonksiyonu sonlandırıyor. Bu pattern kodun okunabilirliğini artırır.


Birden Fazla Değer Döndürme (Tuple Return)

Python'da bir fonksiyon birden fazla değer döndürebilir. Aslında teknik olarak bir tuple döndürürsün, ama Python bunu çok doğal hissettiriyor:

def bolme_islemi(bolunen, bolen):
    """Bölüm ve kalanı birlikte döndürür."""
    bolum = bolunen // bolen
    kalan = bolunen % bolen
    return bolum, kalan  # Aslında bir tuple: (bolum, kalan)

# Tuple unpacking ile al
bolum, kalan = bolme_islemi(17, 5)
print(f"17 ÷ 5 = {bolum}, kalan {kalan}")  # 17 ÷ 5 = 3, kalan 2

# Tek değişkene de atayabilirsin (tuple olarak)
sonuc = bolme_islemi(17, 5)
print(sonuc)        # (3, 2)
print(sonuc[0])     # 3 (bölüm)
print(sonuc[1])     # 2 (kalan)

Bu özellik çok kullanışlı. Birkaç pratik örnek:

def istatistik(sayilar):
    """Bir sayı listesinin min, max ve ortalamasını döndürür."""
    en_kucuk = min(sayilar)
    en_buyuk = max(sayilar)
    ortalama = sum(sayilar) / len(sayilar)
    return en_kucuk, en_buyuk, ortalama

notlar = [70, 85, 90, 65, 78, 92]
minimum, maksimum, ort = istatistik(notlar)
print(f"Min: {minimum}, Max: {maksimum}, Ort: {ort:.1f}")
# Min: 65, Max: 92, Ort: 80.0
def ad_soyad_ayir(tam_ad):
    """Tam adı ad ve soyad olarak ayırır."""
    parcalar = tam_ad.strip().split()
    ad = parcalar[0]
    soyad = parcalar[-1] if len(parcalar) > 1 else ""
    return ad, soyad

ad, soyad = ad_soyad_ayir("Ahmet Yılmaz")
print(f"Ad: {ad}, Soyad: {soyad}")  # Ad: Ahmet, Soyad: Yılmaz

⚠️ Dikkat: Çok fazla değer döndürmek (5-6 tane) kodun okunabilirliğini bozar. 3'ten fazla değer döndüreceksen, bir dictionary veya dataclass kullanmayı düşün.

# ❌ Çok fazla return değeri — hangisi hangisi?
def kullanici_bilgi():
    return "Ahmet", "Yılmaz", 30, "İstanbul", "ahmet@mail.com", True

# ✅ Bunun yerine dictionary kullan
def kullanici_bilgi():
    return {
        "ad": "Ahmet",
        "soyad": "Yılmaz",
        "yas": 30,
        "sehir": "İstanbul",
        "email": "ahmet@mail.com",
        "aktif": True
    }

Docstring: Fonksiyonunu Belgele

Docstring, fonksiyonun hemen altına yazılan üçlü tırnak ("""...""") içindeki açıklama metnidir. Bu sadece bir yorum değil — Python bunu fonksiyonun __doc__ attribute'una kaydeder.

def fibonacci(n):
    """N'inci Fibonacci sayısını döndürür.
    
    Fibonacci dizisi: 0, 1, 1, 2, 3, 5, 8, 13, 21, ...
    Her sayı kendinden önceki iki sayının toplamıdır.
    
    Args:
        n (int): Kaçıncı Fibonacci sayısı isteniyor (0-indexed)
    
    Returns:
        int: N'inci Fibonacci sayısı
    
    Raises:
        ValueError: n negatifse
    
    Examples:
        >>> fibonacci(0)
        0
        >>> fibonacci(6)
        8
    """
    if n < 0:
        raise ValueError("n negatif olamaz")
    if n <= 1:
        return n
    
    a, b = 0, 1
    for _ in range(2, n + 1):
        a, b = b, a + b
    return b

Docstring Stilleri

Üç yaygın docstring stili var:

Google Style (en yaygın ve okunaklı):

def hesapla_kdv(fiyat, oran=0.18):
    """Fiyata KDV ekleyerek toplam tutarı hesaplar.
    
    Args:
        fiyat (float): KDV hariç fiyat
        oran (float): KDV oranı (varsayılan: 0.18)
    
    Returns:
        float: KDV dahil toplam fiyat
    """
    return fiyat * (1 + oran)

Tek satırlık docstring (basit fonksiyonlar için):

def kare(x):
    """Verilen sayının karesini döndürür."""
    return x ** 2

Docstring yazma alışkanlığı edin. Gelecekteki sen, bu fonksiyonu 3 ay sonra gördüğünde sana teşekkür edecek.


help() ile Fonksiyon Belgeleme

Docstring yazdığında help() fonksiyonu ile o belgeye erişebilirsin:

def bmi_hesapla(kilo, boy):
    """Vücut kitle indeksini (BMI) hesaplar.
    
    Args:
        kilo (float): Kilogram cinsinden ağırlık
        boy (float): Metre cinsinden boy
    
    Returns:
        float: BMI değeri
    
    Examples:
        >>> bmi_hesapla(70, 1.75)
        22.857142857142858
    """
    return kilo / (boy ** 2)

# help() ile bilgi al
help(bmi_hesapla)

Bu komut şöyle bir çıktı verir:

Help on function bmi_hesapla in module __main__:

bmi_hesapla(kilo, boy)
    Vücut kitle indeksini (BMI) hesaplar.
    
    Args:
        kilo (float): Kilogram cinsinden ağırlık
        boy (float): Metre cinsinden boy
    ...

Ayrıca __doc__ attribute'u ile doğrudan docstring'e erişebilirsin:

print(bmi_hesapla.__doc__)

Python'un kendi fonksiyonlarında da help() çalışır:

help(len)     # len() fonksiyonunun belgesi
help(print)   # print() fonksiyonunun belgesi
help(str)     # str sınıfının belgesi

Profesyonel projelerde docstring'ler otomatik dokümantasyon araçları (Sphinx, MkDocs) tarafından okunarak web sayfalarına dönüştürülür. Yani iyi docstring yazmak sadece kendin için değil, tüm ekip için faydalı.


Fonksiyon = First-Class Object

Python'da fonksiyonlar birinci sınıf nesnelerdir (first-class objects). Bu ne demek? Fonksiyonları tıpkı sayılar, stringler veya listeler gibi kullanabilirsin:

  1. Bir değişkene atayabilirsin

  2. Başka bir fonksiyona parametre olarak geçebilirsin

  3. Bir fonksiyondan return edebilirsin

  4. Bir listeye veya dictionary'ye koyabilirsin

Bu kavram dekoratörler ve fonksiyonel programlama için temel oluşturur.

Değişkene Atama

def selamla(isim):
    return f"Merhaba, {isim}!"

# Fonksiyonu bir değişkene ata (parantez YOK!)
selam_fonksiyonu = selamla

# Artık her iki isimle de çağırabilirsin
print(selamla("Ali"))           # Merhaba, Ali!
print(selam_fonksiyonu("Ali"))  # Merhaba, Ali!

# Aynı fonksiyon nesnesi mi?
print(selamla is selam_fonksiyonu)  # True

⚠️ Dikkat: selamla (parantezsiz) fonksiyonun kendisi, selamla() (parantezli) fonksiyonun çağrılmış hali. Bu fark çok önemli!

# Parantez farkı
print(selamla)        # <function selamla at 0x...>  → Fonksiyon nesnesi
print(selamla("X"))   # Merhaba, X!  → Fonksiyon çağrısının sonucu

Fonksiyonu Parametre Olarak Geçme

def uygula(fonksiyon, deger):
    """Verilen fonksiyonu verilen değere uygular."""
    return fonksiyon(deger)

def kare(x):
    return x ** 2

def kup(x):
    return x ** 3

print(uygula(kare, 5))   # 25
print(uygula(kup, 5))    # 125

Bu pattern Python'un map(), filter(), sorted() gibi built-in fonksiyonlarında sürekli kullanılır. Lambda dersinde çok daha detaylı göreceksin.

Fonksiyonu Koleksiyonlarda Tutma

def topla(a, b):
    return a + b

def cikar(a, b):
    return a - b

def carp(a, b):
    return a * b

def bol(a, b):
    return a / b if b != 0 else "Sıfıra bölünemez!"

# Fonksiyonları bir dictionary'de tut
islemler = {
    "+": topla,
    "-": cikar,
    "*": carp,
    "/": bol
}

# Kullanıcıdan operatör al ve çalıştır
operator = "+"
sonuc = islemler[operator](10, 3)
print(f"10 {operator} 3 = {sonuc}")  # 10 + 3 = 13

# Tüm işlemleri dene
for op, fonk in islemler.items():
    print(f"10 {op} 3 = {fonk(10, 3)}")

Çıktı:

10 + 3 = 13
10 - 3 = 7
10 * 3 = 30
10 / 3 = 3.3333333333333335

Bu, uzun if-elif-elif zincirlerinden çok daha temiz bir yaklaşım. Bu tekniğe dispatch dictionary denir ve gerçek projelerde çok kullanılır.


pass ile Boş Fonksiyon

Bazen bir fonksiyonu tanımlaman gerekir ama içini henüz yazmamışsındır. Python'da boş bir fonksiyon gövdesi syntax error verir çünkü Python indentation bekler.

İşte pass burada devreye girer:

def ileride_yazilacak():
    pass  # Şimdilik boş, sonra dolduracağım

# Bu fonksiyon çağrılabilir, hiçbir şey yapmaz
ileride_yazilacak()  # Hata vermez, sadece hiçbir şey olmaz

pass bir "yer tutucu" (placeholder) dur. "Burada bir şey olacak ama henüz yazmadım" anlamına gelir.

Ne zaman kullanılır?

# 1. Kod iskeletini oluştururken
def veritabani_baglan():
    pass

def veri_cek():
    pass

def veri_isle():
    pass

def rapor_olustur():
    pass

# 2. Henüz implement edilmemiş fonksiyonlar
def gelismis_arama(query, filtreler):
    pass  # TODO: Elasticsearch entegrasyonu yapılacak

# 3. Alternatif: Ellipsis (...) de kullanılabilir
def baska_fonksiyon():
    ...  # pass ile aynı etkiyi yapar, daha modern

💡 İpucu: Gerçek projelerde pass yerine raise NotImplementedError("Henüz yazılmadı") kullanmak daha iyi olabilir. Böylece yanlışlıkla çağrılırsa sessizce geçmek yerine hata verir.

def onemli_fonksiyon():
    raise NotImplementedError("Bu fonksiyon henüz implement edilmedi!")

# onemli_fonksiyon()  # NotImplementedError fırlatır

Recursive vs Iterative: Kısa Karşılaştırma

Bazı problemler "kendini tekrar eden" yapıdadır. Mesela faktöriyel: 5! = 5 × 4 × 3 × 2 × 1. Burada 5! = 5 × 4! diyebiliriz — yani problem kendisinin daha küçük bir versiyonuna indirgenir.

Recursive (Özyinelemeli) Yaklaşım

Bir fonksiyon kendini çağırdığında buna recursion denir:

def faktoriyel_recursive(n):
    """Faktöriyeli recursive olarak hesaplar."""
    if n <= 1:          # Base case: durma koşulu
        return 1
    return n * faktoriyel_recursive(n - 1)  # Kendini çağırıyor

print(faktoriyel_recursive(5))  # 120
# 5 * faktoriyel(4)
#     4 * faktoriyel(3)
#         3 * faktoriyel(2)
#             2 * faktoriyel(1)
#                 1  ← base case, geri dönmeye başla

Iterative (Döngüsel) Yaklaşım

Aynı işi bir döngüyle de yapabilirsin:

def faktoriyel_iterative(n):
    """Faktöriyeli iterative olarak hesaplar."""
    sonuc = 1
    for i in range(2, n + 1):
        sonuc *= i
    return sonuc

print(faktoriyel_iterative(5))  # 120

Hangisini Seçmeli?

ÖzellikRecursiveIterative
OkunabilirlikBazı problemlerde daha doğalGenelde daha anlaşılır
PerformansDaha yavaş (fonksiyon çağrı maliyeti)Genelde daha hızlı
BellekStack overflow riski (derin recursion)Sabit bellek kullanımı
UygunlukAğaç yapıları, bölümleme problemleriBasit tekrarlı işlemler

Bir başka klasik örnek — Fibonacci:

# Recursive (naif — çok yavaş!)
def fib_recursive(n):
    if n <= 1:
        return n
    return fib_recursive(n - 1) + fib_recursive(n - 2)

# Iterative (çok daha hızlı)
def fib_iterative(n):
    if n <= 1:
        return n
    a, b = 0, 1
    for _ in range(2, n + 1):
        a, b = b, a + b
    return b

# Küçük sayılarda fark yok
print(fib_recursive(10))   # 55
print(fib_iterative(10))   # 55

# Ama büyük sayılarda recursive version çok yavaşlar
# fib_recursive(40) → dakikalar sürer
# fib_iterative(40) → anında biter

⚠️ Dikkat: Python'da varsayılan recursion limiti 1000'dir. Çok derin recursion RecursionError verir. sys.setrecursionlimit() ile artırabilirsin ama bu genelde kötü bir çözümdür. Derin recursion gerekiyorsa iterative çözümü tercih et.

import sys
print(sys.getrecursionlimit())  # 1000

# Çok derin recursion
def geri_sayim(n):
    if n <= 0:
        return "Bitti!"
    return geri_sayim(n - 1)

# geri_sayim(5000)  # RecursionError: maximum recursion depth exceeded

Genel kural: Basit tekrarlı işlemler için iterative, doğası gereği recursive olan problemler (ağaç gezme, dosya sistemi tarama) için recursive kullan. Ve recursive yazıyorsan mutlaka bir base case (durma koşulu) koy — yoksa sonsuz döngüye girersin!


Fonksiyonlar Hakkında Ek Bilgiler

Fonksiyonun Type'ı

Her fonksiyon bir function nesnesidir:

def merhaba():
    return "Merhaba!"

print(type(merhaba))  # <class 'function'>

Fonksiyonun Attribute'ları

Fonksiyonların birçok özel attribute'u var:

def ornek_fonksiyon(x, y):
    """Bu bir örnek fonksiyondur."""
    return x + y

print(ornek_fonksiyon.__name__)    # ornek_fonksiyon
print(ornek_fonksiyon.__doc__)     # Bu bir örnek fonksiyondur.
print(ornek_fonksiyon.__module__)  # __main__

Fonksiyon İçinde Fonksiyon (Nested Functions)

Bir fonksiyonun içinde başka fonksiyonlar tanımlayabilirsin:

def dis_fonksiyon(x):
    """Dış fonksiyon."""
    
    def ic_fonksiyon(y):
        """İç fonksiyon — sadece dış fonksiyonun içinde erişilebilir."""
        return y ** 2
    
    return ic_fonksiyon(x) + 10

print(dis_fonksiyon(5))  # 35 (25 + 10)
# ic_fonksiyon(5)  # NameError! Dışarıdan erişilemez

Nested fonksiyonlar closure ve decorator konularının temelidir. Scope dersinde çok daha detaylı göreceğiz.

None Döndüren Fonksiyonlar: Side Effect Fonksiyonları

Bazı fonksiyonlar bir değer döndürmez, bir işlem yapar:

def log_yaz(mesaj, seviye="INFO"):
    """Konsola log mesajı yazar."""
    print(f"[{seviye}] {mesaj}")

# Bu fonksiyonun amacı bir değer döndürmek değil,
# ekrana yazmak (side effect)
log_yaz("Uygulama başlatıldı")
log_yaz("Bağlantı hatası!", "ERROR")

Bu tür fonksiyonlara side effect fonksiyonları denir. Bir değer hesaplayıp döndürmek yerine dış dünyayı değiştirirler (ekrana yazma, dosyaya kaydetme, veritabanını güncelleme vb.).


Pratik: Mini Hesap Makinesi

Öğrendiklerimizi birleştirelim:

def hesap_makinesi(sayi1, sayi2, islem):
    """Basit bir hesap makinesi.
    
    Args:
        sayi1 (float): İlk sayı
        sayi2 (float): İkinci sayı
        islem (str): Yapılacak işlem (+, -, *, /)
    
    Returns:
        float: İşlemin sonucu
        None: Geçersiz işlem durumunda
    """
    islemler = {
        "+": lambda a, b: a + b,
        "-": lambda a, b: a - b,
        "*": lambda a, b: a * b,
        "/": lambda a, b: a / b if b != 0 else "Hata: Sıfıra bölünemez!"
    }
    
    if islem not in islemler:
        print(f"Geçersiz işlem: {islem}")
        return None
    
    return islemler[islem](sayi1, sayi2)

# Kullanım
print(hesap_makinesi(10, 3, "+"))   # 13
print(hesap_makinesi(10, 3, "-"))   # 7
print(hesap_makinesi(10, 3, "*"))   # 30
print(hesap_makinesi(10, 3, "/"))   # 3.3333...
print(hesap_makinesi(10, 0, "/"))   # Hata: Sıfıra bölünemez!
print(hesap_makinesi(10, 3, "^"))   # Geçersiz işlem: ^ → None

Özet

  • Fonksiyon, belirli bir işi yapan, isimlendirilmiş, tekrar kullanılabilir kod bloğudur. def keyword'ü ile tanımlanır.

  • `return` fonksiyonun sonucunu dışarı gönderir. return olmayan fonksiyonlar None döndürür. print() ile return farklı şeylerdir.

  • Birden fazla değer döndürmek için tuple return kullanılır: return a, b, c. Çağıran tarafta unpacking ile alınır.

  • Docstring ("""...""") fonksiyonun ne yaptığını belgeler. help() ile okunabilir. Her fonksiyona docstring yazmayı alışkanlık edin.

  • Fonksiyonlar first-class object'tir — değişkene atanabilir, parametre olarak geçilebilir, koleksiyonlarda tutulabilir. Bu özellik dekoratörler ve fonksiyonel programlamanın temelidir.

  • Recursion, fonksiyonun kendini çağırmasıdır. Her zaman bir base case (durma koşulu) olmalıdır. Basit işlemler için iterative yaklaşım genellikle daha performanslıdır.