İçeriğe geç

Python Decorators (Dekoratörler): Fonksiyonlarını Güçlendir

T
Tolgahan
· · 13 dk okuma · 81 görüntülenme

Python Decorators (Dekoratörler): Fonksiyonlarını Güçlendir

Bir fonksiyonun ne yaptığını değiştirmeden, ona yeni yetenekler ekleyebilseydik? Mesela her çağrıldığında süresini ölçen, sonuçlarını cache'leyen veya erişim kontrolü yapan bir katman eklesek — ama fonksiyonun kendisine tek bir satır bile dokunmasak? İşte Python decorator'ları tam olarak bunu yapıyor.

Gerçek dünyada düşün: bir binanın girişine güvenlik kapısı koyuyorsun. Bina aynı bina, içindeki daireler aynı daireler — ama artık içeri giren herkes kimlik kontrolünden geçiyor. Decorator'lar da fonksiyonlarına böyle bir "güvenlik kapısı", "zaman sayacı" veya "kayıt defteri" eklemenin Python yolu.

Bu yazıda decorator'ların nasıl çalıştığını gerçekten anlayacaksın. Sadece @ sembolünü yapıştırıp geçmek değil — arka planda neler döndüğünü, nasıl yazılacağını, parametreli decorator'ları, class-based decorator'ları ve gerçek projelerde nerelerde kullanıldığını öğreneceksin. Yazının sonunda kendi decorator'larını yazacak seviyeye geleceksin.

Ön Bilgi: Fonksiyonlar Birinci Sınıf Vatandaştır

Python'da decorator'ları anlamak için önce bir şeyi kavramak gerekiyor: Python'da fonksiyonlar birer nesnedir (object). Yani bir fonksiyonu değişkene atayabilir, başka bir fonksiyona parametre olarak gönderebilir ve bir fonksiyondan başka bir fonksiyon döndürebilirsin.

def selamla(isim):
    return f"Merhaba, {isim}!"

# Fonksiyonu bir değişkene ata
selam_fonksiyonu = selamla
print(selam_fonksiyonu("Ahmet"))  # Merhaba, Ahmet!

# Fonksiyonu başka bir fonksiyona parametre olarak gönder
def fonksiyon_calistir(fn, deger):
    return fn(deger)

print(fonksiyon_calistir(selamla, "Zeynep"))  # Merhaba, Zeynep!

Bu özelliğe first-class functions (birinci sınıf fonksiyonlar) deniyor. Fonksiyonlar tıpkı sayılar, stringler veya listeler gibi elden ele dolaşabiliyor. Bu kavramı sindirmeden decorator'lara geçme — çünkü decorator'ların tüm sihri bu temelin üzerine kurulu.

Decorator Nedir? Temel Mekanizma

Decorator, en basit haliyle bir fonksiyon alıp, yeni bir fonksiyon döndüren fonksiyondur. Hepsi bu. Gelen fonksiyonun etrafına bir katman (wrapper) sarar ve bu katmanı geri döndürür.

def zaman_olcer(fn):
    """Fonksiyonun çalışma süresini ölçen decorator."""
    import time

    def wrapper(*args, **kwargs):
        # Fonksiyon çalışmadan önce
        baslangic = time.time()

        # Orijinal fonksiyonu çalıştır
        sonuc = fn(*args, **kwargs)

        # Fonksiyon çalıştıktan sonra
        bitis = time.time()
        gecen_sure = bitis - baslangic
        print(f"[{fn.__name__}] {gecen_sure:.4f} saniye sürdü")

        return sonuc

    return wrapper

Bu decorator'ı kullanmak için iki yol var:

# Yol 1: Manuel uygulama
def agir_islem():
    toplam = sum(range(1_000_000))
    return toplam

agir_islem = zaman_olcer(agir_islem)  # Decorator'ı elle uygula
agir_islem()  # [agir_islem] 0.0312 saniye sürdü

# Yol 2: @ sözdizimi (syntactic sugar)
@zaman_olcer
def agir_islem():
    toplam = sum(range(1_000_000))
    return toplam

