Merhabalar. Bugünkü yazımızda Python’un en güçlü ama aynı zamanda en kafa karıştırıcı özelliklerinden birini ele alacağız: decorator’lar. Eğer Flask veya Django ile ilgilendiyseniz, @app.route veya @login_required gibi ifadeler görmüşsünüzdür. İşte o @ işaretiyle başlayan şeyler decorator’dur. Bugün bunların ne olduğunu, nasıl yazıldığını ve gerçek projelerde nasıl kullanıldığını adım adım inceleyeceğiz.

Decorator Nedir?

Decorator, en basit anlatımla, bir fonksiyonu alıp onun davranışını değiştiren veya genişleten başka bir fonksiyondur. Orijinal fonksiyonun kodunu değiştirmeden, onun üstüne yeni bir katman ekler.

Diyelim ki bir fonksiyonunuz var ve her çağrıldığında ekrana bir log yazmasını istiyorsunuz. Fonksiyonun içine print ekleyebilirsiniz, ama ya 50 tane fonksiyonunuz varsa? Hepsini tek tek değiştirmek yerine bir decorator yazarsınız ve tüm fonksiyonların başına @ ile eklersiniz.

Decorator’lar aslında Python’da syntactic sugar (sözdizimsel şeker)dir. Yani @decorator yazmak ile fonksiyon = decorator(fonksiyon) yazmak aynı şeydir. @ işareti sadece bunu daha okunaklı hale getirir.

1. İlk Decorator’umuzu Yazalım

Başlangıç olarak basit bir decorator yazalım. Bu decorator, bir fonksiyon çağrıldığında önce “Başlıyor” yazsın, fonksiyonu çalıştırsın, sonra da “Bitti” yazsın:

def logla(func):
    def wrapper():
        print("Başlıyor...")
        func()
        print("Bitti.")
    return wrapper
@logla
def merhaba():
    print("Merhaba Dünya!")

merhaba()

Çıktı:

Başlıyor...
Merhaba Dünya!
Bitti.

Burada olan şudur: merhaba fonksiyonu aslında wrapper fonksiyonuyla değiştirildi. wrapper önce kendi kodlarını çalıştırır, sonra orijinal func() çağırır, sonra yine kendi kodlarını çalıştırır. logla fonksiyonu da bu wrapper‘ı döndürür.

2. Parametre Alan Fonksiyonlar için Decorator

Yukarıdaki örnekte merhaba fonksiyonu parametre almıyordu. Peki ya parametre alan fonksiyonları decorator ile sarmak istersek? İşte o zaman *args ve **kwargs kullanmamız gerekir:

def logla(func):
def wrapper(*args, **kwargs):
print(f”{func.__name__} çağrıldı”)
sonuc = func(*args, **kwargs)
print(f”{func.__name__} tamamlandı”)
return sonuc
return wrapper

@logla
def topla(a, b):
    return a + b
@logla
def selam_ver(isim, sesli=False):
    if sesli:
        return f"SELAM {isim.upper()}!"
    return f"Merhaba {isim}"
print(topla(3, 5))
print(selam_ver("Ahmet", sesli=True))

Çıktı:

topla çağrıldı
topla tamamlandı
8
selam_ver çağrıldı
selam_ver tamamlandı
SELAM AHMET!

Dikkat edin: wrapper fonksiyonu içinde sonuc değişkenine fonksiyonun dönüş değerini atıyoruz ve en son return sonuc ile geri döndürüyoruz. Eğer bunu yapmazsak, decorator’lı fonksiyon her zaman None döndürür çünkü wrapper varsayılan olarak bir şey döndürmez.

3. functools.wraps ile Metadata Koruma

Decorator kullandığınızda bir sorun ortaya çıkar: fonksiyonunuzun adı, docstring’i ve diğer metadata’ları kaybolur. Çünkü artık fonksiyonunuz wrapper adında başka bir fonksiyon olmuştur. Bunu görelim:

