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

İ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.

python
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.

python
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!
python
sonuc = hesapla(3)
def hesapla(x):
    return x * 2
# hata: NameError: name 'hesapla' is not defined

Modü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.

python
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 None

Python'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.

python
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

Varsayılan parametre değerleri: Değiştirilebilir varsayılan değer tuzağı, Python 3.15: None'ı ayırmak için sentinel()

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:

python
i = 5

def goster(deger=i):
    print(deger)

i = 6
goster()      # çıktı: 5

Varsayı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:

python
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.

python
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.

python
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.

python
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ı: 7

Bir 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.

python
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>.carp

Fonksiyon 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.

python
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.

python
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 exceeded

Derinliğ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

Sık yapılan hatalar: print ile return'ü karıştırmak, Parantezleri unutmak, Değiştirilebilir varsayılan değer, Tanımdan önce çağırmak

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.

python
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 return edin; print yalnızca ekrana yazar.
  • Parantezleri unutmak: sonuc = hesapla fonksiyonu ç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=[]) yerine def f(liste=None) yazıp içeride yeni liste oluşturun.
  • Tanımdan önce çağırmak: def satırı çalışmadan fonksiyon adı yoktur; NameError alırsınız.
  • Aynı parametre adını iki kez kullanmak: def f(x, x) tanımı SyntaxError: duplicate parameter 'x' in function definition verir.
  • 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