Fonksiyonlar

Kodun yapı taşları: tanımlama, kapsam, parametreler

Dr. Öğr. Üyesi Oğuz Emre Kural

Fonksiyonlar

Bir problemi çözerken ilk akla gelen yöntem, adımları sırasıyla tekrarlı biçimde yazmaktır. Fonksiyonlar, aynı işi yapan kod parçasına bir ad verip onu istediğimiz yerde çağırmamızı sağlar.

Fonksiyonlardan Önce

Yarıçapı 5 ve 8 olan iki dairenin alanını hesaplayan kodu yazmamız istendiğinde, önce birini sonra diğerini yazabiliriz:

pi = 3.14159
# 1. Dairenin Alanı
yari_cap1 = 5
alan1 = pi * (yari_cap1 ** 2)
print(f"Yarıçapı 5 olan dairenin alanı: {alan1:.2f}")

# 2. Dairenin Alanı
yari_cap2 = 8
alan2 = pi * (yari_cap2 ** 2)
print(f"Yarıçapı 8 olan dairenin alanı: {alan2:.2f}")

Bu kod çalışır, ancak programlarımız büyüdükçe pratik bir yöntem olmaktan çıkar: formülü değiştirmek gerekirse her kopyayı ayrı ayrı düzeltmek gerekir.

Fonksiyon Kullanıldığında

Aynı hesabı bir kez tanımlayıp iki kez çağırıyoruz:

Fonksiyonlar kodu tekrar kullanılabilir, okunabilir ve yönetilebilir hale getirir: kod tekrarını azaltır, okunabilirliği artırır, bakımı kolaylaştırır.

Fonksiyon Tanımlama

Python’da bir fonksiyon tanımlamak için def anahtar kelimesini kullanırız.

def fonksiyon_adi(parametre1, parametre2, ...):
    """Fonksiyonun açıklaması (docstring)"""
    # Fonksiyonun gövdesi
    # Yapılacak işlemler
    return deger  # İsteğe bağlı

Parantez içindeki adlar parametre, çağrıda verilen değerler argümandır. İlk satırdaki üç tırnaklı metin (docstring) fonksiyonun ne yaptığını açıklar.

Fonksiyon Çağırma

Bir fonksiyonu çağırmak için adını ve parantez içinde argümanlarını yazarız. return ile dönen değer bir değişkene atanabilir.

Geri Dönüş Değerleri (return)

  • Fonksiyonlar, return ifadesi ile bir değer döndürebilir; return çalıştığı anda fonksiyon biter.
  • Eğer return kullanılmazsa, fonksiyon varsayılan olarak None değerini döndürür.

Tip İpuçları (Type Hinting)

Modern Python’da kodun okunabilirliğini artırmak ve hataları önlemek için tip ipuçları kullanılır. Bu, fonksiyonun hangi tipte parametreler beklediğini ve ne döndüreceğini belirtir.

# test.py
def topla(sayi1: int, sayi2: int) -> int:
    """İki tam sayıyı toplar ve sonucu tam sayı olarak döndürür."""
    return sayi1 + sayi2

print(topla("2", 3))  # Dikkat: ilk argüman str!

Bu ipuçları zorunlu değildir, Python kodu yine çalıştırır; ama çok iyi bir pratiktir.

mypy ile Kontrol

mypy, kodunuzu çalıştırmadan önce tip ipuçlarını kontrol eden bir araçtır. Amacı, olası TypeError gibi hataları en erken aşamada yakalamaktır.

>>> mypy test.py
test.py:6: error: Argument 1 to "topla" has incompatible type "str"; expected "int"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

Docstring: Davranış Sözleşmesi

Fonksiyon tanımının hemen altındaki üç tırnaklı metin docstring’dir. İyi bir docstring fonksiyonun ne beklediğini, ne döndürdüğünü, varsa neyi değiştirdiğini ve hangi durumda hata verdiğini söyler: çağıranla fonksiyon arasındaki sözleşme.

help() bu metni ekrana getirir; editörler de fare üzerine gelince gösterir. Buradaki raise, hatayı çağırana bildirir — ayrıntısı Bölüm 09’da.

Fonksiyonları Sorumluluğa Göre Ayırmak

Bir fonksiyon hem veriyi alır, hem doğrular, hem hesaplar, hem ekrana yazarsa onu test etmek ve değiştirmek zorlaşır. Her fonksiyonun tek bir işi olsun; adı o işi söylesin.