agir_islem()  # [agir_islem] 0.0312 saniye sürdü

İki yol da tamamen aynı şeyi yapıyor. @zaman_olcer yazmak, agir_islem = zaman_olcer(agir_islem) yazmakla birebir eşdeğer. @ sembolü sadece daha okunabilir bir kısayol — Python'un bize sunduğu bir sözdizimsel şeker (syntactic sugar).

Adım adım ne oluyor, bir bakalım:

  1. Python @zaman_olcer satırını görüyor.

  2. Alttaki agir_islem fonksiyonunu zaman_olcer() fonksiyonuna parametre olarak gönderiyor.

  3. zaman_olcer() bir wrapper fonksiyonu oluşturup döndürüyor.

  4. Artık agir_islem ismi, orijinal fonksiyona değil bu wrapper fonksiyonuna işaret ediyor.

  5. agir_islem() çağrıldığında aslında wrapper() çalışıyor — o da içinde orijinal fonksiyonu çağırıyor.

*args ve **kwargs: Her Fonksiyona Uyum Sağlamak

Yukarıdaki wrapper(*args, **kwargs) kalıbına dikkat et. Bu kalıp, decorator'ın herhangi bir fonksiyona uygulanabilmesini sağlıyor. *args pozisyonel argümanları, **kwargs ise isimli argümanları toplar.

@zaman_olcer
def carpim(a, b):
    return a * b

@zaman_olcer
def profil_olustur(isim, yas, sehir="İstanbul"):
    return {"isim": isim, "yas": yas, "sehir": sehir}

print(carpim(6, 7))           # [carpim] 0.0000 saniye sürdü → 42
print(profil_olustur("Ali", 28, sehir="Ankara"))
# [profil_olustur] 0.0000 saniye sürdü → {'isim': 'Ali', 'yas': 28, 'sehir': 'Ankara'}

Eğer wrapper fonksiyonunda *args, **kwargs kullanmasaydık, decorator sadece belirli sayıda parametre alan fonksiyonlara uygulanabilirdi. Bu kalıbı her decorator'da kullan — altın kural.

functools.wraps: Kimlik Kaybını Önle

Decorator uyguladığında bir sorun oluşur: orijinal fonksiyonun adı, docstring'i ve diğer metadata'sı kaybolur.

@zaman_olcer
def hesapla():
    """Büyük bir hesaplama yapar."""
    return sum(range(100))

print(hesapla.__name__)    # wrapper  ← Yanlış! "hesapla" olmalıydı
print(hesapla.__doc__)     # None     ← Yanlış! Docstring kayboldu

Bu problemi çözmek için functools.wraps decorator'ını kullanıyoruz. Evet, bir decorator'ı düzeltmek için başka bir decorator kullanıyoruz — Python dünyasına hoş geldin.

import functools
import time

def zaman_olcer(fn):
    """Fonksiyonun çalışma süresini ölçen decorator."""

    @functools.wraps(fn)  # Orijinal fonksiyonun metadata'sını koru
    def wrapper(*args, **kwargs):
        baslangic = time.time()
        sonuc = fn(*args, **kwargs)
        bitis = time.time()
        print(f"[{fn.__name__}] {bitis - baslangic:.4f} saniye sürdü")
        return sonuc

    return wrapper

@zaman_olcer
def hesapla():
    """Büyük bir hesaplama yapar."""
    return sum(range(100))

print(hesapla.__name__)  # hesapla  ✓
print(hesapla.__doc__)   # Büyük bir hesaplama yapar.  ✓

⚠️ Dikkat: functools.wraps kullanmamak, debugging sırasında büyük baş ağrısına neden olur. Stack trace'lerde fonksiyon adı olarak "wrapper" görürsün ve hangi fonksiyonun hata verdiğini bulmak kabusa döner. Her decorator'da `@functools.wraps(fn)` kullan — istisnası yok.

Parametreli Decorator'lar

Şimdiye kadar yazdığımız decorator'lar sabit davranıyordu. Peki ya decorator'a parametre göndermek istersen? Mesela @tekrar_et(3) gibi, fonksiyonu 3 kez çalıştıran bir decorator?

