Python'da fonksiyon def anahtar kelimesiyle tanımlanır: def ad(parametreler): satırının altına girintili gövde yazılır, sonuç return ile geri verilir. return hiç çalışmazsa fonksiyon None döndürür. Varsayılan parametre değerleri fonksiyon tanımlandığında bir kez hesaplanır; bu yüzden liste veya sözlük gibi değiştirilebilir bir nesneyi varsayılan değer yapmak, çağrılar arasında paylaşılan bir nesne oluşturur. Fonksiyonlar da birer nesnedir: başka bir ada atanabilir, argüman olarak verilebilir ve başka bir fonksiyondan döndürülebilir.
Bu yazıda def deyiminin nasıl çalıştığını, return davranışını, argümanların fonksiyona nasıl aktarıldığını, varsayılan değer tuzağını, belge dizesi (docstring) kurallarını ve fonksiyon nesnesinin özelliklerini resmî Python belgelerine dayanarak anlatıyoruz. Bütün örnekler Python 3.15.2 ile çalıştırıldı. *args, **kwargs ve parametre türleri konusu ayrı bir yazının konusu: args, kwargs ve parametre türleri.
İlk fonksiyon: def, gövde ve return

Tutorial'daki klasik örnek, belirli bir sınıra kadar Fibonacci sayılarını üreten bir fonksiyondur. def'ten sonra fonksiyonun adı ve parantez içinde parametre listesi gelir, satır iki noktayla biter. Gövde bir sonraki satırdan başlar ve girintili olmak zorundadır; PEP 8 stil kılavuzu girinti için 4 boşluk önerir.
def fib(n):
"""n'den küçük Fibonacci sayılarını liste olarak döndürür."""
sonuc = []
a, b = 0, 1
while a < n:
sonuc.append(a)
a, b = b, a + b
return sonuc
print(fib(100)) # çıktı: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
print(fib.__doc__) # çıktı: n'den küçük Fibonacci sayılarını liste olarak döndürür.Gövdenin ilk satırındaki üç tırnaklı metin fonksiyonun belge dizesidir (docstring). Python onu __doc__ özelliğine koyar; help(fib) ve belge üreten araçlar bu metni okur. return sonuc ise hesaplanan listeyi çağırana geri verir. Fonksiyon bir değer döndürdüğü için sonucu ekrana yazdırabilir, bir değişkene atayabilir ya da başka bir hesaplamada kullanabilirsiniz.
def çalıştırılabilir bir deyimdir
Language Reference'ın vurguladığı önemli bir nokta şudur: def bir bildirim değil, çalıştırılan bir deyimdir. Python bu satıra geldiğinde bir fonksiyon nesnesi oluşturur ve onu verilen ada bağlar. Gövde ise o anda çalışmaz; yalnızca fonksiyon çağrıldığında çalışır. Bunun iki pratik sonucu vardır: Bir fonksiyonu tanımından önce çağıramazsınız ve gövdedeki bir hata, fonksiyon çağrılana kadar ortaya çıkmayabilir.
print('Tanımdan önce')
def selamla(ad):
print('Gövde çalıştı')
return f'Merhaba, {ad}!'
print('Tanımdan sonra')
print(selamla('Elif'))
# çıktı: Tanımdan önce
# çıktı: Tanımdan sonra
# çıktı: Gövde çalıştı
# çıktı: Merhaba, Elif!sonuc = hesapla(3)
def hesapla(x):
return x * 2
# hata: NameError: name 'hesapla' is not definedModülün üst seviyesinde fonksiyonlar genellikle dosyanın başında tanımlanır, asıl çalışan kod en altta durur. Bu düzeni if __name__ == '__main__': bloğuyla kurmak modül ve paket yazısında anlatılıyor.
return: değer döndürmek, erken çıkmak, None
return ifadesi fonksiyonu o noktada bitirir ve yanındaki değeri çağırana verir. Tutorial üç kuralı birlikte verir: return yanında ifade olmadan yazılırsa None döner; gövdenin sonuna return olmadan ulaşılırsa da None döner. Yani her Python fonksiyonu bir değer döndürür, bazen bu değer yalnızca None'dır. Etkileşimli kabuk tek başına None sonucunu ekrana yazmaz; görmek için print() kullanmanız gerekir.
def kaydet(mesaj):
print('Kayıt:', mesaj) # değer döndürmüyor
donen = kaydet('başladı') # çıktı: Kayıt: başladı
print(donen) # çıktı: None
def min_max(sayilar):
return min(sayilar), max(sayilar)
enkucuk, enbuyuk = min_max([4, 9, 1])
print(enkucuk, enbuyuk) # çıktı: 1 9
print(min_max([4, 9, 1])) # çıktı: (1, 9)
def bol(a, b):
if b == 0:
return None # erken çıkış
return a / b
print(bol(7, 2), bol(1, 0)) # çıktı: 3.5 NonePython'da birden fazla değer döndürmek için özel bir yapı yoktur: return a, b aslında tek bir demet (tuple) döndürür ve çağıran taraf bunu x, y = ... ile açar. Açma kuralları tuple ve unpacking yazısında var. Erken return, geçersiz girdileri fonksiyonun başında eleyip asıl işi iç içe if blokları olmadan yazmayı sağlar.
Parametre ve argüman farkı
Programming FAQ iki terimi şöyle ayırır: Parametreler fonksiyon tanımında görünen adlardır, argümanlar ise çağrı sırasında fonksiyona gerçekten verilen değerlerdir. def fiyat(tutar, kdv=20): tanımında tutar ve kdv parametredir; fiyat(100, kdv=10) çağrısındaki 100 ve 10 argümandır. Argümanlar sırayla (konumsal) ya da ad=değer biçiminde (anahtar kelimeyle) verilebilir. Bir parametrenin nasıl verilebileceğini / ve * ile kısıtlamak gibi ayrıntılar parametre türleri yazısında ele alınıyor.
Argümanlar nasıl aktarılır: nesne referansı
Fonksiyon çağrıldığında argümanlar fonksiyonun yerel ad tablosuna yerleştirilir. Tutorial bunu "değerle çağırma" diye adlandırır, ama hemen ardından bir dipnotla düzeltir: Aktarılan değer her zaman bir nesne referansıdır, bu yüzden "nesne referansıyla çağırma" daha doğru bir tarif olur. Pratikte bu şu demektir: Fonksiyon, aldığı değiştirilebilir nesneyi (örneğin bir listeyi) yerinde değiştirirse çağıran taraf bu değişikliği görür. Ama fonksiyon içinde parametre adına yeni bir nesne atarsanız yalnızca yerel ad değişir, çağırandaki değişken etkilenmez.
def ekle(liste):
liste.append(99) # aynı nesneyi yerinde değiştirir
def yeniden_ata(liste):
liste = [0] # yalnızca yerel adı başka nesneye bağlar
sayilar = [1, 2]
ekle(sayilar)
print(sayilar) # çıktı: [1, 2, 99]
yeniden_ata(sayilar)
print(sayilar) # çıktı: [1, 2, 99]Sayılar, metinler ve demetler değiştirilemez (immutable) olduğu için bir fonksiyon onları yerinde değiştiremez; yeni bir değer üretip döndürmesi gerekir. Değiştirilebilir ve değiştirilemez tipler arasındaki fark değişkenler ve veri tipleri yazısında ayrıntılı anlatılıyor.
Varsayılan parametre değerleri

