🐍 0.9: Python Fonksiyonlar

Fonksiyon Tanımlama, Parametreler ve Return Değerleri

📖 Fonksiyon Nedir?

Fonksiyon, belirli bir görevi yerine getiren, yeniden kullanılabilir kod bloklarıdır. Fonksiyonlar sayesinde kodlarımızı daha organize, okunabilir ve bakımı kolay hale getiririz.

🤔 Neden Fonksiyon Kullanmalıyız?

Şu senaryoyu düşünün: Bir programda 5 farklı yerde alan hesaplaması yapmanız gerekiyor...

❌ FONKSİYONSUZ (Kötü)

# 1. hesaplama
en1, boy1 = 5, 10
alan1 = en1 * boy1
print(f"Alan: {alan1}")

# 2. hesaplama (aynı kod tekrar!)
en2, boy2 = 8, 12
alan2 = en2 * boy2
print(f"Alan: {alan2}")

# 3. hesaplama (yine aynı kod!)
en3, boy3 = 3, 7
alan3 = en3 * boy3
print(f"Alan: {alan3}")

# 5 yer daha... 🤦

⚠️ Sorunlar:

  • Aynı kod 5 kez tekrarlanıyor
  • Formülü değiştirmek için 5 yeri bulmak lazım
  • Bir yerde hata yaparsak diğerleri doğru, karışıklık!

✅ FONKSİYONLU (İyi)

def alan_hesapla(en, boy):
    """Dikdörtgen alanı hesaplar"""
    return en * boy

# Şimdi her yerde kullan!
print(f"Alan: {alan_hesapla(5, 10)}")
print(f"Alan: {alan_hesapla(8, 12)}")
print(f"Alan: {alan_hesapla(3, 7)}")

# Formül değişirse?
# Sadece fonksiyonu düzelt!
# Tüm programda otomatik düzelir ✨

✅ Avantajlar:

  • Kod bir kez yazıldı, istediğin kadar kullan
  • Değişiklik tek yerden yapılır
  • Okunması ve anlaması çok kolay
🧠 DRY Prensibi: "Don't Repeat Yourself"

Programlamadaki en önemli prensiplerden biri: "Kendini Tekrarlama!"

📌 Kural: Aynı kodu 2 kez yazdığını fark edersen, o bir fonksiyon olmalı!

Bu prensip sayesinde:

  • Daha az kod yazarsın (daha az hata şansı)
  • Bakımı kolay kod elde edersin
  • Profesyonel programcılar gibi düşünürsün
🎯 Fonksiyonların Faydaları:
  • Kod Tekrarını Önler: Aynı işlemi farklı yerlerde kullanmak için kodu kopyalamaya gerek kalmaz
  • Modüler Yapı: Büyük programları küçük parçalara böleriz
  • Kolay Bakım: Bir değişikliği sadece fonksiyon içinde yaparız
  • Okunabilirlik: Kod daha anlaşılır ve düzenli olur
  • Test Edilebilirlik: Her fonksiyonu ayrı ayrı test edebiliriz

🧩 Modülerlik: Büyük Problemi Küçük Parçalara Böl