Bu durumda üç katmanlı bir yapıya ihtiyacın var: decorator factory → decorator → wrapper.

import functools

def tekrar_et(kac_kez=2):
    """Fonksiyonu belirtilen sayıda tekrar çalıştıran decorator."""
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            sonuc = None
            for i in range(kac_kez):
                print(f"[Çalıştırma {i + 1}/{kac_kez}]")
                sonuc = fn(*args, **kwargs)
            return sonuc  # Son çalıştırmanın sonucunu döndür
        return wrapper
    return decorator

@tekrar_et(kac_kez=3)
def merhaba_de(isim):
    print(f"Merhaba, {isim}!")

merhaba_de("Elif")
# [Çalıştırma 1/3]
# Merhaba, Elif!
# [Çalıştırma 2/3]
# Merhaba, Elif!
# [Çalıştırma 3/3]
# Merhaba, Elif!

Bu yapıda neler oluyor?

  1. tekrar_et(kac_kez=3) çağrılıyor → decorator fonksiyonunu döndürüyor.

  2. decorator, merhaba_de fonksiyonunu alıyor → wrapper fonksiyonunu döndürüyor.

  3. merhaba_de artık wrapper'a işaret ediyor.

İç içe üç fonksiyon. İlk başta kafa karıştırıcı görünebilir ama mantığı şu: dıştaki fonksiyon parametreleri yakalar (closure), ortadaki fonksiyon orijinal fonksiyonu alır, içteki fonksiyon ise asıl çalışma mantığını barındırır.

💡 İpucu: Parametreli decorator yazarken kafan karışırsa, şu şablonu uygula: dıştan içe — parametre al, fonksiyon al, fonksiyonu çalıştır. Her zaman bu sıra.

Gerçek Dünya Örneği: Loglama ve Erişim Kontrolü

Şimdi production kodunda gerçekten kullanılan iki decorator yazalım. İlk olarak, her fonksiyon çağrısını loglayan bir decorator:

import functools
import logging

# Loglama ayarları
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s")
logger = logging.getLogger(__name__)

def logla(seviye=logging.INFO):
    """Fonksiyon çağrılarını ve sonuçlarını loglayan decorator."""
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            args_str = ", ".join([repr(a) for a in args])
            kwargs_str = ", ".join([f"{k}={v!r}" for k, v in kwargs.items()])
            tum_args = ", ".join(filter(None, [args_str, kwargs_str]))

            logger.log(seviye, f"ÇAĞRI: {fn.__name__}({tum_args})")

            try:
                sonuc = fn(*args, **kwargs)
                logger.log(seviye, f"SONUÇ: {fn.__name__} → {sonuc!r}")
                return sonuc
            except Exception as e:
                logger.error(f"HATA: {fn.__name__} → {type(e).__name__}: {e}")
                raise  # Hatayı tekrar fırlat, yutma

        return wrapper
    return decorator

@logla(seviye=logging.DEBUG)
def kullanici_bul(kullanici_id):
    if kullanici_id <= 0:
        raise ValueError("Geçersiz kullanıcı ID")
    return {"id": kullanici_id, "isim": "Ahmet"}

# Başarılı çağrı
kullanici_bul(42)
# 2026-03-01 10:00:00 - ÇAĞRI: kullanici_bul(42)
# 2026-03-01 10:00:00 - SONUÇ: kullanici_bul → {'id': 42, 'isim': 'Ahmet'}

# Hatalı çağrı
try:
    kullanici_bul(-1)
except ValueError:
    pass
# 2026-03-01 10:00:00 - ÇAĞRI: kullanici_bul(-1)
# 2026-03-01 10:00:00 - HATA: kullanici_bul → ValueError: Geçersiz kullanıcı ID

Şimdi de basit bir erişim kontrolü decorator'ı:

import functools

# Basit rol tabanlı erişim kontrolü
aktif_kullanici = {"isim": "Mehmet", "rol": "editor"}