Bir parametreye ad=ifade biçiminde varsayılan değer verdiğinizde, o argüman çağrıda atlanabilir. Language Reference iki kuralı özellikle vurgular. Birincisi, varsayılan değeri olan bir parametreden sonra gelen parametrelerin de (* işaretine kadar) varsayılan değeri olmalıdır; aksi hâlde tanım SyntaxError: parameter without a default follows parameter with a default verir. İkincisi, varsayılan değerler fonksiyon tanımı çalıştırıldığında, soldan sağa ve yalnızca bir kez hesaplanır. Tutorial'daki örnek bunu açıkça gösterir:
i = 5
def goster(deger=i):
print(deger)
i = 6
goster() # çıktı: 5Varsayılan değer tanım anında i'nin değerini (5) aldı; sonradan i'yi değiştirmek onu etkilemedi.
Değiştirilebilir varsayılan değer tuzağı
Varsayılan değerin bir kez hesaplanması, değer liste, sözlük veya çoğu sınıf örneği gibi değiştirilebilir bir nesne olduğunda sürprize dönüşür. Her çağrıda yeni bir boş liste oluşacağını düşünürsünüz, ama aslında tüm çağrılar aynı listeyi paylaşır. FAQ bu hatanın yeni başlayanları sık sık yakaladığını belirtir. Varsayılan değerler fonksiyonun __defaults__ özelliğinde saklandığı için paylaşılan listeyi orada da görebilirsiniz:
def biriktir(oge, liste=[]): # hatalı kalıp
liste.append(oge)
return liste
print(biriktir(1)) # çıktı: [1]
print(biriktir(2)) # çıktı: [1, 2]
print(biriktir.__defaults__) # çıktı: ([1, 2],)
def biriktir_dogru(oge, liste=None):
if liste is None:
liste = [] # her çağrıda yeni liste
liste.append(oge)
return liste
print(biriktir_dogru(1)) # çıktı: [1]
print(biriktir_dogru(2)) # çıktı: [2]Belgelerin önerdiği çözüm, varsayılan değer olarak None kullanıp fonksiyonun içinde is None ile kontrol etmek ve gerekiyorsa yeni nesneyi orada oluşturmaktır. FAQ, bu "bir kez hesaplanma" davranışının bilinçli kullanıldığı bir durumu da anlatır: pahalı hesapların sonuçlarını, yalnızca anahtar kelimeyle verilebilen bir _cache={} parametresinde saklamak (memoization). Aynı işi daha düzenli yapan functools.lru_cache için itertools ve functools yazısına bakın.
Python 3.15: None'ı ayırmak için sentinel()
None kalıbının bir sınırı vardır: Çağıran kişi bilerek None vermek isterse bunu "argüman verilmedi" durumundan ayıramazsınız. Python 3.15 bu iş için yerleşik sentinel() tipini ekledi (PEP 661). Belgelere göre her sentinel nesnesi benzersizdir, yalnızca kendisine eşittir, is ile karşılaştırılmak için tasarlanmıştır ve kısa, okunur bir gösterimi vardır. Kopyalandığında da kimliğini korur. Bu özellik 3.15'ten eski sürümlerde yoktur; o sürümlerde aynı iş için genellikle modül seviyesinde object() ile oluşturulan bir nesne kullanılır.
EKSIK = sentinel('EKSIK')
def ayar_al(ayarlar, anahtar, varsayilan=EKSIK):
if anahtar in ayarlar:
return ayarlar[anahtar]
if varsayilan is EKSIK:
raise KeyError(anahtar)
return varsayilan
ayarlar = {'tema': None}
print(ayar_al(ayarlar, 'tema')) # çıktı: None
print(ayar_al(ayarlar, 'dil', 'tr')) # çıktı: tr
print(ayar_al(ayarlar, 'dil', None)) # çıktı: None
print(EKSIK) # çıktı: EKSIK
try:
ayar_al(ayarlar, 'dil')
except KeyError as e:
print('KeyError:', e) # çıktı: KeyError: 'dil'3.15'in diğer yenilikleri için Python 3.15 yenilikleri yazısına bakabilirsiniz.
Belge dizesi (docstring) kuralları
Tutorial belge dizeleri için birkaç gelenek önerir. İlk satır nesnenin amacını anlatan kısa bir özet olmalı, büyük harfle başlayıp noktayla bitmelidir; fonksiyonun adını veya tipini tekrar etmesine gerek yoktur, çünkü bunlar başka yollarla zaten görülebilir. Daha fazla satır varsa ikinci satır boş bırakılır ve ardından çağırma kuralları, yan etkiler gibi ayrıntılar paragraflar hâlinde yazılır. Python, docstring olarak kullanılan çok satırlı metinlerdeki girintiyi kendisi temizler; bu yüzden __doc__ içinde satır başlarında fazladan boşluk görmezsiniz.
def kdv_ekle(tutar, oran=20):
"""Tutara KDV ekleyip sonucu döndürür.
oran yüzde olarak verilir; varsayılan 20'dir.
"""
return tutar * (100 + oran) / 100
print(kdv_ekle(250)) # çıktı: 300.0
print(repr(kdv_ekle.__doc__)) # çıktı: "Tutara KDV ekleyip sonucu döndürür.\n\noran yüzde olarak verilir; varsayılan 20'dir.\n"Lexical analysis belgesinin hatırlattığı bir ayrıntı: f-string'ler, içlerinde hiç ifade olmasa bile docstring olarak kullanılamaz. Gövdenin ilk satırına f"..." yazarsanız __doc__ değeri None olur.
Fonksiyonlar birer nesnedir
Language Reference fonksiyonları "birinci sınıf nesne" olarak tanımlar. def, fonksiyon nesnesini bir ada bağlar; aynı nesneyi başka adlar da gösterebilir. Fonksiyonu parantezsiz yazdığınızda onu çağırmış olmazsınız, nesnenin kendisini kullanırsınız. Bu sayede fonksiyonları listelerde, sözlüklerde saklayabilir ya da başka fonksiyonlara argüman olarak verebilirsiniz.
def kare(x):
return x * x
f = kare # çağırmadan atama
print(f(4), f is kare, f.__name__) # çıktı: 16 True kare
print(type(kare)) # çıktı: <class 'function'>
def uygula(fonk, degerler):
return [fonk(d) for d in degerler]
print(uygula(kare, [1, 2, 3])) # çıktı: [1, 4, 9]
islemler = {'kare': kare, 'mutlak': abs}
print(islemler['mutlak'](-7)) # çıktı: 7Bir fonksiyonun içinde tanımlanan fonksiyon, dıştaki fonksiyonun yerel değişkenlerine erişebilir ve dışarıya döndürülebilir. Döndürülen iç fonksiyon, dış fonksiyon bittikten sonra bile o değişkenleri hatırlar; buna closure denir. Kapsam kuralları kapsam yazısında, bu tekniğin en yaygın kullanımı olan decorator'lar ise decorator yazısında anlatılıyor.
def carpan_olustur(n):
def carp(x):
return x * n
return carp
iki_kat = carpan_olustur(2)
print(iki_kat(21)) # çıktı: 42
print(iki_kat.__qualname__) # çıktı: carpan_olustur.<locals>.carpFonksiyon nesnesinin faydalı özellikleri
Data model belgesi kullanıcı tanımlı fonksiyonların özelliklerini listeler. Hata ayıklarken ve kendi araçlarınızı yazarken en çok işe yarayanlar şunlardır:
| Özellik | İçerdiği |
|---|---|
| __name__ | Fonksiyonun adı |
| __qualname__ | Nitelikli ad, örneğin dis.<locals>.ic |
| __doc__ | Belge dizesi ya da None |
| __defaults__ | Varsayılan değerlerin demeti ya da None |
| __kwdefaults__ | Yalnızca anahtar kelimeyle verilen parametrelerin varsayılanları (sözlük) |
| __annotations__ | Parametre ve dönüş tipi notları (sözlük) |
| __module__ | Fonksiyonun tanımlandığı modülün adı |
| __closure__ | None ya da closure değişkenlerini tutan hücrelerin demeti |
Tip notları (annotations)
Parametrelerin ve dönüş değerinin beklenen tipini not olarak yazabilirsiniz: parametre adından sonra iki nokta ve bir ifade, parametre listesinden sonra da -> ve bir ifade. Tutorial bu notların tamamen isteğe bağlı üst veri olduğunu ve fonksiyonun davranışını hiçbir şekilde değiştirmediğini vurgular: Python yanlış tipte bir argümanı reddetmez. Notları denetleyen, harici statik tip denetleyicileridir.
def selam(ad: str, tekrar: int = 1) -> str:
return ' '.join([f'Merhaba {ad}'] * tekrar)
print(selam('Can', 2)) # çıktı: Merhaba Can Merhaba Can
print(selam(5)) # çıktı: Merhaba 5
print(selam.__annotations__) # çıktı: {'ad': <class 'str'>, 'tekrar': <class 'int'>, 'return': <class 'str'>}selam(5) çağrısı tip notuna uymadığı hâlde sorunsuz çalıştı. Python 3.14 ile (PEP 649 ve PEP 749) notların değerlendirilme zamanı değişti: Artık tanım anında değil, gerektiğinde (örneğin __annotations__ okunduğunda) hesaplanıyorlar. Bu sayede henüz tanımlanmamış bir sınıfa atıf yapan notları tırnak içine almak gerekmiyor. Tip notlarının ayrıntıları için type hints yazısına bakın.
Özyineleme (recursion)
Bir fonksiyon kendini çağırabilir. Tutorial'a göre her çağrı, ister başka bir fonksiyondan ister kendi içinden yapılsın, yeni bir yerel ad tablosu oluşturur; bu yüzden her seviyenin kendi n değeri vardır. Özyinelemeli bir fonksiyonun mutlaka bir durma koşulu olmalıdır. Python çağrı derinliğine bir sınır koyar; sys.getrecursionlimit() bu sınırı verir. Durma koşulu olmayan bir özyineleme bu sınıra ulaştığında RecursionError yükselir.
import sys
def faktoriyel(n):
if n <= 1: # durma koşulu
return 1
return n * faktoriyel(n - 1)
print(faktoriyel(5)) # çıktı: 120
print(sys.getrecursionlimit()) # çıktı: 1000
def sonsuz(n):
return sonsuz(n + 1)
sonsuz(0)
# hata: RecursionError: maximum recursion depth exceededDerinliği önceden kestirilemeyen işlerde (örneğin çok büyük bir listeyi tek tek işlemek) çoğu zaman bir döngü daha güvenlidir. Döngüler için for ve while yazısına bakabilirsiniz.
Boş gövde ve kısa fonksiyonlar
Henüz yazmadığınız bir fonksiyonu yer tutucu olarak bırakmak için gövdeye pass ya da ... yazabilirsiniz; Tutorial ikisinin de bu amaçla kullanıldığını belirtir. Tek bir ifadeden oluşan küçük fonksiyonlar ise lambda ile yazılabilir. Language Reference'a göre lambda, basitleştirilmiş bir fonksiyon tanımının kısaltmasıdır; def biçimi birden fazla deyim ve tip notu içerebildiği için daha güçlüdür. Hangisini ne zaman seçeceğiniz lambda, map ve sorted yazısında tartışılıyor.
İsimlendirme ve stil
Tutorial'ın kodlama stili bölümü PEP 8'den birkaç temel kural aktarır: Fonksiyon ve metot adları kucuk_harf_alt_cizgi biçiminde, sınıf adları BuyukHarfleBaslayan biçiminde yazılır; girinti 4 boşluktur ve sekme kullanılmaz; satırlar 79 karakteri geçmemelidir; fonksiyonlar boş satırlarla ayrılır ve docstring kullanılır. Ayrıca kodun farklı dillerden kişilerce okunma ihtimali varsa tanımlayıcılarda ASCII dışı karakter kullanmamanız önerilir; Türkçe kelimeleri ı, ş, ğ olmadan yazmak (sayi_bul gibi) bu yüzden yaygın bir alışkanlıktır.
Sık yapılan hatalar