# Her şeyi yapan tek fonksiyon: test edilemez, yeniden kullanılamaz
def not_isle():
    notlar = [int(x) for x in input("Notlar: ").split()]
    ort = sum(notlar) / len(notlar)
    print(f"Ortalama: {ort:.1f}", "Geçti" if ort >= 50 else "Kaldı")

Sorumluluklar ayrılınca her parça tek başına çağrılabilir ve sınanabilir:

def ortalama(notlar):
    return sum(notlar) / len(notlar)

def gecti_mi(ort, sinir=50):
    return ort >= sinir

def rapor(ort):
    print(f"Ortalama: {ort:.1f}", "Geçti" if gecti_mi(ort) else "Kaldı")

Girdi alma ve ekrana yazma kenarda, hesap ortada durur; ortalama([70, 90]) için input() beklemek gerekmez.

Örnek: Üç Sayının En Büyüğü

Parametre olarak verilen 3 değerden en büyük olanı bulan fonksiyonu yazalım.

Kapsam (Scope)

Bir değişkenin kapsamı (scope), o değişkenin programın hangi bölümlerinde erişilebilir olduğunu belirler.

  • Yerel Kapsam (Local Scope): Fonksiyon içinde tanımlanan değişkenler. Sadece o fonksiyonda geçerlidir.
  • Global Kapsam (Global Scope): Fonksiyonların dışında, ana kod bloğunda tanımlanan değişkenler.

Yerel ve Global x

Fonksiyon içindeki x = 5 satırı yeni bir yerel x oluşturur; dışarıdaki global x bundan etkilenmez.

Global Değişkeni Güncelleme Denemesi

Aynı nedenle, fonksiyon içinden global x’i değiştirmeye çalıştığımızda beklediğimiz olmaz:

Fonksiyon içindeki x = 20 satırı global x’i değiştirmedi; yeni bir yerel x oluşturdu.

UnboundLocalError

Peki fonksiyon dıştaki değeri önce okuyup sonra değiştirmek isterse?

Python bir adın yerel olup olmadığına satır satır değil, fonksiyon gövdesinin tamamına bakarak karar verir. - Gövdede sayac’a atama olduğu için sayac baştan yerel sayılır; print satırı çalıştığında bu yerel ada henüz değer verilmemiştir → UnboundLocalError. - Dıştaki sayac’ı gerçekten değiştirmek istiyorsak bunu açıkça söylememiz gerekir.

global Bildirimi

  • Global bir değişkeni fonksiyon içinden okumak için özel bir şey gerekmez.
  • Ona fonksiyon içinden yeni bir değer atamak istiyorsak global ile bildirmemiz gerekir.

İç İçe Fonksiyonlar ve nonlocal

  • Bir fonksiyonun içinde başka bir fonksiyon tanımlayabiliriz.
  • İçteki fonksiyon, dıştaki fonksiyonun yerel kapsamına ve global kapsama erişebilir.
  • nonlocal: İç içe fonksiyonlarda, bir üstteki (ama global olmayan) fonksiyonun değişkenini değiştirmek için kullanılır.

ic_fonksiyon, nonlocal a sayesinde dış fonksiyonun a değişkenini okur ve değiştirir:

Parametre Çeşitleri

Varsayılan Parametre Değerleri

Fonksiyonlarda parametrelere varsayılan değerler atanabilir. Eğer fonksiyon çağrılırken bu parametreye bir değer verilmezse, varsayılan değer kullanılır.

Varsayılan değer, parametreyi isteğe bağlı yapar:

Anahtar Kelime Argümanı

Argümanlar iki biçimde verilebilir: konumsal (sırayla) ya da anahtar kelimeli (parametrenin adıyla). Anahtar kelimeli çağrıda sıra önemli değildir ve hangi değerin hangi parametreye gittiği çağrı satırından okunur.

Birden fazla isteğe bağlı parametre olduğunda anahtar kelime kullanın; kaydet(dosya, True, False) yerine kaydet(dosya, uzerine_yaz=True, yedekle=False) kendini açıklar. Konumsal argümanlar her zaman anahtar kelimelilerden önce gelmelidir.

Tehlikeli Tuzak: Değişken Varsayılan Parametreler

Her müşteri için yeni bir sepet açan bir fonksiyon yazalım: sepet verilmezse boş bir sepetle başlasın, ilk ürünü koysun. Ali kalem, Ayşe defter alıyor. Ali’nin sepetinde ne var?