def yetki_gerekli(gerekli_rol):
    """Belirli bir role sahip olmayı gerektiren decorator."""
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            kullanici_rolu = aktif_kullanici.get("rol", "misafir")
            if kullanici_rolu != gerekli_rol:
                raise PermissionError(
                    f"'{fn.__name__}' için '{gerekli_rol}' rolü gerekli, "
                    f"mevcut rol: '{kullanici_rolu}'"
                )
            return fn(*args, **kwargs)
        return wrapper
    return decorator

@yetki_gerekli("admin")
def kullanici_sil(kullanici_id):
    return f"Kullanıcı {kullanici_id} silindi"

@yetki_gerekli("editor")
def makale_yayinla(baslik):
    return f"'{baslik}' yayınlandı"

# Editor rolüyle
print(makale_yayinla("Python Rehberi"))  # 'Python Rehberi' yayınlandı ✓

try:
    kullanici_sil(5)  # PermissionError fırlatır ✗
except PermissionError as e:
    print(f"Erişim reddedildi: {e}")
# Erişim reddedildi: 'kullanici_sil' için 'admin' rolü gerekli, mevcut rol: 'editor'

Bu iki decorator, Flask ve Django gibi framework'lerde her gün kullanılan pattern'ların basitleştirilmiş versiyonları. Flask'taki @login_required, Django'daki @permission_required — hepsi bu mantıkla çalışıyor.

Birden Fazla Decorator Yığmak (Stacking)

Bir fonksiyona birden fazla decorator uygulayabilirsin. Decorator'lar aşağıdan yukarıya uygulanır ama yukarıdan aşağıya çalışır.

import functools
import time

