← Kursa Dön
📄 Text · 20 min

Decorator Pattern

Bir fonksiyonun çalışma süresini ölçmek istiyorsun. Her fonksiyona başlangıç/bitiş zamanı kodu mu ekleyeceksin? Ya da her fonksiyona log mu yazacaksın? Peki ya aynı "ek özelliği" 50 farklı fonksiyona eklemen gerekirse?

İşte decorator'lar tam da bu sorunu çözer. Fonksiyonun kendisine dokunmadan, ona yeni özellikler eklemeni sağlar.

Bu derste decorator'ların nasıl çalıştığını sıfırdan anlayacak, kendi decorator'larını yazacak ve gerçek dünyada kullanılan pattern'ları öğreneceksin.


Decorator Nedir?

Hediye paketi analojisi: Bir hediye düşün. Hediyenin kendisi (fonksiyon) değişmiyor — ama onu güzel bir kağıda sarıyorsun (decorator). Dışarıdan bakınca paketli bir hediye görüyorsun. Paketi açınca içindeki hediye aynen duruyor.

Teknik olarak: Decorator, bir fonksiyonu parametre olarak alan ve yeni bir fonksiyon döndüren fonksiyondur. Bu yeni fonksiyon, orijinal fonksiyonu sarar (wrap) ve onun öncesine/sonrasına ek işlemler ekler.


Temel: Fonksiyonlar First-Class Object

Decorator'ları anlamak için önce fonksiyonların Python'da nasıl davrandığını hatırlayalım.

Fonksiyon Parametre Olarak Geçilebilir

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

def cagir_ve_yazdir(fonksiyon, deger):
    """Verilen fonksiyonu verilen değerle çağırır ve yazdırır."""
    sonuc = fonksiyon(deger)
    print(sonuc)

# Fonksiyonu parametre olarak geçiyoruz
cagir_ve_yazdir(selamla, "Ahmet")
# Merhaba, Ahmet!

Fonksiyon Başka Fonksiyondan Döndürülebilir

def selamlama_sec(resmi=False):
    """Duruma göre farklı selamlama fonksiyonu döndürür."""
    
    def resmi_selam(isim):
        return f"Sayın {isim}, hoş geldiniz."
    
    def samimi_selam(isim):
        return f"Selam {isim}, naber!"
    
    if resmi:
        return resmi_selam
    return samimi_selam

# Fonksiyon döndürülüyor
selam = selamlama_sec(resmi=True)
print(selam("Müdür Bey"))  # Sayın Müdür Bey, hoş geldiniz.

selam = selamlama_sec(resmi=False)
print(selam("Ali"))  # Selam Ali, naber!

Bu iki özellik decorator'ların temelidir. Bir fonksiyonu alıp, onu sarıp, yeni fonksiyonu döndürmek.


Wrapper Pattern: Fonksiyonu Sarmalama

Decorator'ın özü şu pattern'dır:

def benim_decorator(fonksiyon):
    """Bir fonksiyonu sarıp ek işlem ekler."""
    
    def wrapper(*args, **kwargs):
        # --- ÖNCESİNDE yapılacak işlemler ---
        print(f"🔵 {fonksiyon.__name__} çağrılıyor...")
        
        # Orijinal fonksiyonu çağır
        sonuc = fonksiyon(*args, **kwargs)
        
        # --- SONRASINDA yapılacak işlemler ---
        print(f"🟢 {fonksiyon.__name__} tamamlandı.")
        
        return sonuc
    
    return wrapper

Bu decorator'ı nasıl kullanırız?

def benim_decorator(fonksiyon):
    def wrapper(*args, **kwargs):
        print(f"🔵 {fonksiyon.__name__} çağrılıyor...")
        sonuc = fonksiyon(*args, **kwargs)
        print(f"🟢 {fonksiyon.__name__} tamamlandı.")
        return sonuc
    return wrapper

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

# Decorator'ı manuel uygula
topla = benim_decorator(topla)

# Artık topla() çağrıldığında ek işlemler de çalışır
sonuc = topla(3, 5)
# 🔵 topla çağrılıyor...
# 🟢 topla tamamlandı.
print(sonuc)  # 8