def logla(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper

@logla
def topla(a, b):
“””İki sayıyı toplar.”””
return a + b

print(topla.__name__)
print(topla.__doc__)

Çıktı:

wrapper
None

Gördüğünüz gibi topla artık wrapper adında ve docstring kaybolmuş. Bunu çözmek için functools.wraps kullanırız:

from functools import wraps

def logla(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f”{func.__name__} çağrıldı”)
return func(*args, **kwargs)
return wrapper

@logla
def topla(a, b):
“””İki sayıyı toplar.”””
return a + b

print(topla.__name__)
print(topla.__doc__)

Çıktı:

topla
İki sayıyı toplar.

@wraps(func) sayesinde fonksiyonumuz tüm özgün metadata’sını korur. Bu özellikle debugging yaparken ve dokümantasyon工具ları kullanırken çok önemlidir. Her decorator yazdığınızda functools.wraps kullanmayı alışkanlık edinin.

4. Parametre Alan Decorator’lar

Bazen decorator’ın kendisinin de parametre almasını istersiniz. Örneğin bir timeout süresi belirlemek veya log seviyesini ayarlamak gibi. Bu durumda decorator’ınız üç katmanlı olur:

from functools import wraps

def tekrar(n):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(n):
sonuc = func(*args, **kwargs)
return sonuc
return wrapper
return decorator

@tekrar(3)
def selam_ver(isim):
    print(f"Merhaba {isim}!")
    return isim

selam_ver(“Ayşe”)

Çıktı:

Merhaba Ayşe!
Merhaba Ayşe!
Merhaba Ayşe!

Burada olanlar: tekrar(3) çağrılır ve decorator fonksiyonunu döndürür. Bu decorator da selam_ver fonksiyonunu alır ve wrapper‘a sarar. wrapper fonksiyonu n kere orijinal fonksiyonu çağırır. Üç katmanlı yapı biraz kafa karıştırıcı olabilir ama mantığı basit: dıştan içe doğru parametre, fonksiyon, wrapper.

5. Pratik Örnek: Süre Ölçme Decorator’ı

Gerçek projelerde en sık kullanılan decoratorlardan biri süre ölçmedir. Bir fonksiyonun ne kadar sürdüğünü bilmek istersiniz:

import time
from functools import wraps

def sure_olc(func):
@wraps(func)
def wrapper(*args, **kwargs):
baslangic = time.perf_counter()
sonuc = func(*args, **kwargs)
bitis = time.perf_counter()
print(f”{func.__name__} {bitis – baslangic:.4f} saniye sürtü”)
return sonuc
return wrapper

@sure_olc
def faktoriyel_hesapla(n):
    sonuc = 1
    for i in range(1, n + 1):
        sonuc *= i
    return sonuc
print(faktoriyel_hesapla(10000))

Çıktı:

faktoriyel_hesapla 0.0234 saniye sürtü
284625968091705451890641321211986889020…

Bu decorator’ı herhangi bir fonksiyona ekleyebilirsiniz ve otomatik olarak çalışma süresini ölçer. Kodunuzda değişiklik yapmanıza gerek yoktur.

6. Pratik Örnek: Basit Auth Kontrolü

Web uygulamalarında sıkça gördüğümüz bir kullanım: sadece giriş yapmış kullanıcıların erişebileceği sayfalar. Flask tarzı basit bir örnek yapalım:

from functools import wraps
# Basit bir kullanıcı taklit sistemi
aktif_kullanici = None
def giris_yap(isim):
    global aktif_kullanici
    aktif_kullanici = isim
    print(f"{isim} giriş yaptı")
def cikis_yap():
    global aktif_kullanici
    aktif_kullanici = None
    print("Çıkış yapıldı")

def login_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
if aktif_kullanici is None:
raise PermissionError(“Bu işlem için giriş yapmalısınız!”)
return func(*args, **kwargs)
return wrapper

@login_required
def profil_goster():
    print(f"Hoş geldiniz, {aktif_kullanici}!")
# Deneyelim:
cikis_yap()
try:
    profil_goster()
except PermissionError as e:
    print(f"Hata: {e}")
giris_yap("Ömer")
profil_goster()

Çıktı:

Çıkış yapıldı
Hata: Bu işlem için giriş yapmalısınız!
Ömer giriş yaptı
Hoş geldiniz, Ömer!

İşte Flask’taki @login_required decorator’ı temelde böyle çalışır. Tabi gerçek projede session veya JWT token kontrolü yapar, ama mantık aynıdır.

7. Birden Fazla Decorator’ı Üst Üste Kullanma

Bir fonksiyona birden fazla decorator ekleyebilirsiniz. Bunlar alttan üste doğru uygulanır:

@decorator_a
@decorator_b
@decorator_c
def fonksiyonum():
    pass

Bu, şu anlamına gelir: önce decorator_c uygulanır, sonra decorator_b, en son decorator_a. Yani çalışma sırası: decorator_a(decorator_b(decorator_c(fonksiyon))). En dıştaki decorator en başta, en içteki en son çalışır.

Pratik bir örnek:

import time
from functools import wraps

def sure_olc(func):
@wraps(func)
def wrapper(*args, **kwargs):
baslangic = time.perf_counter()
sonuc = func(*args, **kwargs)
print(f”{func.__name__} {time.perf_counter() – baslangic:.4f} sn”)
return sonuc
return wrapper

def logla(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f”-> {func.__name__} başlıyor”)
sonuc = func(*args, **kwargs)
print(f”-> {func.__name__} bitti”)
return sonuc
return wrapper

@sure_olc
@logla
def islem_yap(n):
    time.sleep(0.5)
    return n * 2
print(islem_yap(5))

Çıktı:

-> islem_yap başlıyor
-> islem_yap bitti
islem_yap 0.5012 sn
10

Gördüğünüz gibi önce logla (içteki) sonra sure_olc (dıştaki) çalıştı. Sıralama önemlidir.

8. Sınıf (Class) Decorator’ları

Decorator’lar sadece fonksiyonlar için değil, sınıflar için de yazılabilir. Örneğin bir sınıfa otomatik olarak bir özellik eklemek istiyorsanız:

def sinif_bilgisi_ekle(cls):
    cls.sinif_adi = cls.__name__
    return cls
@sinif_bilgisi_ekle
class Kullanici:
    def __init__(self, isim):
        self.isim = isim
k = Kullanici("Ahmet")
print(k.sinif_adi)

Çıktı:

Kullanici

Sınıf decorator’ları daha az yaygındır ama bazı framework’lerde (Örneğin Django’da @dataclass tarzı) kullanılır.

Yaygın Hatalar ve Çözümleri

Hata: Fonksiyon None döndürüyor

Decorator’ınız içinde func() çağırdınız ama return etmediniz. wrapper içinde mutlaka return func(*args, **kwargs) yapmalısınız. Aksi halde orijinal fonksiyonun dönüş değeri kaybolur.

Hata: “missing positional argument”

Decorator’ın wrapper fonksiyonu *args, **kwargs almıyordur. Eğer orijinal fonksiyon parametre alıyorsa, wrapper da parametreleri geçirmeli. Her zaman def wrapper(*args, **kwargs) kullanın.

Hata: __name__ artık wrapper görünüyor

functools.wraps kullanmayı unuttunuz. Her decorator’da @wraps(func) kullanın. Bu, debugging yaparken fonksiyon adlarının doğru görünmesini sağlar.

Hata: Parametreli decorator’da çok fazla katman

Parametreli decorator yazarken üç katmanlı yapı kafa karıştırır. def parametreli_deco(param):def decorator(func):def wrapper(*args, **kwargs):. Eğer kaçırsanız bir hata alırsınız. Dikkatli olun.

Sonuç

Decorator’lar Python’da kod tekrarını azaltmanın ve fonksiyon davranışını temiz bir şekilde genişletmenin en güçlü yollarından biridir. İster loglama, isterauth kontrolü, ister süre ölçme olsun; decorator yazmayı öğrendiğinizde pek çok şey çok daha düzenli hale gelir. Başlangıçta üç katmanlı yapılar kafa karıştırır, ama birkaç kez yazınca el alışır.

Unutmayın: her decorator’da functools.wraps kullanın, wrapper‘da *args, **kwargs kullanın ve dönüş değerini return etmeyi unutmayın. Bu üç kuralı takip ederseniz büyük ölçüde sorun yaşamazsınız.

Bir sonraki yazımızda görüşmek üzere. Okuduğunuz için teşekkür ederim!