def zaman_olcer(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        baslangic = time.time()
        sonuc = fn(*args, **kwargs)
        print(f"[SÜRE] {fn.__name__}: {time.time() - baslangic:.4f}s")
        return sonuc
    return wrapper

def logla_basit(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        print(f"[LOG] {fn.__name__} çağrıldı")
        sonuc = fn(*args, **kwargs)
        print(f"[LOG] {fn.__name__} tamamlandı")
        return sonuc
    return wrapper

@zaman_olcer      # 2. uygulanır (dıştaki katman)
@logla_basit      # 1. uygulanır (içteki katman)
def veri_isle():
    time.sleep(0.1)
    return "Tamamlandı"

veri_isle()
# [LOG] veri_isle çağrıldı
# [LOG] veri_isle tamamlandı
# [SÜRE] veri_isle: 0.1023s

Bu şununla eşdeğer: veri_isle = zaman_olcer(logla_basit(veri_isle)). Önce logla_basit uygulanıyor (fonksiyona en yakın olan), sonra zaman_olcer onun üstüne sarılıyor. Çalışma sırasında ise dıştaki katman (zaman_olcer) önce başlıyor, içteki katmana (logla_basit) geçiyor, o da orijinal fonksiyonu çağırıyor. Sonra tamamlanma sırası ters dönüyor.

Bunu bir Rus oyuncak bebeği (matruşka) gibi düşün: en dıştan başlayıp en içe iniyorsun, sonra en içten başlayıp en dışa çıkıyorsun.

Class-Based Decorator'lar

Decorator'lar sadece fonksiyon olmak zorunda değil. __call__ metodu tanımlı herhangi bir nesne decorator olarak kullanılabilir. Bu, özellikle decorator'ın bir durum (state) tutması gerektiğinde işe yarar.

import functools

class CagriSayaci:
    """Fonksiyonun kaç kez çağrıldığını sayan decorator."""

    def __init__(self, fn):
        functools.update_wrapper(self, fn)
        self.fn = fn
        self.cagri_sayisi = 0

    def __call__(self, *args, **kwargs):
        self.cagri_sayisi += 1
        print(f"[{self.fn.__name__}] {self.cagri_sayisi}. çağrı")
        return self.fn(*args, **kwargs)

    def sifirla(self):
        """Sayacı sıfırla."""
        self.cagri_sayisi = 0

@CagriSayaci
def api_cagir(endpoint):
    return f"GET {endpoint} → 200 OK"

print(api_cagir("/users"))     # [api_cagir] 1. çağrı → GET /users → 200 OK
print(api_cagir("/products"))  # [api_cagir] 2. çağrı → GET /products → 200 OK
print(api_cagir("/orders"))    # [api_cagir] 3. çağrı → GET /orders → 200 OK

print(f"Toplam çağrı: {api_cagir.cagri_sayisi}")  # Toplam çağrı: 3

api_cagir.sifirla()
print(f"Sıfırlama sonrası: {api_cagir.cagri_sayisi}")  # Sıfırlama sonrası: 0

Class-based decorator'ın avantajı, sifirla() gibi ek metotlar sunabilmesi. Fonksiyon tabanlı decorator'larda bunu yapmak daha zahmetli olurdu. Ancak çoğu durumda fonksiyon tabanlı decorator'lar yeterli ve daha okunabilir. Class-based olanları durum tutman veya karmaşık API sunman gerektiğinde tercih et.

Yaygın Hatalar ve Tuzaklar

1. return Unutmak

En sık yapılan hata: wrapper fonksiyonunda orijinal fonksiyonun dönüş değerini döndürmeyi unutmak.

# ❌ YANLIŞ — return yok, sonuç kaybolur
def hatali_decorator(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        print("Çalışıyor...")
        fn(*args, **kwargs)  # Sonuç döndürülmüyor!
    return wrapper

@hatali_decorator
def topla(a, b):
    return a + b

sonuc = topla(3, 5)
print(sonuc)  # None ← Beklenen 8'di!

# ✅ DOĞRU — return eklendi
def dogru_decorator(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        print("Çalışıyor...")
        return fn(*args, **kwargs)  # Sonucu döndür
    return wrapper

2. Decorator'ı Çağırmak vs Çağırmamak

Parametresiz decorator'larda parantez koymuyorsun, parametreli olanlarda koyuyorsun.

# ❌ YANLIŞ — parametresiz decorator'a parantez koyma
@zaman_olcer()  # TypeError: zaman_olcer() missing 1 required positional argument: 'fn'
def islem():
    pass

# ✅ DOĞRU
@zaman_olcer  # Parantez yok
def islem():
    pass

# ✅ DOĞRU — parametreli decorator'da parantez gerekli
@tekrar_et(kac_kez=3)  # Parantez ile parametre gönderiyorsun
def islem():
    pass

3. Decorator İçinde Mutable Varsayılan Argüman

# ❌ YANLIŞ — mutable default argument tuzağı
def cache_decorator(fn, cache={}):  # cache tüm çağrılar arasında paylaşılır!
    @functools.wraps(fn)
    def wrapper(*args):
        if args not in cache:
            cache[args] = fn(*args)
        return cache[args]
    return wrapper

# ✅ DOĞRU — cache'i wrapper içinde oluştur
def cache_decorator(fn):
    cache = {}  # Her fonksiyon için ayrı cache
    @functools.wraps(fn)
    def wrapper(*args):
        if args not in cache:
            cache[args] = fn(*args)
        return cache[args]
    return wrapper

4. Exception'ları Yutmak

# ❌ YANLIŞ — hata yutulur, debug imkansız
def sessiz_decorator(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        try:
            return fn(*args, **kwargs)
        except Exception:
            pass  # Hata sessizce yutuldu
    return wrapper

# ✅ DOĞRU — hatayı logla ve tekrar fırlat
def guvenli_decorator(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        try:
            return fn(*args, **kwargs)
        except Exception as e:
            logger.error(f"{fn.__name__} hata verdi: {e}")
            raise  # Hatayı tekrar fırlat
    return wrapper

Python'un Yerleşik Decorator'ları

Python'un standart kütüphanesinde çok kullanılan hazır decorator'lar var. Bunları bilmek, tekerleği yeniden icat etmeni önler.

@property — Attribute Gibi Erişilen Metotlar

class Dikdortgen:
    def __init__(self, en, boy):
        self._en = en
        self._boy = boy

    @property
    def alan(self):
        """Alan hesapla — dikdortgen.alan şeklinde çağrılır."""
        return self._en * self._boy

    @property
    def cevre(self):
        return 2 * (self._en + self._boy)

    @property
    def en(self):
        return self._en

    @en.setter
    def en(self, deger):
        if deger <= 0:
            raise ValueError("En pozitif olmalı")
        self._en = deger

d = Dikdortgen(5, 3)
print(d.alan)    # 15 — parantez yok, attribute gibi
print(d.cevre)   # 16
d.en = 10        # setter çalışır
print(d.alan)    # 30

@staticmethod ve @classmethod

class Tarih:
    def __init__(self, gun, ay, yil):
        self.gun = gun
        self.ay = ay
        self.yil = yil

    @classmethod
    def string_ile_olustur(cls, tarih_str):
        """'01-03-2026' formatından Tarih nesnesi oluştur."""
        gun, ay, yil = map(int, tarih_str.split("-"))
        return cls(gun, ay, yil)  # cls = Tarih sınıfı

    @staticmethod
    def artik_yil_mi(yil):
        """Yılın artık yıl olup olmadığını kontrol et."""
        return yil % 4 == 0 and (yil % 100 != 0 or yil % 400 == 0)

    def __repr__(self):
        return f"{self.gun:02d}/{self.ay:02d}/{self.yil}"

t = Tarih.string_ile_olustur("01-03-2026")
print(t)                          # 01/03/2026
print(Tarih.artik_yil_mi(2024))   # True
print(Tarih.artik_yil_mi(2026))   # False

@functools.lru_cache — Otomatik Memoization

import functools

@functools.lru_cache(maxsize=128)
def fibonacci(n):
    """N. Fibonacci sayısını hesapla — cache ile O(n) karmaşıklık."""
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

# Cache olmadan: O(2^n) — n=35 için ~9.2 milyar işlem
# Cache ile: O(n) — n=35 için sadece 36 işlem
print(fibonacci(35))   # 9227465 — anında döner
print(fibonacci(100))  # 354224848179261915075 — yine anında

# Cache istatistikleri
print(fibonacci.cache_info())
# CacheInfo(hits=99, misses=101, maxsize=128, currsize=101)

# Cache'i temizle
fibonacci.cache_clear()

@lru_cache, pahalı hesaplamaları veya tekrarlayan fonksiyon çağrılarını dramatik şekilde hızlandırır. LRU (Least Recently Used) algoritmasıyla çalışır — cache dolduğunda en az kullanılan sonuçları atar. maxsize=None yaparak sınırsız cache de kullanabilirsin, ama bellek tüketimine dikkat et.

Best Practices: Decorator Yazarken Uyulması Gerekenler

1. Her zaman `functools.wraps` kullan. Decorator'ın orijinal fonksiyonun metadata'sını korumasını sağlar. Bu olmadan debugging, dokümantasyon araçları ve introspection (iç gözlem) bozulur.

2. Decorator'ları tek sorumluluklu tut. Bir decorator bir iş yapsın: loglama, caching, yetkilendirme. Hepsini tek decorator'a tıkma. Birden fazla özellik istiyorsan decorator'ları yığ (stack).

3. Orijinal fonksiyonun dönüş değerini koru. wrapper fonksiyonunda return fn(*args, **kwargs) yazmayı kesinlikle unutma. Aksi halde tüm fonksiyonların None döner.

4. Exception'ları yutma. Hata yönetimi yapıyorsan logla ama raise ile hatayı tekrar fırlat. Sessiz hatalar, production'da saatler süren debug seanslarına neden olur.

5. Performans etkisini düşün. Her decorator bir fonksiyon çağrısı katmanı ekler. Milisaniyeler önemliyse (tight loop gibi), gereksiz decorator kullanımından kaçın. Ama çoğu durumda bu ek yük ihmal edilebilir düzeydedir.

6. Parametresiz ve parametreli kullanımı aynı decorator'da destekle. İleri seviye bir teknik ama kullanıcı deneyimini artırır:

import functools

def tekrar_et(_fn=None, *, kac_kez=2):
    """Hem @tekrar_et hem @tekrar_et(kac_kez=3) şeklinde çalışır."""
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            for _ in range(kac_kez):
                sonuc = fn(*args, **kwargs)
            return sonuc
        return wrapper

    if _fn is not None:
        # @tekrar_et şeklinde çağrıldı (parantezsiz)
        return decorator(_fn)
    # @tekrar_et(kac_kez=3) şeklinde çağrıldı (parantezli)
    return decorator

@tekrar_et
def selamla():
    print("Merhaba!")

@tekrar_et(kac_kez=4)
def veda_et():
    print("Hoşça kal!")

selamla()   # 2 kez çalışır (varsayılan)
veda_et()   # 4 kez çalışır

Bu pattern'da _fn parametresi, decorator'ın parantezli mi parantezsiz mi çağrıldığını anlamaya yarar. Parantezsiz çağrıda Python fonksiyonu doğrudan _fn'e atar; parantezli çağrıda _fn None kalır ve parametreler keyword argument olarak gelir.

Ne Zaman Decorator Kullanmalısın?

Decorator'lar güçlü bir araç ama her yerde kullanılmamalı. İşte decorator'ın doğru tercih olduğu durumlar:

  • Cross-cutting concerns (çapraz kesen ilgiler): Loglama, caching, yetkilendirme, rate limiting, retry mekanizması — birçok fonksiyonda tekrarlanan ortak mantık.

  • Kod tekrarını azaltmak: Aynı try-except bloğunu veya aynı ön/son kontrolleri 10 farklı fonksiyona kopyalıyorsan, decorator yaz.

  • Fonksiyonun sorumluluğunu temiz tutmak: Bir fonksiyon hem iş mantığını çalıştırıp hem loglama yapıp hem cache kontrolü yapıyorsa, loglama ve cache'i decorator'a taşı. Fonksiyon sadece kendi işini yapsın.

Decorator kullanmamanın daha iyi olduğu durumlar:

  • Mantık sadece tek bir fonksiyona özel ise — decorator yerine fonksiyonun içine yaz.

  • Çok fazla iç içe decorator yığılıyorsa — kod okunamaz hale gelir.

  • Basit bir if kontrolü yeterliyse — overengineering yapma.

Sonuç

Bu yazıda Python decorator'larının tüm yüzlerini gördük. Öğrendiklerimizi özetleyelim:

  • Decorator, bir fonksiyonu alıp yeni bir fonksiyon döndüren callable'dır. @ sembolü sadece sözdizimsel şekerdir.

  • `*args, kwargs`** ile decorator'ın herhangi bir fonksiyona uygulanabilmesini sağlarsın.

  • `functools.wraps` her decorator'da kullanılmalı — orijinal fonksiyonun metadata'sını korur.

  • Parametreli decorator'lar üç katmanlı yapı gerektirir: factory → decorator → wrapper.

  • Class-based decorator'lar, durum tutman gereken durumlarda fonksiyon tabanlı decorator'lara alternatiftir.

  • Python'un yerleşik decorator'ları (@property, @classmethod, @staticmethod, @lru_cache) günlük kodlamada sıkça kullanılır.

  • Best practice: Tek sorumluluk, functools.wraps, dönüş değerini koruma, exception'ları yutmama.

Decorator'lar Python'un en zarif özelliklerinden biri. Doğru kullanıldığında kodunu temiz, okunabilir ve bakımı kolay tutar. Yanlış kullanıldığında ise debugging kabusu yaratır. Anahtar kural: decorator'ın ne yaptığını bilmelisin ve onu okuyan birinin de kolayca anlayabilmesi lazım. Şeffaf ol, sihir yapma.

Paylaş:
Son güncelleme: Jun 04, 2026

Yorumlar

Giriş yapın ve yorum bırakın.

Henüz yorum yok

Düşüncelerinizi paylaşan ilk siz olun!

Bu yazıyı beğendiniz mi?

Bültene abone olun ve yeni yazılardan ilk siz haberdar olun. Spam yok, söz.

İlgili Yazılar