Olan şey:

  1. benim_decorator(topla)wrapper fonksiyonunu döndürür

  2. topla = wrapper → artık topla dediğimizde wrapper çağrılır

  3. wrapper içinde orijinal topla (closure ile hatırlanan) çağrılır


@ Syntax Sugar

Her fonksiyona fonksiyon = decorator(fonksiyon) yazmak zahmetli. Python bize @ söz dizimini sunar:

def benim_decorator(fonksiyon):
    def wrapper(*args, **kwargs):
        print(f"🔵 {fonksiyon.__name__} çağrılıyor...")
        sonuc = fonksiyon(*args, **kwargs)
        print(f"🟢 {fonksiyon.__name__} tamamlandı.")
        return sonuc
    return wrapper

@benim_decorator       # ← Bu satır: topla = benim_decorator(topla)
def topla(a, b):
    """İki sayıyı toplar."""
    return a + b

@benim_decorator       # ← Bu satır: carp = benim_decorator(carp)
def carp(a, b):
    """İki sayıyı çarpar."""
    return a * b

print(topla(3, 5))  # 8 (+ log çıktıları)
print(carp(4, 7))   # 28 (+ log çıktıları)

@decorator sadece syntactic sugar — yani güzel görünen bir kısayol. Arka planda aynı şey oluyor.


functools.wraps: Metadata Koruma

Decorator uyguladığında bir sorun var: orijinal fonksiyonun adı ve docstring'i kaybolur.

def benim_decorator(fonksiyon):
    def wrapper(*args, **kwargs):
        return fonksiyon(*args, **kwargs)
    return wrapper

@benim_decorator
def topla(a, b):
    """İki sayıyı toplar."""
    return a + b

print(topla.__name__)  # wrapper ← "topla" değil!
print(topla.__doc__)   # None ← Docstring kayboldu!
help(topla)            # wrapper(*args, **kwargs) ← Yanlış bilgi!

Çünkü topla artık wrapper fonksiyonuna işaret ediyor. Bunu düzeltmek için functools.wraps kullanırız:

from functools import wraps

def benim_decorator(fonksiyon):
    @wraps(fonksiyon)  # ← Bu satır metadata'yı korur
    def wrapper(*args, **kwargs):
        return fonksiyon(*args, **kwargs)
    return wrapper

@benim_decorator
def topla(a, b):
    """İki sayıyı toplar."""
    return a + b

print(topla.__name__)  # topla ✅
print(topla.__doc__)   # İki sayıyı toplar. ✅

⚠️ Dikkat: Her decorator'da @functools.wraps(fonksiyon) kullan. Bu bir zorunluluk değil ama kullanmamak debugging ve dokümantasyon sorunlarına yol açar. Profesyonel kodda wraps olmazsa code review'dan geçmez.


Pratik Decorator Örnekleri

1. @timer — Çalışma Süresini Ölç

import time
from functools import wraps

def timer(fonksiyon):
    """Fonksiyonun çalışma süresini ölçer."""
    @wraps(fonksiyon)
    def wrapper(*args, **kwargs):
        baslangic = time.perf_counter()
        sonuc = fonksiyon(*args, **kwargs)
        bitis = time.perf_counter()
        sure = bitis - baslangic
        print(f"⏱️ {fonksiyon.__name__} → {sure:.4f} saniye")
        return sonuc
    return wrapper

@timer
def agir_islem(n):
    """N'e kadar olan sayıların toplamını hesaplar."""
    return sum(range(n))

@timer
def bekle(saniye):
    """Belirli süre bekler."""
    time.sleep(saniye)

sonuc = agir_islem(10_000_000)
# ⏱️ agir_islem → 0.2341 saniye

bekle(1)
# ⏱️ bekle → 1.0012 saniye

Bu decorator'ı projelerde performans ölçümü için kullanabilirsin. Fonksiyonun kendisine dokunmadın — sadece üzerine @timer yazdın.

2. @retry — Başarısız İşlemi Tekrar Dene

import time
from functools import wraps

