Flask veya FastAPI'yi bir haftadan fazla kullandıysanız, zaten decorator'lar kullanmışsınızdır — @app.route, @login_required, @pytest.mark.parametrize. İlk başta sihir gibi hissettirirler, sonra biri gerçekte ne olduğunu açıklar ve hemen anlaşılır. Bir decorator, başka bir fonksiyonu saran sadece bir fonksiyondur. @ sözdizimi salt sözdizimsel şekerdir — bir fonksiyon tanımının üzerindeki @my_decorator, hemen ardından func = my_decorator(func) yazmakla tamamen eşdeğerdir. İşte tüm sır bu. Geri kalanı o tek fikir üzerine inşa edilmiş kalıplardan ibarettir. Bu makale, zihinsel modeli sıfırdan kurar, ardından gerçekten kullanacağınız kalıpları ele alır: @functools.wraps, argümanlı decorator'lar, sınıf tabanlı decorator'lar ve @lru_cache, @dataclass ve üstel geri çekilmeli düzgün bir @retry dahil gerçek dünyadan bir dizi örnek.

Fonksiyonlar Birinci Sınıf Nesnelerdir

Decorator'ların mantıklı gelmesi için Python hakkında bir gerçeği iyi kavramanız gerekir: fonksiyonlar nesnedir. Bunları değişkenlere atayabilir, diğer fonksiyonlara argüman olarak geçirebilir, fonksiyonlardan döndürebilir ve listelerde veya sözlüklerde saklayabilirsiniz. Bir şeyin fonksiyon olması nedeniyle özel bir şey olmaz.

python
def greet(name: str) -> str:
    return f"Hello, {name}"

# Assign to a variable — no () means we're not calling it, just referencing it
say_hello = greet
print(say_hello("Alice"))   # 'Hello, Alice'

# Pass a function as an argument
def run_twice(fn, value):
    return fn(value), fn(value)

run_twice(greet, "Bob")     # ('Hello, Bob', 'Hello, Bob')

# Return a function from another function — this is a "factory"
def make_prefixer(prefix: str):
    def prefixed_greet(name: str) -> str:
        return f"{prefix}, {name}"
    return prefixed_greet

morning_hello = make_prefixer("Good morning")
morning_hello("Carol")      # 'Good morning, Carol'

İçteki prefixed_greet fonksiyonu, çevreleyen kapsamdaki prefix değişkenini "kapatır" — make_prefixer döndükten sonra bile, iç fonksiyon hâlâ prefix'e erişime sahiptir. Bu bir closure'dır ve decorator'ları işleten mekanizmadır. Python'ın kapsam kuralları hakkındaki belgeleri tam tabloyu görmek istiyorsanız bunu ayrıntılı olarak açıklar.

Sıfırdan Bir Decorator Oluşturma

Bir decorator, bir fonksiyon alan ve (genellikle değiştirilmiş) bir fonksiyon döndüren bir fonksiyondur. Klasik ilk örnek, herhangi bir fonksiyonu saran ve ne kadar sürdüğünü günlüğe kaydeden bir zamanlama decorator'ıdır.

python
import time
import functools