Aşağıdaki örnek, yeni başlayanların en çok karıştırdığı ikiliyi gösterir: Sonucu ekrana print ile yazan fonksiyon değer döndürmez, bu yüzden sonucu başka bir hesapta kullanamazsınız.
def topla_yaz(a, b):
print(a + b) # yalnızca ekrana yazar
def topla(a, b):
return a + b # değeri geri verir
x = topla_yaz(2, 3) # çıktı: 5
y = topla(2, 3)
print(x, y) # çıktı: None 5- print ile return'ü karıştırmak: Sonucu başka yerde kullanacaksanız
returnedin;printyalnızca ekrana yazar. - Parantezleri unutmak:
sonuc = hesaplafonksiyonu çağırmaz, fonksiyon nesnesini atar. Yazdırırsanız<function hesapla at 0x...>gibi bir gösterim görürsünüz. - Değiştirilebilir varsayılan değer:
def f(liste=[])yerinedef f(liste=None)yazıp içeride yeni liste oluşturun. - Tanımdan önce çağırmak:
defsatırı çalışmadan fonksiyon adı yoktur;NameErroralırsınız. - Aynı parametre adını iki kez kullanmak:
def f(x, x)tanımıSyntaxError: duplicate parameter 'x' in function definitionverir. - Global değişkeni fonksiyon içinde değiştirmeye çalışmak: Fonksiyonda atama yapılan ad yerel sayılır; ayrıntı ve çözüm kapsam yazısında.
- Hata mesajını okumamak:
TypeError: f() missing 1 required positional argument: 'a'gibi mesajlar sorunu doğrudan söyler; yaygın mesajların listesi sık hata mesajları yazısında.
Sık Sorulan Sorular
Python fonksiyonu return yazmazsam ne döndürür?
None döndürür. Gövdenin sonuna return olmadan ulaşmak veya yalın return yazmak aynı sonucu verir.
Bir fonksiyondan birden fazla değer döndürebilir miyim?
Evet, return a, b yazdığınızda Python tek bir demet döndürür; çağıran taraf x, y = f() ile değerleri ayırır.
Python argümanları referansla mı değerle mi aktarır?
Belgelerin ifadesiyle nesne referansıyla. Fonksiyon aldığı değiştirilebilir nesneyi yerinde değiştirirse çağıran bunu görür; parametre adına yeni bir nesne atamak ise yalnızca fonksiyonun içini etkiler.
Varsayılan değer olarak neden boş liste kullanmamalıyım?
Varsayılan değer tanım anında bir kez oluşturulur ve bütün çağrılar aynı listeyi paylaşır. None kullanıp fonksiyon içinde yeni liste oluşturun; None'ın da geçerli bir değer olduğu durumlarda Python 3.15'te sentinel() kullanabilirsiniz.
Tip notları hatalı tipte argümanı engeller mi?
Hayır. Notlar çalışma anında denetlenmez; yalnızca __annotations__ içinde saklanır ve tip denetleyicileri, editörler gibi araçlar tarafından kullanılır.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Python öğrenme rehberi · args, kwargs ve parametreler · lambda, map ve sorted · Kapsam: global ve nonlocal · Decorator yazma · Type hints · for ve while döngüleri