def retry(max_deneme=3, bekleme=1):
    """Başarısız fonksiyonu belirli sayıda tekrar dener."""
    def decorator(fonksiyon):
        @wraps(fonksiyon)
        def wrapper(*args, **kwargs):
            for deneme in range(1, max_deneme + 1):
                try:
                    return fonksiyon(*args, **kwargs)
                except Exception as e:
                    print(f"⚠️ Deneme {deneme}/{max_deneme} başarısız: {e}")
                    if deneme < max_deneme:
                        print(f"   {bekleme}s bekleniyor...")
                        time.sleep(bekleme)
                    else:
                        print(f"❌ Tüm denemeler başarısız!")
                        raise
        return wrapper
    return decorator

@retry(max_deneme=3, bekleme=0.5)
def guvenilmez_api():
    """Bazen başarısız olan bir API çağrısı simülasyonu."""
    import random
    if random.random() < 0.7:  # %70 ihtimalle başarısız
        raise ConnectionError("Sunucu yanıt vermedi!")
    return {"status": "ok", "data": [1, 2, 3]}

# Çağır — başarısız olursa otomatik tekrar dener
try:
    sonuc = guvenilmez_api()
    print(f"✅ Başarılı: {sonuc}")
except ConnectionError:
    print("Tüm denemeler başarısız oldu.")

3. @cache (Memoization) — Sonuçları Önbelleğe Al

from functools import wraps

def cache(fonksiyon):
    """Fonksiyon sonuçlarını önbelleğe alır (memoization)."""
    onbellek = {}
    
    @wraps(fonksiyon)
    def wrapper(*args):
        if args in onbellek:
            print(f"  📦 Cache hit: {fonksiyon.__name__}{args}")
            return onbellek[args]
        
        print(f"  🔄 Hesaplanıyor: {fonksiyon.__name__}{args}")
        sonuc = fonksiyon(*args)
        onbellek[args] = sonuc
        return sonuc
    
    wrapper.cache_clear = lambda: onbellek.clear()
    wrapper.cache_info = lambda: dict(size=len(onbellek), entries=list(onbellek.keys()))
    
    return wrapper

@cache
def fibonacci(n):
    """N'inci Fibonacci sayısını hesaplar."""
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))
# İlk çağrıda hesaplar, sonraki çağrılarda cache'den döner
# 55

print(fibonacci(8))
# 📦 Cache hit! Çünkü fibonacci(10) hesaplanırken fibonacci(8) de hesaplandı
# 21

💡 İpucu: Python 3.9+ sürümlerde functools.cache ve functools.lru_cache built-in olarak gelir. Kendi cache decorator'ını yazmana gerek yok:

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(100))  # Anında hesaplar
print(fibonacci.cache_info())
# CacheInfo(hits=98, misses=101, maxsize=128, currsize=101)

4. @validate — Girdi Doğrulama

from functools import wraps

def validate_types(**tip_bilgileri):
    """Parametre tiplerini kontrol eden decorator."""
    def decorator(fonksiyon):
        @wraps(fonksiyon)
        def wrapper(*args, **kwargs):
            # Positional argümanları kontrol et
            param_isimleri = fonksiyon.__code__.co_varnames
            for i, (deger, isim) in enumerate(zip(args, param_isimleri)):
                if isim in tip_bilgileri:
                    beklenen_tip = tip_bilgileri[isim]
                    if not isinstance(deger, beklenen_tip):
                        raise TypeError(
                            f"'{isim}' parametresi {beklenen_tip.__name__} olmalı, "
                            f"{type(deger).__name__} verildi!"
                        )
            
            # Keyword argümanları kontrol et
            for isim, deger in kwargs.items():
                if isim in tip_bilgileri:
                    beklenen_tip = tip_bilgileri[isim]
                    if not isinstance(deger, beklenen_tip):
                        raise TypeError(
                            f"'{isim}' parametresi {beklenen_tip.__name__} olmalı, "
                            f"{type(deger).__name__} verildi!"
                        )
            
            return fonksiyon(*args, **kwargs)
        return wrapper
    return decorator

@validate_types(ad=str, yas=int, maas=(int, float))
def calisanOlustur(ad, yas, maas):
    return {"ad": ad, "yas": yas, "maas": maas}

print(calisanOlustur("Ahmet", 30, 15000.0))
# {'ad': 'Ahmet', 'yas': 30, 'maas': 15000.0} ✅

# calisanOlustur(123, 30, 15000)
# TypeError: 'ad' parametresi str olmalı, int verildi!