def timer(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = fn(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{fn.__name__} finished in {elapsed:.4f}s")
        return result
    return wrapper

@timer
def fetch_user_records(db, user_id: int):
    """Fetch all records for a given user from the database."""
    return db.query("SELECT * FROM records WHERE user_id = ?", user_id)

# Calling fetch_user_records is now: timer(fetch_user_records)(db, 42)
# Which is exactly what @timer desugars to

Dikkat edilmesi gereken birkaç şey: wrapper, orijinal fonksiyona herhangi bir argüman kombinasyonunu imzasını bilmeden iletebilmek için *args, **kwargs kullanır. Dönüş değerini result'ta yakalar ve döndürür, böylece sarılmış fonksiyon çağıranın bakış açısından aynı şekilde davranmaya devam eder — sadece ekstra bir yazdırma yan etkisi vardır. Zamanlamayı kaldırın ve yazacağınız neredeyse her decorator'ın iskeletini elde etmiş olursunuz.

Desugarlama kuralı: Bir fonksiyon tanımının üzerinde @some_decorator gördüğünüzde, bunu def bloğundan hemen sonra yazılmış fn = some_decorator(fn) ile zihinsel olarak değiştirin. İkisi tamamen eşdeğerdir. Sihir yok — bu bir fonksiyon çağrısıdır.

Neden @functools.wraps Kullanmanız Gerekir

Yukarıdaki örnekte wrapper üzerinde bir @functools.wraps(fn) satırı var. Bu isteğe bağlı değildir. Onsuz, dekore edilmiş fonksiyonunuz kimliğini kaybeder — __name__, __doc__ ve __qualname__ nitelikleri iç wrapper fonksiyonunkilerle değiştirilir. Bu, birkaç gerçek durumda ince kırılmalara neden olur:

  • Docstring'ler kaybolur. help(fetch_user_records), "Fetch all records for a given user..." yerine wrapper'ın boş docstring'ini gösterir.
  • Traceback'ler yalan söyler. Sarılmış fonksiyon içinde bir istisna atıldığında, traceback gerçek fonksiyon adı yerine wrapper gösterir — hata ayıklaması zor.
  • İntrospeksiyon bozulur. pytest, Flask'ın yönlendirme sistemi ve inspect.signature() gibi araçlar __name__ ve __wrapped__'a güvenir. Flask'ın yönlendiricisi iki route aynı (wrapper) adını paylaşırsa hata fırlatır.
  • functools.lru_cache ve benzeri araçlar, önbellek anahtarlaması için fonksiyonun kimliğini kullanır — wraps olmadan şaşırtıcı önbellek çakışmaları yaşanabilir.
python
import functools

# Without @functools.wraps — broken
def bad_timer(fn):
    def wrapper(*args, **kwargs):
        return fn(*args, **kwargs)
    return wrapper

@bad_timer
def process_batch(batch_id: int):
    """Process a single batch job."""
    pass

print(process_batch.__name__)   # 'wrapper'   ← wrong
print(process_batch.__doc__)    # None         ← docstring gone

# With @functools.wraps — correct
def good_timer(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        return fn(*args, **kwargs)
    return wrapper

@good_timer
def process_batch(batch_id: int):
    """Process a single batch job."""
    pass

print(process_batch.__name__)   # 'process_batch'  ← correct
print(process_batch.__doc__)    # 'Process a single batch job.'  ← preserved

functools.wraps kendisi bir decorator'dır — __module__, __name__, __qualname__, __annotations__, __doc__'u kopyalar ve __wrapped__'ı orijinal fonksiyona ayarlar. Her wrapper fonksiyonunda kullanın, nokta. Bunu yapmamak için hiçbir neden yoktur.

Argümanlı Decorator'lar

Düz bir decorator bir fonksiyon alır ve bir fonksiyon döndürür. Argümanlı bir decorator'ın bir seviye daha fazlasına ihtiyacı vardır: argümanları alan ve bir decorator döndüren bir fonksiyon. Bu üç iç içe geçme seviyesidir ve neredeyse herkesi ilk gördüğünde şaşırtır. İşte bir istisna durumunda bir fonksiyonu max_attempts kereye kadar yeniden deneyen bir @retry decorator'ı:

python
import functools
import time

def retry(max_attempts: int = 3, delay: float = 1.0, backoff: float = 2.0):
    """
    Retry a function on exception with exponential backoff.

    Usage:
        @retry(max_attempts=5, delay=0.5, backoff=2.0)
        def call_external_api(endpoint: str) -> dict:
            ...
    """
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            current_delay = delay
            last_exception = None

            for attempt in range(1, max_attempts + 1):
                try:
                    return fn(*args, **kwargs)
                except Exception as exc:
                    last_exception = exc
                    if attempt < max_attempts:
                        print(
                            f"{fn.__name__}: attempt {attempt}/{max_attempts} failed "
                            f"({exc!r}), retrying in {current_delay:.1f}s..."
                        )
                        time.sleep(current_delay)
                        current_delay *= backoff
                    else:
                        print(f"{fn.__name__}: all {max_attempts} attempts failed.")

            raise last_exception

        return wrapper
    return decorator

@retry(max_attempts=4, delay=0.5, backoff=2.0)
def fetch_price_data(ticker: str) -> dict:
    """Fetch stock price data from external API."""
    response = requests.get(f"https://api.example.com/prices/{ticker}", timeout=5)
    response.raise_for_status()
    return response.json()

Python @retry(max_attempts=4, delay=0.5, backoff=2.0)'ı işlerken çağrı zinciri: önce retry(max_attempts=4, delay=0.5, backoff=2.0) çağrılır ve decorator döndürür. Sonra decorator(fetch_price_data) çağrılır ve wrapper döndürür. Son olarak fetch_price_data, wrapper'a yeniden bağlanır. Yani @retry(...), fetch_price_data = retry(...)(fetch_price_data)'dır — üç çağrı, iki sarma katmanı. Bu kalıbı bir kez gördüğünüzde decorator factory'leri kafa karıştırmayı bırakır.

Sınıf Tabanlı Decorator'lar

__call__'ı tanımlayarak bir decorator'ı sınıf olarak da uygulayabilirsiniz. Bu, decorator'ın çağrılar arasında durum tutması gerektiğinde kullanışlıdır — bir çağrı sayacı, bir önbellek, bağlantı havuzları — çünkü örnek değişkenler bu durum için closure değişkenlerinden daha doğal bir evdir.

python
import functools
import time

class RateLimiter:
    """
    Decorator that limits how often a function can be called.
    Raises RuntimeError if the function is called within `min_interval` seconds
    of the previous call.
    """

    def __init__(self, min_interval: float):
        self.min_interval = min_interval
        self._last_called: float = 0.0

    def __call__(self, fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            now = time.monotonic()
            since_last = now - self._last_called
            if since_last < self.min_interval:
                wait = self.min_interval - since_last
                raise RuntimeError(
                    f"{fn.__name__} called too soon — "
                    f"wait {wait:.2f}s before calling again."
                )
            self._last_called = now
            return fn(*args, **kwargs)
        return wrapper

# Usage — one call per 2 seconds
@RateLimiter(min_interval=2.0)
def send_sms_alert(phone: str, message: str) -> None:
    """Send an SMS alert via the gateway API."""
    sms_gateway.send(phone, message)

@RateLimiter(min_interval=2.0), tam olarak bir decorator factory gibi çalışır: RateLimiter(2.0) bir örnek oluşturur, sonra bu örnek __call__'a sahip olduğu için send_sms_alert ile çağrılır. Örnek, _last_called'ı bir nitelik olarak saklar — closure değişkeni uğraşı gerekmez. @ sözdiziminin alternatifler yerine neden seçildiğine dair tasarım gerekçesi için PEP 318'e (orijinal decorator önerisi) ve Python 3.9'da gelen gevşetilmiş decorator grameri için PEP 614'e bakın.

Standart Kütüphaneden Gerçek Dünya Decorator'ları

Kendinizinkini yazmadan önce stdlib'nin ihtiyacınız olanı zaten içerip içermediğini kontrol edin. functools'taki üç decorator, üretim Python kodunda sürekli karşılaşılır.

python
from functools import lru_cache, cache
import requests

# @cache — unbounded memoisation (Python 3.9+)
# Caches every unique set of arguments forever.
# Good for pure functions with a small domain of inputs.
@cache
def get_country_name(country_code: str) -> str:
    """Look up a country name from ISO 3166 code. Cached after first call."""
    response = requests.get(f"https://restcountries.com/v3.1/alpha/{country_code}")
    return response.json()[0]["name"]["common"]

get_country_name("DE")   # hits the API
get_country_name("DE")   # served from cache, no network call

# @lru_cache(maxsize=N) — bounded LRU cache
# Evicts least-recently-used entries once the cache hits `maxsize`.
# Better when the input domain is large and memory is a concern.
@lru_cache(maxsize=256)
def compute_discount(base_price: float, tier: str) -> float:
    """Heavy computation — price varies by tier. Cache the top 256 combinations."""
    discount_table = load_discount_table()          # expensive DB call
    rate = discount_table.get(tier, 0.0)
    return round(base_price * (1 - rate), 2)

# Inspect cache performance
print(compute_discount.cache_info())
# CacheInfo(hits=142, misses=14, maxsize=256, currsize=14)

@dataclass farklı bir kategoridedir — alan ek açıklamalarınızdan otomatik olarak __init__, __repr__ ve __eq__ üreten bir sınıf decorator'ıdır. Veri tutan sınıflardaki önemli miktarda tekrar kodu keser:

python
from dataclasses import dataclass, field
from typing import Optional
from datetime import datetime

@dataclass
class WebhookEvent:
    event_type: str
    source_id: int
    payload: dict
    received_at: datetime = field(default_factory=datetime.utcnow)
    retry_count: int = 0
    error_message: Optional[str] = None

# @dataclass generates all of this for free:
# - __init__(self, event_type, source_id, payload, received_at=..., retry_count=0, error_message=None)
# - __repr__ that shows all fields
# - __eq__ that compares field-by-field

event = WebhookEvent(event_type="order.created", source_id=9912, payload={"order_id": 44501})
print(event)
# WebhookEvent(event_type='order.created', source_id=9912, payload={...}, retry_count=0, ...)

@property decorator'ı bir metodu nitelik stili erişiciye dönüştürür — çağıranlar user.get_display_name() yerine user.display_name okur. Yazma sırasında doğrulama yapmak için @property.setter ile birleştirin:

python
class UserProfile:
    def __init__(self, first_name: str, last_name: str, email: str):
        self._first_name = first_name
        self._last_name = last_name
        self._email = email.strip().lower()

    @property
    def display_name(self) -> str:
        return f"{self._first_name} {self._last_name}"

    @property
    def email(self) -> str:
        return self._email

    @email.setter
    def email(self, value: str) -> None:
        if "@" not in value:
            raise ValueError(f"Invalid email address: {value!r}")
        self._email = value.strip().lower()

profile = UserProfile("Alice", "Smith", "  [email protected]  ")
print(profile.display_name)   # 'Alice Smith'
print(profile.email)          # '[email protected]'

profile.email = "[email protected]"    # setter runs validation
profile.email = "not-an-email"           # raises ValueError

Route Handler'lar için @require_auth Decorator'ı

Web çerçevelerindeki kimlik doğrulama kontrolleri, decorator kullanımı için ders kitabı bir örnek olayı. Her route handler'ın üstüne "kullanıcı oturum açmış mı?" kontrolünü kopyalamak yerine, onu bir kez decorator olarak yazıp nerede gerekiyorsa uygularsınız. İşte Flask ile çalışacak şekilde yazılmış, ancak herhangi bir çerçeveye aktarılabilir kalıp:

python
import functools
from flask import request, jsonify, g

def require_auth(fn):
    """
    Decorator that validates a Bearer token before the route handler runs.
    Sets g.current_user on success; returns 401 JSON on failure.
    """
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        auth_header = request.headers.get("Authorization", "")
        if not auth_header.startswith("Bearer "):
            return jsonify({"error": "Missing or malformed Authorization header"}), 401

        token = auth_header[len("Bearer "):]
        user = verify_token(token)          # your token validation logic
        if user is None:
            return jsonify({"error": "Invalid or expired token"}), 401

        g.current_user = user               # available to the route handler
        return fn(*args, **kwargs)
    return wrapper

# Usage — the auth check runs before the handler body
@app.route("/api/v1/reports/<int:report_id>")
@require_auth
def get_report(report_id: int):
    report = Report.query.get_or_404(report_id)
    if report.owner_id != g.current_user.id:
        return jsonify({"error": "Forbidden"}), 403
    return jsonify(report.to_dict())

Sırayı not edin: @app.route ilk gider (en dışta), @require_auth ikinci gider. Bu önemlidir — bir sonraki bölüme bakın. Kalıp doğal olarak genişler: @require_auth'ın kullanıcının var olduğunu doğruladıktan sonra g.current_user.role'ü kontrol eden bir @require_role("admin") decorator factory ekleyebilirsiniz.

Decorator'ları Yığmak — Sıra Önemlidir

Birden fazla decorator yığdığınızda, bunlar aşağıdan yukarıya uygulanır (fonksiyona en yakın decorator önce uygulanır), ancak fonksiyon çağrıldığında yukarıdan aşağıya çalışırlar. Bu insanları şaşırtır.

python
@decorator_a
@decorator_b
@decorator_c
def my_function():
    pass

# This is exactly equivalent to:
my_function = decorator_a(decorator_b(decorator_c(my_function)))

# When my_function() is called:
# 1. decorator_a's wrapper runs first (outermost)
# 2. decorator_b's wrapper runs second
# 3. decorator_c's wrapper runs third (innermost, closest to the real function)
# 4. The real my_function body runs
# 5. Unwinding: decorator_c → decorator_b → decorator_a
python
# Practical example: order matters for @timer and @retry
# If timer is outermost, it measures total time including retry wait periods.
# If retry is outermost, it measures only the final successful call.

@timer          # measures: total time across all retry attempts + sleep
@retry(max_attempts=3, delay=1.0)
def sync_with_partner_api(partner_id: int) -> dict:
    ...

# vs.

@retry(max_attempts=3, delay=1.0)  # measures: only the final successful call
@timer
def sync_with_partner_api(partner_id: int) -> dict:
    ...

# Usually you want @timer outermost — it tells you the real wall-clock cost of the operation.
# Think about what "calling this function" means to the caller, then wrap in that order.
Traceback ipucu: Dekore edilmiş bir fonksiyon istisna atarken, traceback çağrı yığınındaki her wrapper'ı gösterir. @functools.wraps'ı doğru kullandıysanız, adlar orijinal fonksiyonları yansıtacaktır. Bir sürü wrapper çerçevesi görürseniz, biri @functools.wraps'ı unutmuştur. Real Python decorator primer'ı, yığılmış decorator'ların nasıl hata ayıklanacağına dair iyi bir adım adım kılavuza sahiptir.

Özet

Taşıyacağınız zihinsel model: @decorator, fn = decorator(fn)'dir. Diğer her şey — argümanlı decorator'lar, sınıf tabanlı decorator'lar, yığılmış decorator'lar — o tek ikamenin bir varyasyonudur. Her iç wrapper'da @functools.wraps kullanın, sarılmış fonksiyona her zaman *args, **kwargs iletin ve sonucunu döndürün. Argümanlı decorator'ların üç iç içe geçme seviyesine ihtiyacı vardır: argüman fonksiyonu, decorator ve wrapper. Sınıf tabanlı decorator'lar, decorator'ınızın çağrılar arasında durum tutması gerektiğinde doğru tercihtir.

Daha fazla okuma için: decorator'lar hakkındaki Python sözlük girdisi kısa ama kesindir. Orijinal decorator önerisi PEP 318, alternatifler yerine @ sözdiziminin neden seçildiğine dair bağlam için okunmaya değer. Decorator'ları çok fazla veri işleme — dosya okuma, kayıt dönüştürme — yapan bir kod tabanında kullanıyorsanız, buradaki kalıplar Python File Handling'de ele alınanlarla doğal olarak eşleşir. Koleksiyonları dönüştürmek veya arama yapıları oluşturmak için decorator kullanıyorsanız, Python List Comprehensions bu tablonun veri dönüştürme tarafını ele alır. Dekore edilmiş fonksiyonlarınız JSON çıktısı döndürüyorsa ve bunu hızla incelemek istiyorsanız, bu sitedeki JSON Formatter bunun için kullanışlıdır.