Varsayılan [] her çağrıda yeniden oluşturulmaz: - Python onu def satırı çalışırken bir kez oluşturur ve sonraki çağrılar aynı listeyi paylaşır. - Ali ile Ayşe aynı sepeti kullanıyor.

Liste gibi değiştirilebilir (mutable) tipler — oluşturulduktan sonra içeriği değişebilen nesneler — bu yüzden varsayılan değer olarak kullanılmamalıdır.

Doğru Kullanım: None Kalıbı

Varsayılan değer olarak None kullanın ve listeyi gövdede oluşturun; her çağrı kendi sepetini alır. Buradaki None, “çağıran sepet vermedi” anlamına gelen bir işarettir (nöbetçi değer).

Parametre Sayısı Belli Değilse?

İki sayıyı toplayan bir fonksiyon kolay:

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

Üç sayı için yenisi gerekir:

def topla_uc_sayi(a, b, c):
    return a + b + c

Peki 100 sayı? Bu yöntem sürdürülebilir değil. Çözüm, fonksiyonun “ne kadar parametre gelirse” almasını sağlamaktır. İşte burada * ve ** operatörleri devreye girer.

Değişken Sayıda Argüman (*args, **kwargs)

  • * ve ** sembolleri, Python’da “paketleme” (packing) ve “açma” (unpacking) işlemleri için kullanılan özel operatörlerdir.
    • * : Bir dizi veya demet (list, tuple) gibi konumsal elemanları paketler veya açar.
    • ** : Bir sözlük (dict) gibi anahtar-değer çiftlerini paketler veya açar.
  • Fonksiyonlarda bu operatörler, gelen dağınık argümanları düzgün bir veri yapısında paketlemek için kullanılır.

*args: Gelenleri Bir Demete (Tuple) Paketle

*args sözdizimi, fonksiyona gönderilen tüm konumsal argümanları toplar ve onları tek bir tuple içine paketler.

**kwargs: Gelenleri Bir Sözlüğe (Dict) Paketle

**kwargs sözdizimi, fonksiyona gönderilen tüm anahtar kelimeli argümanları (isim="Ali" gibi) toplar ve tek bir sözlük içine paketler.

Bölüm 03’ü hatırlayın

Çıktıdaki Isim noktasız I ile başladı: capitalize() de upper() gibi Türkçe i → İ kuralını bilmez.

İsimler Sadece Bir Gelenek!

args (“arguments”ın kısaltması) ve kwargs (“keyword arguments”ın kısaltması) isimleri zorunlu değildir, sadece Python topluluğunda yaygın olarak kullanılan bir gelenektir (convention). Önemli olan * ve ** sembolleridir.

İleri Fonksiyon Kavramları

Lambda Fonksiyonları

Tek satırda tanımlanan, isimsiz (anonim) fonksiyonlardır. Basit işlemler için kullanılırlar.

Sözdizimi: lambda argümanlar: ifade

Lambda Fonksiyonlarının Gerçek Gücü

Asıl güçleri, sorted, map, filter gibi üst düzey fonksiyonlara argüman olarak verildiklerinde ortaya çıkar.

Lambda Ne Zaman Kullanılmamalı

  • Karmaşık işlemler: Fonksiyonunuzda birden fazla ifade veya döngü varsa, lambda okunabilirliği azaltır. Normal def kullanın.
  • Yan etkiler: Lambda yalnızca bir ifadeyi hesaplayıp döndürür; global değişken değiştirmek, dosya yazmak gibi yan etkiler için uygun değildir. Yan etki gerekiyorsa def ile tanımlayın.

Özyineli Fonksiyonlar (Recursive Functions)

Kendini çağıran fonksiyonlardır. Bir problemi daha küçük, kendi benzeri alt problemlere bölerek çözerler.

  • Temel Durum (Base Case): Özyinelemenin durduğu, en basit ve çözülebilir durum.
  • Özyineli Adım (Recursive Step): Problem, temel duruma bir adım daha yaklaştırılır ve fonksiyon kendini tekrar çağırır.

Örnek: Faktöriyel

Özyineleme Uyarısı

  • Temel durum doğru tanımlanmalı ve her çağrıda probleme bir adım daha yaklaşılmalıdır.
  • Python’da özyineleme derinliği sınırlıdır; RecursionError alabilirsiniz.
  • Naif çözümler üstel zaman alabilir; iteratif/önbellekli yöntemleri düşünün.