Parametreli Decorator

Şimdiye kadar gördüğümüz @timer parametresizdi. Ama @retry(max_deneme=3) gibi parametreli decorator nasıl yazılır?

Anahtar: üç katmanlı fonksiyon.

from functools import wraps

def tekrarla(kac_kere):
    """Fonksiyonu belirtilen sayıda tekrar çalıştıran decorator."""
    def decorator(fonksiyon):
        @wraps(fonksiyon)
        def wrapper(*args, **kwargs):
            sonuc = None
            for i in range(kac_kere):
                print(f"  Çalıştırma {i+1}/{kac_kere}")
                sonuc = fonksiyon(*args, **kwargs)
            return sonuc
        return wrapper
    return decorator

@tekrarla(kac_kere=3)  # tekrarla(3) → decorator döndürür → decorator(selamla) → wrapper
def selamla(isim):
    print(f"Merhaba, {isim}!")

selamla("Ali")

Çıktı:

  Çalıştırma 1/3
Merhaba, Ali!
  Çalıştırma 2/3
Merhaba, Ali!
  Çalıştırma 3/3
Merhaba, Ali!

Katmanlar:

  1. tekrarla(kac_kere=3)decorator fonksiyonunu döndürür

  2. decorator(selamla)wrapper fonksiyonunu döndürür

  3. wrapper("Ali") → asıl işi yapar

Hem Parametreli Hem Parametresiz Çalışan Decorator

Bazen @decorator ve @decorator(param) her ikisini de desteklemek istersin:

from functools import wraps

def log(fonksiyon=None, *, seviye="INFO"):
    """Hem @log hem @log(seviye="DEBUG") olarak kullanılabilir."""
    def decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            print(f"[{seviye}] {fn.__name__} çağrılıyor")
            return fn(*args, **kwargs)
        return wrapper
    
    if fonksiyon is not None:
        # @log şeklinde kullanıldı (parametresiz)
        return decorator(fonksiyon)
    
    # @log(seviye="DEBUG") şeklinde kullanıldı
    return decorator

@log
def fonksiyon_a():
    print("A çalıştı")

@log(seviye="DEBUG")
def fonksiyon_b():
    print("B çalıştı")

fonksiyon_a()
# [INFO] fonksiyon_a çağrılıyor
# A çalıştı

fonksiyon_b()
# [DEBUG] fonksiyon_b çağrılıyor
# B çalıştı

Çoklu Decorator: Stacking Sırası

Bir fonksiyona birden fazla decorator uygulayabilirsin:

from functools import wraps

def kalin(fonksiyon):
    @wraps(fonksiyon)
    def wrapper(*args, **kwargs):
        return f"<b>{fonksiyon(*args, **kwargs)}</b>"
    return wrapper

def italik(fonksiyon):
    @wraps(fonksiyon)
    def wrapper(*args, **kwargs):
        return f"<i>{fonksiyon(*args, **kwargs)}</i>"
    return wrapper

def alti_cizili(fonksiyon):
    @wraps(fonksiyon)
    def wrapper(*args, **kwargs):
        return f"<u>{fonksiyon(*args, **kwargs)}</u>"
    return wrapper

@kalin
@italik
@alti_cizili
def mesaj():
    return "Merhaba Dünya"

print(mesaj())
# <b><i><u>Merhaba Dünya</u></i></b>

Sıralama aşağıdan yukarı uygulanır:

  1. Önce @alti_cizilimesaj = alti_cizili(mesaj)

  2. Sonra @italikmesaj = italik(mesaj)

  3. Son olarak @kalinmesaj = kalin(mesaj)

Yani @kalin @italik @alti_cizili mesaj aslında kalin(italik(alti_cizili(mesaj))) demek.

Daha gerçekçi bir örnek:

from functools import wraps
import time

def timer(fonksiyon):
    @wraps(fonksiyon)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        sonuc = fonksiyon(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"⏱️ {fonksiyon.__name__}: {elapsed:.4f}s")
        return sonuc
    return wrapper

