Python Decorators (Dekoratörler): Fonksiyonlarını Güçlendir
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 wrapperBu 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:
Python
@zaman_olcersatırını görüyor.Alttaki
agir_islemfonksiyonunuzaman_olcer()fonksiyonuna parametre olarak gönderiyor.zaman_olcer()birwrapperfonksiyonu oluşturup döndürüyor.Artık
agir_islemismi, orijinal fonksiyona değil buwrapperfonksiyonuna işaret ediyor.agir_islem()çağrıldığında aslındawrapper()ç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 kaybolduBu 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?
tekrar_et(kac_kez=3)çağrılıyor →decoratorfonksiyonunu döndürüyor.decorator,merhaba_defonksiyonunu alıyor →wrapperfonksiyonunu döndürüyor.merhaba_deartıkwrapper'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.1023sBu ş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ı: 0Class-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 wrapper2. 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():
pass3. 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 wrapper4. 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 wrapperPython'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ışırBu 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.
Bu yazıyı beğendiniz mi?
Bültene abone olun ve yeni yazılardan ilk siz haberdar olun. Spam yok, söz.
Bu konuyu derinlemesine öğrenmek ister misin?
Python Programlama: Sıfırdan İleri Seviyeye
İlgili Yazılar
Python Generators ve Iterators: Bellek Verimli Programlamanın Sırrı
Python'da generator ve iterator yapıları nasıl çalışır? Lazy evaluation, yield, generator expression, pipeline pattern v...
Python Nedir? Neden Python Öğrenmelisiniz?
Python nedir, ne işe yarar? Yapay zeka, veri bilimi, web geliştirme ve otomasyon için neden Python? Kariyer fırsatları v...
Python'da Context Manager ve with Bloğu Derinlemesine
Python context manager: with bloğu, __enter__/__exit__, contextlib, @contextmanager decorator ve gerçek dünya kaynak yön...