Alıştırmalar

Boşlukları doldurup çalıştırın; sonra çözümle karşılaştırın.

Alıştırma 1: Asal mı?

Bir sayının asal olup olmadığını kontrol eden asal_mi(sayi: int) -> bool fonksiyonunu tamamlayın. 1 asal değildir. 7 için True, 10 için False çıkmalı.

Çözüm

  • False — 1 ve altındaki sayılar asal sayılmaz; fonksiyon burada hemen biter.
  • int(sayi ** 0.5) + 1 — bölen ararken kareköke kadar bakmak yeterlidir. Karekökten büyük bir bölen varsa, karekökten küçük bir eşi de vardır ve onu zaten denemiş oluruz. range bitişi dışarıda bıraktığı için + 1 ekliyoruz.
  • sayi % i == 0 — kalan sıfırsa i bir bölendir; False döndürüp duruyoruz.
  • Döngü hiçbir bölen bulamadan biterse sayı asaldır: son satırdaki True buradan gelir.

Alıştırma 2: En Büyüğü Bul

Verilen sayıların en büyüğünü bulan en_buyugu_bul(*sayilar) fonksiyonunu tamamlayın. Hiç argüman gelmezse None döndürmelidir.

Çözüm

  • *sayilar: float — çağrıda verilen tüm sayılar bir demette toplanır. Hiç sayı verilmemişse demet boştur; not sayilar bunu yakalar ve None döneriz.
  • sayi > en_buyuk — ilk sayıyı geçici olarak en büyük kabul edip kalanları onunla karşılaştırıyoruz; daha büyüğünü gördükçe güncelliyoruz. Başlangıcı 0 yapmak yaygın bir hatadır: -1, -5, -2 için sonuç yanlış çıkardı.

Alıştırma 3: filter ve lambda

Bir listedeki çift sayıları lambda ve filter() kullanarak süzün. filter(fonksiyon, liste), listedeki her elemanı fonksiyona verir ve True dönenleri tutar.

Çözüm

  • x % 2 == 0 — “bu sayı çift mi?” sorusunun tek satırlık karşılığı. filter, listedeki her elemanı bu fonksiyona gönderir ve yalnızca True dönenleri tutar.
  • filter sonucu doğrudan bir liste olarak vermez; ekrana yazdırmak için list() ile listeye çeviriyoruz.

Alıştırma 4: Profil Oluştur

profil_olustur fonksiyonu zorunlu olarak isim ve soyisim almalı, isteğe bağlı olarak **kwargs ile ek bilgiler (yaş, şehir vb.) alabilmelidir.

Çözüm

  • **ek_bilgiler — adıyla verilen fazladan argümanlar (yas=30, sehir="Ankara") bu sözlükte toplanır; zorunlu isim ve soyisim her zamanki gibi alınır.
  • ek_bilgiler.items() — sözlüğü anahtar-değer çiftleri hâlinde dolaşmanın yolu; her çift bir satır olarak yazdırılır.
  • İkinci çağrıda bambaşka bir anahtar (meslek) gelmesi sorun çıkarmaz: fonksiyon hangi ek bilgilerin geleceğini önceden bilmek zorunda değildir.

Alıştırma 5: Özyineli Ters Çevirme

Parametre olarak aldığı metni, döngü kullanmadan, özyineli olarak tersine çeviren ters_cevir fonksiyonunu tamamlayın. İpucu: ters_cevir("abc") = ters_cevir("bc") + "a"

Çözüm

  • metin — boş ya da tek karakterli bir metnin tersi kendisidir; özyinelemenin durduğu temel durum budur.
  • ters_cevir(metin[1:]) + metin[0] — metni ikiye ayırıyoruz: ilk karakter ve kalanı. Kalanın tersini fonksiyonun kendisinden isteyip ilk karakteri sona ekliyoruz.
  • Her çağrı metni bir karakter kısalttığı için zincir mutlaka temel duruma ulaşır; sonsuz çağrı riski yoktur.

Kendini Sına

Her soruda önce kendi cevabınızı seçin, sonra cevap anahtarındaki gerekçeleri okuyun; yanlış şıklar bu konuda gerçekten yapılan hatalardan seçildi.

Soru 1

def selamla(isim):
    print(f"Merhaba {isim}")

sonuc = selamla("Ada")
print(sonuc)
  • A. Merhaba Ada ve Merhaba Ada
  • B. Merhaba Ada ve None
  • C. Yalnız Merhaba Ada
  • D. None ve Merhaba Ada
  • E. Hata verir