def log(fonksiyon):
    @wraps(fonksiyon)
    def wrapper(*args, **kwargs):
        print(f"📝 {fonksiyon.__name__} çağrıldı: args={args}, kwargs={kwargs}")
        sonuc = fonksiyon(*args, **kwargs)
        print(f"📝 {fonksiyon.__name__} döndü: {sonuc}")
        return sonuc
    return wrapper

@timer       # 2. sırada uygulanır (dışta)
@log         # 1. sırada uygulanır (içte)
def hesapla(n):
    return sum(range(n))

hesapla(1000000)
# 📝 hesapla çağrıldı: args=(1000000,), kwargs={}
# 📝 hesapla döndü: 499999500000
# ⏱️ hesapla: 0.0234s

@timer dışta olduğu için @log'un süresini de dahil ediyor. Sıralamayı değiştirsen sonuç farklı olurdu.


Class-Based Decorator

Decorator'ı bir class olarak da yazabilirsin. __call__ metodu sayesinde class instance'ı fonksiyon gibi çağrılabilir:

from functools import wraps

class CountCalls:
    """Fonksiyonun kaç kere çağrıldığını sayar."""
    
    def __init__(self, fonksiyon):
        wraps(fonksiyon)(self)  # metadata'yı koru
        self.fonksiyon = fonksiyon
        self.sayac = 0
    
    def __call__(self, *args, **kwargs):
        self.sayac += 1
        print(f"📊 {self.fonksiyon.__name__} → {self.sayac}. çağrı")
        return self.fonksiyon(*args, **kwargs)

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

print(selamla("Ali"))    # 📊 selamla → 1. çağrı → Merhaba, Ali!
print(selamla("Veli"))   # 📊 selamla → 2. çağrı → Merhaba, Veli!
print(selamla("Ayşe"))   # 📊 selamla → 3. çağrı → Merhaba, Ayşe!

# Sayaca erişebilirsin
print(f"Toplam çağrı: {selamla.sayac}")  # 3

Class-based decorator'lar state tutmak gerektiğinde kullanışlı. Ama çoğu durumda fonksiyon-based decorator yeterli.

Parametreli Class-Based Decorator

class RateLimit:
    """Fonksiyonun belirli aralıklarla çağrılmasını zorlar."""
    
    def __init__(self, min_aralik=1.0):
        self.min_aralik = min_aralik
        self.son_cagri = 0
    
    def __call__(self, fonksiyon):
        @wraps(fonksiyon)
        def wrapper(*args, **kwargs):
            import time
            simdi = time.time()
            gecen = simdi - self.son_cagri
            
            if gecen < self.min_aralik:
                kalan = self.min_aralik - gecen
                print(f"⏳ Rate limit! {kalan:.1f}s beklemelisiniz.")
                return None
            
            self.son_cagri = simdi
            return fonksiyon(*args, **kwargs)
        return wrapper

@RateLimit(min_aralik=2.0)
def api_cagri():
    return "Veri alındı!"

print(api_cagri())  # Veri alındı!
print(api_cagri())  # ⏳ Rate limit! 1.8s beklemelisiniz. → None

Built-in Decorator'lar

Python birçok yerleşik decorator sunar. Bunlardan en önemli üçünü kısaca hatırlayalım:

@property — Attribute Gibi Erişim

class Daire:
    def __init__(self, yaricap):
        self._yaricap = yaricap
    
    @property
    def yaricap(self):
        """Yarıçapı döndürür."""
        return self._yaricap
    
    @yaricap.setter
    def yaricap(self, deger):
        if deger < 0:
            raise ValueError("Yarıçap negatif olamaz!")
        self._yaricap = deger
    
    @property
    def alan(self):
        """Alanı hesaplar (salt okunur)."""
        return 3.14159 * self._yaricap ** 2

d = Daire(5)
print(d.yaricap)   # 5 (getter — fonksiyon çağrısı gibi görünmüyor!)
print(d.alan)      # 78.53975

d.yaricap = 10     # setter
print(d.alan)      # 314.159

# d.alan = 100     # AttributeError: setter yok, salt okunur
# d.yaricap = -5   # ValueError: Yarıçap negatif olamaz!

@staticmethod — Instance Gerektirmeyen Metod

class Matematik:
    @staticmethod
    def topla(a, b):
        return a + b
    
    @staticmethod
    def faktoriyel(n):
        sonuc = 1
        for i in range(2, n + 1):
            sonuc *= i
        return sonuc