graph TD A["🎯 BÜYÜK PROGRAM
Öğrenci Not Sistemi"] --> B["📝 ogrenci_ekle()"] A --> C["📊 ortalama_hesapla()"] A --> D["🔍 ogrenci_bul()"] A --> E["📋 rapor_olustur()"] A --> F["💾 dosyaya_kaydet()"] B --> B1["Ad, soyad, numara al"] C --> C1["Notları topla, böl"] D --> D1["Numara ile ara"] E --> E1["Tüm bilgileri formatla"] F --> F1["Dosyaya yaz"] style A fill:#667eea,color:#fff,stroke:#764ba2,stroke-width:3px style B fill:#28a745,color:#fff style C fill:#28a745,color:#fff style D fill:#28a745,color:#fff style E fill:#28a745,color:#fff style F fill:#28a745,color:#fff

Her fonksiyon tek bir iş yapar → Anlaması kolay, test etmesi kolay, değiştirmesi kolay!

📊 Fonksiyon Anatomisi

def fonksiyon_adi(parametre1, parametre2):
    """Docstring - fonksiyonun açıklaması"""
    # Fonksiyon gövdesi
    sonuc = parametre1 + parametre2
    return sonuc
🔑 def

Fonksiyon tanımlama anahtar kelimesi

📛 İsim

Fonksiyonun benzersiz adı

📥 Parametreler

Fonksiyona gönderilen değişkenler

📤 Return

Fonksiyonun döndürdüğü değer

⚡ Önemli Terimler:
Parametre: Fonksiyon tanımında parantez içinde belirtilen değişkenler
Argüman: Fonksiyonu çağırırken gönderdiğimiz gerçek değerler
Return: Fonksiyonun geri döndürdüğü değer
def: Fonksiyon tanımlamak için kullanılan anahtar kelime

🔧 Fonksiyon Tanımlama ve Çağırma

Basit Bir Fonksiyon

En basit haliyle, hiç parametre almayan ve değer döndürmeyen bir fonksiyon:

🐍 Python Kodu
def selamla():
    """Bu fonksiyon basit bir selamlama yapar"""
    print("Merhaba Dünya!")
    print("Python öğreniyorum!")

# Fonksiyonu çağırma
selamla()
print("---")
selamla()  # Birden fazla kez çağırabiliriz

Parametreli Fonksiyonlar

Fonksiyonlara dışarıdan veri göndererek daha esnek hale getirebiliriz:

🐍 Python Kodu
def selamla_kisi(isim):
    """Belirtilen kişiye selamlar"""
    print(f"Merhaba {isim}!")
    print(f"{isim}, nasılsın?")

# Farklı argümanlarla çağırma
selamla_kisi("Ahmet")
print("---")
selamla_kisi("Ayşe")
print("---")
selamla_kisi("Mehmet")

Return ile Değer Döndürme

return anahtar kelimesi, fonksiyonun bir değer döndürmesini sağlar:

flowchart LR A[Fonksiyon Çağırma] --> B[Fonksiyon Çalışır] B --> C{Return var mı?} C -->|Evet| D[Değer Döndür] C -->|Hayır| E[None Döndür] D --> F[Değer Kullanılabilir] E --> F style A fill:#667eea,color:#fff style D fill:#28a745,color:#fff style E fill:#ffc107,color:#333
🐍 Python Kodu
def topla(a, b):
    """İki sayıyı toplar ve sonucu döndürür"""
    sonuc = a + b
    return sonuc

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

# Return değerlerini kullanma
toplam = topla(15, 25)
print(f"Toplam: {toplam}")

kare = kare_al(7)
print(f"7'nin karesi: {kare}")

# Return değerini doğrudan kullanma
print(f"5 + 8 = {topla(5, 8)}")
print(f"12'nin karesi: {kare_al(12)}")

# Return değerini başka işlemlerde kullanma
sonuc = topla(10, 20) + kare_al(5)
print(f"10+20 + 5^2 = {sonuc}")
⚠️ Dikkat: return olmadan bir fonksiyon None döndürür. print() ile return farklıdır! print() sadece ekrana yazar, return ise değeri fonksiyondan çıkartır.

🎨 Örnek: Dizide Minimum Bulma

📋 Flowchart (Akış Şeması)

flowchart TD A([BAŞLA]) --> B[liste parametresini al] B --> C[min = liste'#91;0'#93;] C --> D[i = 0] D --> E{i < liste uzunluğu?} E -->|Evet| F{liste'#91;i'#93; < min?} F -->|Evet| G[min = liste'#91;i'#93;] F -->|Hayır| H[i = i + 1] G --> H H --> E E -->|Hayır| I[return min] I --> J([BİTİR]) style A fill:#667eea,color:#fff style J fill:#667eea,color:#fff style I fill:#28a745,color:#fff style G fill:#f093fb,color:#fff style E fill:#ffc107,color:#333 style F fill:#17a2b8,color:#fff

📝 Pseudocode (Sözde Kod)

Pseudocode
FUNCTION min_bul(liste):
    min ← liste[0]

    FOR her eleman IN liste:
        IF eleman < min:
            min ← eleman
        END IF
    END FOR

    RETURN min
END FUNCTION

// Kullanım
sayilar ← [45, 23, 67, 12, 89]
sonuc ← min_bul(sayilar)
PRINT sonuc

💪 Daha Fazla Algoritma Örnekleri

Ortalama Hesaplama

🐍 Python Kodu
def ortalama_hesapla(liste):
    """Listedeki sayıların ortalamasını hesaplar"""
    if len(liste) == 0:
        return 0

    toplam = 0
    for sayi in liste:
        toplam += sayi

    ortalama = toplam / len(liste)
    return ortalama

# Test
notlar1 = [85, 90, 78, 92, 88]
notlar2 = [60, 70, 65, 80, 75]

print(f"Notlar 1: {notlar1}")
print(f"Ortalama: {ortalama_hesapla(notlar1):.2f}")
print()
print(f"Notlar 2: {notlar2}")
print(f"Ortalama: {ortalama_hesapla(notlar2):.2f}")

Linear Search (Doğrusal Arama)

flowchart TD A([BAŞLA]) --> B[liste, aranan al] B --> C[i = 0] C --> D{i < liste uzunluğu?} D -->|Evet| E{liste i == aranan?} E -->|Evet| F[return i] E -->|Hayır| G[i = i + 1] G --> D D -->|Hayır| H[return -1] F --> I([BİTİR]) H --> I style A fill:#667eea,color:#fff style I fill:#667eea,color:#fff style F fill:#28a745,color:#fff style H fill:#dc3545,color:#fff
🐍 Python Kodu
def linear_search(liste, aranan):
    """
    Listedeki bir elemanı arar ve indeksini döndürür.
    Bulunamazsa -1 döndürür.
    """
    for i in range(len(liste)):
        if liste[i] == aranan:
            return i  # Bulundu, indeksini döndür

    return -1  # Bulunamadı

# Test
isimler = ["Ali", "Ayşe", "Mehmet", "Fatma", "Ahmet"]
sayilar = [10, 25, 30, 45, 50, 75]

print(f"İsimler: {isimler}")
print(f"'Mehmet' aranıyor...")
indeks = linear_search(isimler, "Mehmet")
if indeks != -1:
    print(f"Bulundu! İndeks: {indeks}")
else:
    print("Bulunamadı!")

print()
print(f"'Zeynep' aranıyor...")
indeks = linear_search(isimler, "Zeynep")
if indeks != -1:
    print(f"Bulundu! İndeks: {indeks}")
else:
    print("Bulunamadı!")

print()
print(f"Sayılar: {sayilar}")
print(f"45 aranıyor: İndeks = {linear_search(sayilar, 45)}")
print(f"100 aranıyor: İndeks = {linear_search(sayilar, 100)}")

Asal Sayı Kontrolü

🐍 Python Kodu
def asal_mi(sayi):
    """
    Bir sayının asal olup olmadığını kontrol eder
    Return: True (asal) veya False (asal değil)
    """
    if sayi < 2:
        return False

    if sayi == 2:
        return True

    if sayi % 2 == 0:
        return False

    # 3'ten başlayarak sayının kareköküne kadar kontrol et
    for i in range(3, int(sayi**0.5) + 1, 2):
        if sayi % i == 0:
            return False

    return True

def asal_sayilari_listele(baslangic, bitis):
    """Verilen aralıktaki asal sayıları listeler"""
    asal_liste = []
    for sayi in range(baslangic, bitis + 1):
        if asal_mi(sayi):
            asal_liste.append(sayi)
    return asal_liste

# Test
print("Asal mı kontrolleri:")
test_sayilar = [2, 3, 4, 5, 10, 17, 19, 20, 23, 25]
for sayi in test_sayilar:
    sonuc = "ASAL" if asal_mi(sayi) else "ASAL DEĞİL"
    print(f"{sayi}: {sonuc}")

print("\n1-50 arası asal sayılar:")
asallar = asal_sayilari_listele(1, 50)
print(asallar)
print(f"Toplam {len(asallar)} adet asal sayı var")

🔄 Pass by Value vs Pass by Reference

🧠 Önemli Kavram:
Python'da değişkenler iki şekilde davranır:
  • Immutable (Değiştirilemez): int, float, str, tuple → Pass by Value gibi davranır
  • Mutable (Değiştirilebilir): list, dict, set → Pass by Reference gibi davranır

Immutable Tipler (Sayılar, String'ler)

🐍 Python Kodu
def sayi_degistir(x):
    """Sayıyı değiştirmeye çalışır"""
    print(f"Fonksiyon içinde (önce): x = {x}")
    x = x + 10
    print(f"Fonksiyon içinde (sonra): x = {x}")
    return x

# Ana program
sayi = 5
print(f"Fonksiyon öncesi: sayi = {sayi}")
sonuc = sayi_degistir(sayi)
print(f"Fonksiyon sonrası: sayi = {sayi}")
print(f"Return değeri: sonuc = {sonuc}")
print("\nGöründüğü gibi, orijinal 'sayi' değişmedi!")

Mutable Tipler (Listeler) - Pass by Reference

🐍 Python Kodu
def liste_degistir(liste):
    """Listeyi değiştirir"""
    print(f"Fonksiyon içinde (önce): {liste}")
    liste.append(99)
    liste[0] = 999
    print(f"Fonksiyon içinde (sonra): {liste}")

# Ana program
sayilar = [1, 2, 3, 4, 5]
print(f"Fonksiyon öncesi: {sayilar}")
liste_degistir(sayilar)
print(f"Fonksiyon sonrası: {sayilar}")
print("\n⚠️ DİKKAT: Orijinal liste değişti!")

♻️ Recursive (Özyinelemeli) Fonksiyonlar

📌 Recursion Nedir?

Bir fonksiyonun kendi kendini çağırmasına recursion denir.

Her recursive fonksiyonda iki kritik bölüm vardır:

  • Base Case: Fonksiyonun durmasını sağlayan koşul
  • Recursive Case: Fonksiyonun kendini çağırdığı kısım

Faktöriyel Hesaplama

📚 Recursive Call Stack: faktoriyel(5)

① Çağrı
faktoriyel(5)
5 × faktoriyel(4)
② Çağrı
faktoriyel(4)
4 × faktoriyel(3)
③ Çağrı
faktoriyel(3)
3 × faktoriyel(2)
④ Çağrı
faktoriyel(2)
2 × faktoriyel(1)
⑤ BASE CASE
faktoriyel(1)
return 1 ✅
⬇️ Dönüş Yolu (Return Path) ⬇️
1 döndü
2×1 = 2 döndü
3×2 = 6 döndü
4×6 = 24 döndü
5×24 = 120 🎉
🔍 Recursion'ı Anlamak İçin: Step-by-Step Debugging

Recursion'u gerçekten anlamak için debugger kullanın:

  1. VS Code'da: Sol kenara tıklayarak faktoriyel fonksiyonunun içine breakpoint koyun
  2. F5 ile debugging başlatın
  3. F11 (Step Into) ile her recursive çağrıyı takip edin
  4. Call Stack panelinde fonksiyonların nasıl üstleştiğini görün
  5. Her adımda n değerinin nasıl değiştiğini izleyin
🐍 Python Kodu
def faktoriyel(n):
    """Recursive faktöriyel hesaplama"""
    # Base case
    if n == 0 or n == 1:
        return 1

    # Recursive case
    return n * faktoriyel(n - 1)

# Test
print("Faktöriyel hesaplamaları:")
for i in range(1, 8):
    print(f"{i}! = {faktoriyel(i)}")

Fibonacci Serisi

🎮 İnteraktif Faktöriyel Hesaplama

🐍 Python Kodu
def fibonacci(n):
    """
    N. Fibonacci sayısını hesaplar
    Fibonacci: 0, 1, 1, 2, 3, 5, 8, 13, 21, ...
    """
    # Base cases
    if n == 0:
        return 0
    if n == 1:
        return 1

    # Recursive case
    return fibonacci(n - 1) + fibonacci(n - 2)

# İlk 10 Fibonacci sayısı
print("Fibonacci Serisi:")
for i in range(10):
    print(f"F({i}) = {fibonacci(i)}")

🌳 Fibonacci Çağrı Ağacı: fib(5)

⚠️ Dikkat: Aynı değerler tekrar tekrar hesaplanıyor! (Kırmızı ile işaretli)

fib(5) = fib(4) + fib(3) = 3 + 2 = 5
    │
    ├── fib(4) = fib(3) + fib(2) = 2 + 1 = 3
    │       │
    │       ├── fib(3) = fib(2) + fib(1) = 1 + 1 = 2
    │       │       │
    │       │       ├── fib(2) 🔄 = fib(1) + fib(0) = 1 + 0 = 1
    │       │       │       ├── fib(1) = 1 ✅ BASE
    │       │       │       └── fib(0) = 0 ✅ BASE
    │       │       │
    │       │       └── fib(1) = 1 ✅ BASE
    │       │
    │       └── fib(2) 🔄 = fib(1) + fib(0) = 1 + 0 = 1
    │               ├── fib(1) = 1 ✅ BASE
    │               └── fib(0) = 0 ✅ BASE
    │
    └── fib(3) 🔄 = fib(2) + fib(1) = 1 + 1 = 2
            │
            ├── fib(2) 🔄 = fib(1) + fib(0) = 1 + 0 = 1
            │       ├── fib(1) = 1 ✅ BASE
            │       └── fib(0) = 0 ✅ BASE
            │
            └── fib(1) = 1 ✅ BASE
Tekrar Aynı hesap tekrarı!
Base fib(1)=1
Base fib(0)=0
📊 Verimsizlik Analizi (fib(5) için):
fib(2): 3 kez hesaplandı
fib(3): 2 kez hesaplandı
Toplam: 15 çağrı (optimal: 5)

🎮 İnteraktif Fibonacci Hesaplama

🤔 Recursion Nedir ve Ne Zaman Kullanmalı?

📖 Recursion (Özyineleme) Nedir?

Bir fonksiyonun kendini çağırmasıdır. Her recursive fonksiyonun iki bileşeni olmalı:

  • Base Case (Temel Durum): Fonksiyonun durduğu, kendini çağırmadığı durum
  • Recursive Case: Fonksiyonun daha küçük bir problemle kendini çağırdığı durum

✅ Recursion İyi Seçim:

  • Ağaç yapıları gezmek (DOM, dosya sistemi)
  • Böl ve fethet algoritmaları (Merge Sort, Quick Sort)
  • Matematiksel tanımlar (Faktöriyel: n! = n × (n-1)!)
  • Backtracking problemleri (Sudoku, N-Queens)
  • Problem doğal olarak alt problemlere bölünüyorsa

❌ Recursion Kötü Seçim:

  • Basit döngüler - iterasyon daha verimli
  • Tekrarlı hesaplamalar (Fibonacci gibi - memoization olmadan)
  • Çok derin recursion (stack overflow riski)
  • Performans kritik kodlar (fonksiyon çağrısı maliyetli)
  • Problem sıralı/lineer ise

⚖️ Recursion vs Iteration

Özellik 🔄 Recursion 🔁 Iteration (Döngü)
Bellek Her çağrı stack'te yer kaplar Sabit bellek kullanımı
Hız Fonksiyon çağrısı overhead'i var Genellikle daha hızlı
Okunabilirlik Bazı problemler için daha temiz Basit problemler için daha açık
Limit Stack overflow riski (Python: ~1000) Pratik limit yok
💡 Pro Tip: Memoization ile Recursion'ı Optimize Edin

Fibonacci gibi tekrarlı hesaplamalarda memoization (sonuçları önbellekleme) kullanın:

from functools import lru_cache

@lru_cache(maxsize=None)
def fib_memo(n):
    if n <= 1: return n
    return fib_memo(n-1) + fib_memo(n-2)

# fib(50) artık milisaniyede hesaplanır!

🤔 Ne Zaman Fonksiyon Yazmalıyım?

Yeni başlayanlar için en zor sorulardan biri: "Bu kodu fonksiyon yapmalı mıyım?" İşte basit kurallar:

✅ Fonksiyon YAZ şu durumlarda:
🔁 Kod tekrarı Aynı veya benzer kodu 2+ kez yazdın
📦 Mantıksal birim Bir işlem grubu tek bir amaç için çalışıyor (örn: "dosya oku", "ortalama hesapla")
📏 Uzun kod bloğu 10-15 satırdan uzun bir iş yapılıyor
🏷️ İsim verilebilir Kod bloğuna anlamlı bir isim verebiliyorsun ("kullanici_dogrula", "rapor_olustur")
❌ Fonksiyon KULLANMA şu durumlarda:
📝 Tek satır + tek kullanım print("Merhaba") gibi basit kodlar için fonksiyon gereksiz
📦 Çok fazla parametre 7-8 parametre alan fonksiyon muhtemelen yanlış tasarlanmış
✨ Zaten çok basit Gereksiz karmaşıklık ekleme, basit tutulabilecek kodu karmaşıklaştırma

📋 Pratik Örnek: Fonksiyon Yazmalı mı?

Senaryo 1: ✅ Fonksiyon YAZ

# Program boyunca 5 farklı yerde kullanıcı yaşını doğruluyorsun
# HER SEFERINDE aynı kontrol:
if yas < 0 or yas > 150:
    print("Geçersiz yaş!")
else:
    print("Yaş geçerli")

# ✅ ÇÖZÜM: Fonksiyon yap!
def yas_gecerli_mi(yas):
    """Yaşın geçerli aralıkta olup olmadığını kontrol eder"""
    return 0 <= yas <= 150

# Artık her yerde:
if yas_gecerli_mi(yas):
    # devam et...

Senaryo 2: ❌ Fonksiyon YAZMA

# Sadece bir kez kullanılacak basit bir print
print("Program başlıyor...")

# ❌ BU GEREKSIZ:
def baslangic_mesaji():
    print("Program başlıyor...")

baslangic_mesaji()

# Tek satırlık, tek kullanımlık kod için fonksiyon fazla!

🎯 Altın Kural

"Bir fonksiyon TEK BİR ŞEY yapmalı ve onu İYİ yapmalı"

— Unix Felsefesi / Clean Code Prensibi