# Python'da dekoratörler: nedir, nasıl çalışır ve nasıl yazılır

URL: https://skillaria.top/tr/guides/python/python-dekoratorler

Python'da dekoratör, bir fonksiyon alıp yeni bir fonksiyon (genellikle bir sarmalayıcı) döndüren fonksiyondur; çağrıdan önce ya da sonra loglama, süre ölçme, önbellekleme veya yetki kontrolü gibi davranış ekler. @dekoratör yazımı func = dekoratör(func) kısayoludur; sarmalayıcının orijinal fonksiyonun adını ve docstring'ini koruması için functools.wraps kullanılır.

Python'da dekoratör, bir fonksiyon alıp başka bir fonksiyon döndüren bir fonksiyondur; döndürülen genellikle orijinali saran bir sarmalayıcıdır ve çağrıya ek davranış kazandırır: loglama, süre ölçme, önbellekleme, yetki kontrolü. Dekoratörler, bir fonksiyonun üstüne `@ad` satırı yazılarak uygulanır ve Flask, FastAPI, pytest gibi kütüphanelerin ve standart kütüphanenin her yerinde görülür.

## Dekoratör sade bir dille nedir

Bir hediye ve onu saran kâğıdı düşünün. Hediye (fonksiyon) aynı kalır, ama dışına ilk önce devreye giren bir katman eklenir. Dekoratör böyle bir ambalajdır: çağrı yapıldığında önce sarmalayıcının kodu çalışır, o orijinal fonksiyonu çağırır ve sonucu üzerinde bir şey yapabilir.

Bu, Python'da fonksiyonların sıradan nesneler olmasıyla mümkündür. Fonksiyonlar başka fonksiyonlara verilebilir, fonksiyonlardan döndürülebilir ve fonksiyonların içinde tanımlanabilir. Dekoratörler bu üç özelliğin üzerine kuruludur.

## Dekoratör nasıl çalışır: @ bir sözdizimi kısayoludur

En basit dekoratörü ele alalım; çağrıdan önce ve sonra bir mesaj yazdırır:

```
def log_calls(func):
    def wrapper():
        print(f"{func.__name__} çağrılıyor")
        result = func()
        print(f"{func.__name__} tamamlandı")
        return result
    return wrapper

@log_calls
def hello():
    print("Merhaba!")

hello()
```

```
hello çağrılıyor
Merhaba!
hello tamamlandı
```

`def` üstündeki `@log_calls` satırı, fonksiyon tanımlandıktan sonra yazılan şu satıra tamamen eşdeğerdir:

```
def hello():
    print("Merhaba!")

hello = log_calls(hello)
```

Artık `hello` adı `wrapper`'ı gösterir; orijinal fonksiyon ise bir kapanımın içinde yaşar, çünkü `wrapper` dış fonksiyondaki `func` değişkenini hatırlar. Önemli bir ayrıntı: dekoratör yalnızca bir kez, `def` çalıştığında, yani modül içe aktarıldığında uygulanır; sarmalayıcı ise her çağrıda devreye girer.

## Kapanımlar ve dekoratörler

Kapanım (closure), dış fonksiyon işini bitirdikten sonra da onun değişkenlerini hatırlayan iç fonksiyondur. Kapanımlar olmasaydı dekoratörler de olmazdı: `wrapper`, `func`'a bu sayede erişir. Kapanım içinde durum da tutulabilir; örneğin çağrı sayacı:

```
def count_calls(func):
    calls = 0
    def wrapper(*args, **kwargs):
        nonlocal calls
        calls += 1
        print(f"{func.__name__}: {calls}. çağrı")
        return func(*args, **kwargs)
    return wrapper
```

## Python'da dekoratör yazmak: genel şablon

İlk örneğin iki eksiği var: sarmalayıcı argüman kabul etmiyor ve orijinal fonksiyonun adını "yutuyor". Her dekoratörün başlangıç noktası olması gereken çalışan şablon şöyledir:

```
import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # çağrıdan önce yapılacaklar
        result = func(*args, **kwargs)
        # çağrıdan sonra yapılacaklar
        return result
    return wrapper
```

Şablonun üç kuralı var:

1. **`*args, **kwargs`** — sarmalayıcı her türlü argümanı kabul eder ve değiştirmeden iletir.
2. **`return result`** — sonucu döndürmeyi unutmayın; aksi halde dekore edilmiş fonksiyon her zaman `None` verir.
3. **`@functools.wraps(func)`** — orijinal fonksiyonun meta verilerini sarmalayıcıya taşır.

Çalışma süresini ölçen bir dekoratör örneği:

```
import functools
import time

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} {elapsed:.3f} sn sürdü")
        return result
    return wrapper

@timer
def slow_sum(n):
    return sum(range(n))

slow_sum(10_000_000)   # slow_sum 0.142 sn sürdü
```

## functools.wraps neden gerekli

`wraps` olmadan, dekore edilmiş fonksiyonun adı ve docstring'i değişir; dışarıdan `wrapper` görünür:

```
def plain(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

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

print(add.__name__, add.__doc__)   # wrapper None
```

`@functools.wraps(func)` ile sarmalayıcı orijinalin `__name__`, `__doc__`, `__module__`, `__qualname__` ve `__annotations__` değerlerini alır; ayrıca `__wrapped__` özniteliğinde orijinale bir referans tutulur. Bu, hata ayıklama ve hata izleri, `help()`, adıyla fonksiyon kaydeden framework'ler ve gerçek imzayı gösteren `inspect.signature` için önemlidir.

## Parametreli dekoratör (argümanlı)

Bazen dekoratörün ayarlara ihtiyacı olur: `@retry(times=3)`, `@app.route("/")`. Bunun için bir iç içe katman daha eklenir. Dış fonksiyon parametreleri alır ve gerçek dekoratörü döndürür; bu yapıya dekoratör fabrikası denir.

```
import functools

def repeat(times):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def ping():
    print("ping")

ping()   # ping, ping, ping — üç satır
```

Çağrı sırası şöyledir: `repeat(times=3)` `decorator`'ı döndürür, o da `ping`'e uygulanıp `wrapper`'ı verir. Yani `@repeat(times=3)`, `ping = repeat(times=3)(ping)` anlamına gelir.

Pratik bir örnek olarak hata durumunda yeniden deneme:

```
import functools
import time

def retry(times=3, delay=1.0, exceptions=(Exception,)):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, times + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions:
                    if attempt == times:
                        raise
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(times=5, delay=0.5, exceptions=(ConnectionError,))
def fetch_rate():
    ...
```

## Bir fonksiyonda birden fazla dekoratör

Dekoratörler üst üste yazılabilir. Uygulanma sırası alttan yukarıdır: `def`'e en yakın olan ilk uygulanır.

```
@timer
@log_calls
def report():
    ...

# aynı anlama gelir:
report = timer(log_calls(report))
```

Çağrı sırasında dış katman (`timer`) ilk devreye girer ve `log_calls`'un çalışma süresini de ölçer. Sıra önemlidir: örneğin biri yetki kontrolü, diğeri sonuç önbellekleme yapıyorsa hangisinin dışta olacağı davranışı değiştirir.

## Standart ve yerleşik Python dekoratörleri

Birçok faydalı dekoratör zaten dilde ve standart kütüphanede bulunur:

| Dekoratör | Ne yapar |
| --- | --- |

| `@property` | metodu yalnızca okunabilir bir özniteliğe çevirir; `@x.setter` ile yönetilen bir özniteliğe |

| `@staticmethod` | `self` almayan sınıf metodu; sınıfın ad alanında duran sıradan bir fonksiyon |

| `@classmethod` | metot örnek yerine sınıfı (`cls`) alır; alternatif kurucular bu şekilde yazılır |

| `@functools.cache`, `@functools.lru_cache` | sonuçları argümanlarına göre önbelleğe alır |

| `@functools.wraps` | fonksiyonun meta verilerini sarmalayıcıya kopyalar |

| `@dataclasses.dataclass` | sınıf için `__init__`, `__repr__`, `__eq__` üretir |

| `@contextlib.contextmanager` | bir üreteci `with` için bağlam yöneticisine çevirir |

### Önbellekleme: functools.cache ve lru_cache

Önbellekleyen dekoratör, her argüman kümesi için sonucu saklar ve tekrar çağrıldığında hesaplamadan döndürür. Klasik örnek, önbellek olmadan üssel sürede çalışan özyinelemeli Fibonacci sayılarıdır:

```
import functools

@functools.cache
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

print(fib(100))   # 354224848179261915075
```