# Instance oluşturmadan çağırabilirsin
print(Matematik.topla(3, 5))       # 8
print(Matematik.faktoriyel(5))     # 120

@classmethod — Class'a Bağlı Metod

class Tarih:
    def __init__(self, gun, ay, yil):
        self.gun = gun
        self.ay = ay
        self.yil = yil
    
    @classmethod
    def from_string(cls, tarih_str):
        """String'den Tarih oluşturur (alternatif constructor)."""
        gun, ay, yil = map(int, tarih_str.split("-"))
        return cls(gun, ay, yil)
    
    @classmethod
    def bugun(cls):
        """Bugünün tarihini döndürür."""
        from datetime import date
        today = date.today()
        return cls(today.day, today.month, today.year)
    
    def __repr__(self):
        return f"{self.gun:02d}/{self.ay:02d}/{self.yil}"

t1 = Tarih(15, 1, 2024)
t2 = Tarih.from_string("20-06-2024")
t3 = Tarih.bugun()

print(t1)  # 15/01/2024
print(t2)  # 20/06/2024
print(t3)  # bugünün tarihi

Gerçek Dünya Decorator Pattern'ları

Authentication Decorator (Web Framework Tarzı)

from functools import wraps

# Basit kullanıcı veritabanı
aktif_oturumlar = {"abc123": {"ad": "Ahmet", "rol": "admin"}}

def login_required(fonksiyon):
    """Kullanıcının giriş yapmış olmasını zorunlu kılar."""
    @wraps(fonksiyon)
    def wrapper(token, *args, **kwargs):
        if token not in aktif_oturumlar:
            print("❌ Yetkisiz erişim! Lütfen giriş yapın.")
            return None
        
        kullanici = aktif_oturumlar[token]
        print(f"✅ Hoş geldin, {kullanici['ad']}!")
        return fonksiyon(token, *args, **kwargs)
    return wrapper

def admin_required(fonksiyon):
    """Admin yetkisi gerektirir."""
    @wraps(fonksiyon)
    def wrapper(token, *args, **kwargs):
        kullanici = aktif_oturumlar.get(token)
        if not kullanici or kullanici["rol"] != "admin":
            print("❌ Bu işlem için admin yetkisi gerekli!")
            return None
        return fonksiyon(token, *args, **kwargs)
    return wrapper

@login_required
def profil_goster(token):
    kullanici = aktif_oturumlar[token]
    print(f"Profil: {kullanici}")

@login_required
@admin_required
def kullanici_sil(token, hedef_id):
    print(f"Kullanıcı #{hedef_id} silindi.")

# Test
profil_goster("abc123")       # ✅ Hoş geldin, Ahmet! → Profil bilgisi
profil_goster("yanlis_token") # ❌ Yetkisiz erişim!
kullanici_sil("abc123", 42)   # ✅ Admin → Kullanıcı #42 silindi.

Deprecation Warning Decorator

import warnings
from functools import wraps

def deprecated(yeni_fonksiyon=None):
    """Fonksiyonun kullanımdan kaldırıldığını bildirir."""
    def decorator(fonksiyon):
        @wraps(fonksiyon)
        def wrapper(*args, **kwargs):
            mesaj = f"⚠️ {fonksiyon.__name__}() kullanımdan kaldırıldı."
            if yeni_fonksiyon:
                mesaj += f" Bunun yerine {yeni_fonksiyon}() kullanın."
            warnings.warn(mesaj, DeprecationWarning, stacklevel=2)
            return fonksiyon(*args, **kwargs)
        return wrapper
    return decorator

@deprecated(yeni_fonksiyon="topla_yeni")
def topla_eski(a, b):
    return a + b

def topla_yeni(a, b):
    """Yeni ve geliştirilmiş toplama fonksiyonu."""
    return a + b

# Eski fonksiyon hâlâ çalışır ama uyarı verir
sonuc = topla_eski(3, 5)  # DeprecationWarning uyarısı gösterir

Singleton Decorator

from functools import wraps

def singleton(cls):
    """Bir class'ın sadece tek bir instance'ı olmasını sağlar."""
    instances = {}
    
    @wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    
    return get_instance

