Fonksiyonlar
Kodun yapı taşları: tanımlama, kapsam, parametreler
Bölüm 4
defvereturnile fonksiyon tanımlamak;Nonedönüşü; tip ipuçları vemypy- Docstring ile davranış sözleşmesi ve
help(); fonksiyonları tek sorumluluğa göre ayırmak - Yerel/global kapsam;
UnboundLocalError;globalvenonlocalne zaman gerekir - Varsayılan parametreler, anahtar kelime argümanı ve değiştirilebilir (mutable) varsayılan tuzağı
*argsve**kwargsile değişken sayıda argümanlambdavesorted/filterile kullanımı; özyineleme
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,
returnifadesi ile bir değer döndürebilir;returnçalıştığı anda fonksiyon biter. - Eğer
returnkullanılmazsa, fonksiyon varsayılan olarakNonedeğ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
globalile 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 + cPeki 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.
Çı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
defkullanı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
defile 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
- 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;
RecursionErroralabilirsiniz. - 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ı.
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.rangebitişi dışarıda bıraktığı için+ 1ekliyoruz.sayi % i == 0— kalan sıfırsaibir bölendir;Falsedöndürüp duruyoruz.- Döngü hiçbir bölen bulamadan biterse sayı asaldır: son satırdaki
Trueburadan 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.
*sayilar: float— çağrıda verilen tüm sayılar bir demette toplanır. Hiç sayı verilmemişse demet boştur;not sayilarbunu yakalar veNonedö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ı0yapmak yaygın bir hatadır:-1, -5, -2iç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.
x % 2 == 0— “bu sayı çift mi?” sorusunun tek satırlık karşılığı.filter, listedeki her elemanı bu fonksiyona gönderir ve yalnızcaTruedönenleri tutar.filtersonucu doğrudan bir liste olarak vermez; ekrana yazdırmak içinlist()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.
**ek_bilgiler— adıyla verilen fazladan argümanlar (yas=30,sehir="Ankara") bu sözlükte toplanır; zorunluisimvesoyisimher 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"
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 AdaveMerhaba Ada - B.
Merhaba AdaveNone - C. Yalnız
Merhaba Ada - D.
NoneveMerhaba Ada - E. Hata verir
Doğru: B. return olmayan fonksiyon None döndürür; önce fonksiyon içindeki print, sonra print(sonuc) çalışır. A print’in değer döndürdüğünü sanır. C ikinci print’i atlar. D sırayı ters çevirir. E None yazdırmak hata değildir.
Soru 2
x = 10
def f():
x = 20
f()
print(x)- A.
20 - B.
10 - C.
UnboundLocalError - D.
None - E.
30
Doğru: B. Fonksiyon içindeki atama yeni bir yerel x oluşturur; global x değişmez. A global x yazılmış gibi okur. C bu hata yalnızca fonksiyon içinde x’i hem okuyup hem atadığınızda çıkar. D/E dayanaksız.
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.
[]
Doğru: B. Varsayılan liste def çalışırken bir kez oluşturulur; ali ve ayse aynı listeye bağlıdır, ikinci çağrının eklediği "defter" Ali’nin sepetinde de görünür. A her çağrıda yeni liste bekler — sepet=None kalıbı bunu sağlar. C ilk eklemenin silindiğini sanır. D hata yok. E append’in etkisiz olduğunu sanır.
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
argsolmalıdır
Doğru: B. Tek yıldız konumsal argümanları tuple olarak toplar. A liste değil demet. C sınırsız argüman alır. D iki yıldızın (**kwargs) işidir. E ad gelenektir, zorunlu değildir.
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)
Doğru: A. Nota göre artan sıralanır, ilk eleman en düşük not (("Ali", 80)), [0] ile ad alınır. B en yüksek notu seçer (reverse=True olsaydı). C ada göre sıralamayla karıştırır. D [0][0] yerine [0][1] okur. E son [0]’ı atlar.
Soru 6
sayac = 10
def arttir():
print(sayac)
sayac = sayac + 1
arttir()- A.
10yazar,sayac11 olur - B.
11yazar - C.
UnboundLocalError; atama yüzündensayacyerel sayılır - D.
10yazar;sayacdıştan okunur, sonra yerel olur - E.
NameError;sayactanımlı değildir
Doğru: C. Gövdede sayac’a atama olduğu için ad fonksiyonun tamamında yereldir; print çalıştığında henüz değeri yoktur. A/D satır satır karar verildiğini sanır. B global sayac yazılmış gibi okur. E ad tanımlıdır; sorun yerel olup değer almamasıdır.
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
Doğru: A. uzerine_yaz verilmediği için varsayılan False, yedekle anahtar kelimeyle False yapıldı. B yedekle=False değerini uzerine_yaz’a konumsal verildiğini sanır. C anahtar kelime argümanını yok sayar. D anahtar kelimenin tam amacı aradaki parametreyi atlayabilmektir. E verilmeyen parametre None değil, varsayılanını alır.
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Ü").
def sesli_say(metin: str) -> int:
sesliler = "aeıioöuüAEIİOÖUÜ"
sayac = 0
for harf in metin:
if harf in sesliler:
sayac += 1
return sayac
print(sesli_say("Ondokuz Mayıs")) # 5lower() kullanmadan iki harf kümesini birden kontrol ettik; Bölüm 03’teki Türkçe İ sorunundan böylece kaçındık.
Özet ve Kazanımlar
Tek Sayfa Özet
- Fonksiyon tanımlama:
defile kod blokları oluşturma vereturnile değer döndürme;returnyoksaNone. - Motivasyon: Fonksiyonlar kodu okunabilir, tekrar kullanılabilir ve yönetilebilir kılar.
- Tip ipuçları:
sayi: int -> boolgibi ipuçları vemypyile 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);globalvenonlocalyalnı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;
Nonekalıbı kullanılır. - İleri kavramlar:
lambdaile anonim fonksiyonlar; temel durum + özyineli adım ile özyineleme.
Bu Bölümü Bitiren Öğrenci
def/returnile fonksiyon tanımlar;returnolmayan fonksiyonunNonedö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 veglobal/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ı
Nonekalıbıyla önler. *args/**kwargsalan fonksiyon yazar ve çağırır.lambda’yısorted/filterile kullanır; ne zamandeftercih edileceğini söyler.- Temel durum ve özyineli adımı olan basit bir özyineli fonksiyon yazar.