`functools.cache` (Python 3.9'da geldi) tüm sonuçları sınırsız saklar. `@functools.lru_cache(maxsize=128)` ise yalnızca son kayıtları tutar ve eskileri atar; uzun süre çalışan servisler için daha güvenlidir. İstatistiklere `fib.cache_info()` ile bakılır, `fib.cache_clear()` ile temizlenir. Bu tür fonksiyonların argümanları özetlenebilir olmalıdır: argüman olarak liste verirseniz `TypeError: unhashable type: 'list'` alırsınız. Ayrıca yalnızca saf fonksiyonları önbelleğe alın: aynı argümanlarla her zaman aynı değeri döndüren ve yan etkisi olmayanları.

### property dekoratörü ne yapar

`@property`, bir metoda parantez olmadan, öznitelik gibi erişmenizi sağlar; gerekirse atamayı da denetleyebilirsiniz:

```
class Account:
    def __init__(self, balance):
        self._balance = balance

    @property
    def balance(self):
        return self._balance

    @balance.setter
    def balance(self, value):
        if value < 0:
            raise ValueError("Bakiye negatif olamaz")
        self._balance = value

acc = Account(100)
print(acc.balance)   # 100
acc.balance = -5     # ValueError
```

### staticmethod ve classmethod

```
class Money:
    def __init__(self, kurus):
        self.kurus = kurus

    @classmethod
    def from_lira(cls, lira):
        return cls(round(lira * 100))

    @staticmethod
    def is_valid(value):
        return value >= 0

m = Money.from_lira(12.5)
print(m.kurus, Money.is_valid(-1))   # 1250 False
```

Yalnızca `@classmethod`, metodun örneklerle değil doğrudan sınıfla çalışmasını sağlar: `from_lira` bir alt sınıfın nesnesini de doğru biçimde oluşturur.

## Sınıf dekoratörleri ve dekoratör olarak sınıflar

Dekoratör bir sınıfa da uygulanabilir: sınıfı alır ve aynı veya değiştirilmiş bir sınıf döndürür. `@dataclass` böyle çalışır. Basit bir örnek olarak sınıf kaydı (registry):

```
registry = {}

def register(cls):
    registry[cls.__name__] = cls
    return cls

@register
class CsvExporter:
    pass

print(registry)   # {'CsvExporter': }
```

Tersine, bir sınıf da dekoratör olabilir. `__call__` metodu olan her nesne bu işi görür; durum örneğin özniteliklerinde tutulur:

```
import functools

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.calls = 0

    def __call__(self, *args, **kwargs):
        self.calls += 1
        return self.func(*args, **kwargs)

@CountCalls
def tick():
    pass

tick(); tick()
print(tick.calls)   # 2
```

## Asenkron dekoratör

`async def` fonksiyonları için sarmalayıcının da bir coroutine olması ve sonucu `await` ile beklemesi gerekir; aksi halde bekleyen bir coroutine nesnesi döner:

```
import functools

def log_async(func):
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        print(f"{func.__name__} başladı")
        result = await func(*args, **kwargs)
        print(f"{func.__name__} bitti")
        return result
    return wrapper
```

## Dekoratörler neden kullanılır

- **Ortak mantık** — loglama, metrikler, süre ölçümü; her fonksiyona kod kopyalamadan.
- **Önbellekleme** — pahalı ve saf hesaplamalar için `@functools.cache`.
- **Kontroller** — yetki, argüman doğrulama, web işleyicilerinde kimlik doğrulama.
- **Kayıt** — Flask ve FastAPI'de rotalar (`@app.get("/")`), pytest fikstürleri.
- **Dayanıklılık** — yeniden deneme, zaman aşımı, hata yönetimi.

Asıl kazanç sorumlulukların ayrılmasıdır: fonksiyon kendi işine odaklanır, yardımcı davranış tek satırla eklenir ve aynı kolaylıkla kaldırılır.

## Tipik hatalar

- **Sarmalayıcı sonucu döndürmüyor** — dekore edilmiş fonksiyon `None` verir.
- **`*args, **kwargs` yok** — argümanlı çağrıda `TypeError`.
- **`wraps` unutulmuş** — yanlış `__name__` ve docstring, loglarda karışıklık.
- **Fabrikada parantez eksik** — `@repeat` yerine `@repeat(3)` yazılmalı; aksi halde fonksiyon `times` parametresine geçer.
- **Dekoratörün gövdesine ağır kod** — bu kod çağrıda değil, modül içe aktarılırken çalışır.

## Özet

Python dekoratörleri, fonksiyon alıp fonksiyon döndüren yüksek dereceli fonksiyonlardır; `@` ise atamanın kısa yazımıdır. Yeni bir dekoratöre `*args, **kwargs`, `return result` ve `functools.wraps` içeren şablonla başlayın; ayar gerektiğinde bir fabrika katmanı ekleyin. Dekoratörlerin dayandığı fonksiyonlar, kapanımlar ve tür ipuçları kursta uygulamalı olarak işlenir.

## FAQ

**Bir dekoratörü bir fonksiyondan nasıl kaldırabilirim?**

Dekoratör functools.wraps kullanıyorsa orijinal fonksiyon __wrapped__ özniteliğinde durur: func.__wrapped__(...) çağrısı sarmalayıcısız çalışır. Kullanmıyorsa kodda @ ile başlayan satırı silmek gerekir.

**Dekoratörün kodu ne zaman çalışır?**

Dekoratörün kendi gövdesi bir kez, fonksiyon tanımlanırken, genellikle modül içe aktarılırken çalışır. wrapper içindeki kod ise dekore edilmiş fonksiyon her çağrıldığında çalışır.

**Dekoratörleri sınıf metotlarına uygulayabilir miyim?**

Evet; *args, **kwargs alan sıradan bir dekoratör metotlarla da çalışır. self, args'ın ilk elemanı olarak gelir.

**Dekoratör ile kalıtım arasındaki fark nedir?**

Kalıtım bir sınıfın davranışını hiyerarşi üzerinden değiştirir; dekoratör ise bir fonksiyonu ya da sınıfı dışarıdan sarar ve kodunu değiştirmeden diğer dekoratörlerle kolayca birleştirilir.


---
Source: Skillaria.Top (https://skillaria.top)