@singleton
class Database:
    def __init__(self):
        print("🔌 Veritabanı bağlantısı oluşturuldu!")
        self.connected = True

db1 = Database()  # 🔌 Veritabanı bağlantısı oluşturuldu!
db2 = Database()  # Sessiz — yeni instance oluşturmuyor!

print(db1 is db2)  # True — aynı nesne!

Decorator Yazarken Dikkat Edilecekler

1. Her Zaman *args, **kwargs Kullan

# ❌ Kötü: sadece belirli parametrelere izin verir
def kotu_decorator(fonksiyon):
    def wrapper(a, b):  # Sadece 2 parametreli fonksiyonlarda çalışır!
        return fonksiyon(a, b)
    return wrapper

# ✅ İyi: her fonksiyona uyar
def iyi_decorator(fonksiyon):
    @wraps(fonksiyon)
    def wrapper(*args, **kwargs):
        return fonksiyon(*args, **kwargs)
    return wrapper

2. Her Zaman @wraps Kullan

from functools import wraps

def decorator(fonksiyon):
    @wraps(fonksiyon)  # ← Bu satırı ASLA unutma
    def wrapper(*args, **kwargs):
        return fonksiyon(*args, **kwargs)
    return wrapper

3. Return Değerini Unutma

# ❌ Kötü: return'ü unuttuk!
def kotu(fonksiyon):
    @wraps(fonksiyon)
    def wrapper(*args, **kwargs):
        fonksiyon(*args, **kwargs)  # Return yok!
    return wrapper

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

print(topla(3, 5))  # None! 😱 Return kayboldu!

# ✅ İyi: return'ü geçir
def iyi(fonksiyon):
    @wraps(fonksiyon)
    def wrapper(*args, **kwargs):
        return fonksiyon(*args, **kwargs)  # Return'ü geçir!
    return wrapper

💡 İpucu: Decorator yazarken şu şablonu kullan ve her seferinde buradan başla:

from functools import wraps

def decorator_adi(fonksiyon):
    """Decorator'ın ne yaptığını açıkla."""
    @wraps(fonksiyon)
    def wrapper(*args, **kwargs):
        # ÖNCESİNDE: ek işlemler
        sonuc = fonksiyon(*args, **kwargs)
        # SONRASINDA: ek işlemler
        return sonuc
    return wrapper

Decorator Akış Diyagramı

@my_decorator
def my_function():
    ...

↓ Eşdeğeri:
my_function = my_decorator(my_function)

↓ my_decorator ne yapar?
def my_decorator(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        # 1. Ön işlem (log, auth, timer start...)
        result = fn(*args, **kwargs)   # 2. Orijinal fonksiyon
        # 3. Son işlem (log, timer stop...)
        return result                  # 4. Sonucu döndür
    return wrapper                     # 5. wrapper'ı döndür

↓ my_function("hello") çağrıldığında:
→ Aslında wrapper("hello") çalışır
→ wrapper içinde orijinal fn("hello") çağrılır
→ Ek işlemler + orijinal sonuç döner

Özet

  • Decorator, bir fonksiyonu parametre olarak alıp onu saran yeni bir fonksiyon döndüren yapıdır. @decorator söz dizimi fonksiyon = decorator(fonksiyon) ile eşdeğerdir.

  • Wrapper pattern: def wrapper(*args, **kwargs) ile her türlü fonksiyonu sarmalayabilirsin. Orijinal fonksiyonu içeride çağırıp öncesine/sonrasına ek işlemler eklersin.

  • `functools.wraps` orijinal fonksiyonun __name__, __doc__ gibi metadata'sını korur. Her decorator'da kullanmak zorunlu değil ama şiddetle tavsiye edilir.

  • Parametreli decorator üç katmanlı fonksiyon gerektirir: dış(param)decorator(fn)wrapper(*args, **kwargs).

  • Çoklu decorator aşağıdan yukarıya uygulanır: @A @B @C def f()f = A(B(C(f))).

  • Pratik decorator'lar: @timer (performans), @retry (hata toleransı), @cache (memoization), @validate (girdi doğrulama), @login_required (yetkilendirme). Built-in'ler: @property, @staticmethod, @classmethod.