Soru 2

x = 10
def f():
    x = 20
f()
print(x)
  • A. 20
  • B. 10
  • C. UnboundLocalError
  • D. None
  • E. 30

Soru 3

def sepet_olustur(ilk_urun, sepet=[]):
    sepet.append(ilk_urun)
    return sepet

ali = sepet_olustur("kalem")
ayse = sepet_olustur("defter")
print(ali)
  • A. ['kalem']
  • B. ['kalem', 'defter']
  • C. ['defter']
  • D. Hata verir
  • E. []

Soru 4

def topla(*sayilar):
    return sum(sayilar)

print(topla(1, 2, 3))

Aşağıdakilerden hangisi *sayilar için doğrudur?

  • A. Argümanları bir listeye paketler
  • B. Argümanları bir demete (tuple) paketler
  • C. Yalnız bir argüman kabul eder
  • D. Anahtar kelimeli argümanları paketler
  • E. Adı mutlaka args olmalıdır

Soru 5

ogrenciler = [("Ali", 80), ("Veli", 95), ("Ayşe", 90)]
print(sorted(ogrenciler, key=lambda o: o[1])[0][0])
  • A. Ali
  • B. Veli
  • C. Ayşe
  • D. 80
  • E. ("Ali", 80)

Soru 6

sayac = 10

def arttir():
    print(sayac)
    sayac = sayac + 1

arttir()
  • A. 10 yazar, sayac 11 olur
  • B. 11 yazar
  • C. UnboundLocalError; atama yüzünden sayac yerel sayılır
  • D. 10 yazar; sayac dıştan okunur, sonra yerel olur
  • E. NameError; sayac tanımlı değildir

Soru 7

def kaydet(dosya, uzerine_yaz=False, yedekle=True):
    return f"{dosya} {uzerine_yaz} {yedekle}"

print(kaydet("a.txt", yedekle=False))
  • A. a.txt False False
  • B. a.txt True False
  • C. a.txt False True
  • D. Hata verir; atlanan parametre varken sonraki verilemez
  • E. a.txt None False

Kod Yazma

Parametre olarak aldığı metnin sesli harf sayısını döndüren sesli_say(metin: str) -> int fonksiyonunu yazın. Büyük/küçük harf fark etmesin ("aeıioöuüAEIİOÖUÜ").

Özet ve Kazanımlar

Tek Sayfa Özet

  • Fonksiyon tanımlama: def ile kod blokları oluşturma ve return ile değer döndürme; return yoksa None.
  • Motivasyon: Fonksiyonlar kodu okunabilir, tekrar kullanılabilir ve yönetilebilir kılar.
  • Tip ipuçları: sayi: int -> bool gibi ipuçları ve mypy ile erken hata yakalama.
  • Docstring: ne alır, ne döndürür, neyi değiştirir, ne zaman hata verir; help() gösterir. Her fonksiyonun tek işi olsun.
  • Kapsam: yerel/global ayrımı; gövdede atama varsa ad baştan yereldir (UnboundLocalError); global ve nonlocal yalnızca yeniden atama için gerekir.
  • Parametre çeşitleri: varsayılan değerler, anahtar kelime argümanı, *args, **kwargs.
  • Tuzak: değiştirilebilir (mutable) tipler varsayılan parametre olmaz; None kalıbı kullanılır.
  • İleri kavramlar: lambda ile anonim fonksiyonlar; temel durum + özyineli adım ile özyineleme.

Bu Bölümü Bitiren Öğrenci

  • def/return ile fonksiyon tanımlar; return olmayan fonksiyonun None döndürdüğünü bilir.
  • Bir fonksiyonun sözleşmesini docstring ile yazar ve help() ile okur; tek işi olan fonksiyonlar tasarlar.
  • Bir değişkenin yerel mi global mi olduğunu belirler; UnboundLocalError’ın nedenini ve global/nonlocal’ın yalnız yeniden atama için gerektiğini açıklar.
  • Varsayılan parametre ve anahtar kelime argümanı kullanır; değiştirilebilir varsayılan tuzağını None kalıbıyla önler.
  • *args/**kwargs alan fonksiyon yazar ve çağırır.
  • lambda’yı sorted/filter ile kullanır; ne zaman def tercih edileceğini söyler.
  • Temel durum ve özyineli adımı olan basit bir özyineli fonksiyon yazar.