Python'da decorator, bir fonksiyonu (veya sınıfı) argüman olarak alıp yerine kullanılacak yeni bir çağrılabilir nesne döndüren bir fonksiyondur. @ad satırı yalnızca bir sözdizimi kolaylığıdır: @kayit ile işaretlenmiş def topla(...) tanımı, fonksiyonu tanımladıktan hemen sonra topla = kayit(topla) yazmakla aynı işi görür. Böylece kayıt tutma, yeniden deneme, önbellekleme ya da yetki kontrolü gibi ortak davranışları, asıl fonksiyonun koduna dokunmadan ekleyebilirsiniz.
Bu yazıda decorator'ın arkasındaki fikri (fonksiyonların birer nesne olması ve closure), functools.wraps ile meta verinin korunmasını, argüman alan decorator'ları, birden fazla decorator'ın uygulanma sırasını, sınıf decorator'larını ve standart kütüphanedeki hazır decorator'ları adım adım ele alıyoruz. Tüm örnekler Python 3.15.2 ile çalıştırıldı. Serinin bütününe Python öğrenme rehberi sayfasından ulaşabilirsiniz.
Temel: fonksiyonlar birer nesnedir

Decorator'ları anlamanın anahtarı, Python'da fonksiyonların da sayılar ve listeler gibi nesne olmasıdır. Bir fonksiyonu bir değişkene atayabilir, başka bir fonksiyona argüman olarak verebilir, bir fonksiyonun içinde yeni bir fonksiyon tanımlayıp onu döndürebilirsiniz. İçeride tanımlanan fonksiyon, dış fonksiyonun yerel değişkenlerini (aşağıdaki örnekte fonk) hatırlar; buna closure denir ve Python kapsam, global ve nonlocal yazısında ayrıntılı anlatılıyor. Fonksiyon tanımlamanın temelleri için Python fonksiyon tanımlama yazısına bakabilirsiniz.
def selamla(isim):
return f'Merhaba {isim}'
def bagir(fonk):
def sarmalayici(*args, **kwargs):
return fonk(*args, **kwargs).upper()
return sarmalayici
print(type(selamla).__name__)
selamla = bagir(selamla)
print(selamla('ayşe'))
# çıktı: function
# çıktı: MERHABA AYŞEBurada bagir bir decorator'dır: bir fonksiyon alır, onu çağıran ve sonucu büyük harfe çeviren sarmalayici adlı yeni bir fonksiyon döndürür. selamla = bagir(selamla) satırından sonra selamla adı artık sarmalayıcıyı gösterir. Sarmalayıcının *args ve **kwargs alması önemlidir; böylece dekore edilen fonksiyonun imzası ne olursa olsun bütün argümanları olduğu gibi iletebilir. Bu sözdiziminin ayrıntıları Python *args ve **kwargs yazısında.
@ sözdizimi resmî olarak ne yapar?
Language Reference'a göre bir fonksiyon tanımı bir ya da daha fazla decorator ifadesiyle sarılabilir. Decorator ifadeleri fonksiyon tanımlandığı anda, tanımı içeren kapsamda değerlendirilir. Sonuç çağrılabilir bir nesne olmalıdır; bu nesne fonksiyon nesnesini tek argüman olarak alır ve döndürdüğü değer, fonksiyon nesnesinin yerine fonksiyon adına bağlanır. Belge, @f1(arg) ve @f2 ile işaretli bir fonksiyonun kabaca func = f1(arg)(f2(func)) anlamına geldiğini söyler; tek fark, orijinal fonksiyonun geçici olarak func adına bağlanmamasıdır.
Python 3.9 ile gelen PEP 614 sonrasında @ işaretinden sonra geçerli herhangi bir atama ifadesi yazılabilir; önceden dil bilgisi çok daha kısıtlıydı. Glossary de decorator'ı "başka bir fonksiyon döndüren, genellikle @wrapper sözdizimiyle uygulanan fonksiyon" olarak tanımlar ve @classmethod ile @staticmethod örneklerini verir.
def kayit(fonk):
def sarmalayici(*args, **kwargs):
print(f'{fonk.__name__} çağrıldı: {args} {kwargs}')
return fonk(*args, **kwargs)
return sarmalayici
@kayit
def topla(a, b):
"""İki sayıyı toplar."""
return a + b
print(topla(2, b=3))
print(topla.__name__)
print(topla.__doc__)
# çıktı: topla çağrıldı: (2,) {'b': 3}
# çıktı: 5
# çıktı: sarmalayici
# çıktı: NoneÖrnek çalışıyor, ama son iki satırda bir sorun var: topla.__name__ artık sarmalayici döndürüyor ve belge dizesi (docstring) kaybolmuş. Hata ayıklarken, log kayıtlarında, help() çıktısında ve otomatik belge araçlarında yanlış adı görürsünüz.
functools.wraps ile meta veriyi koruyun
functools.wraps, sarmalayıcı fonksiyonu sarılan fonksiyona benzetmek için kullanılan bir decorator fabrikasıdır ve arka planda update_wrapper() çağırır. functools belgesine göre varsayılan olarak __module__, __name__, __qualname__, __annotations__, __type_params__ ve __doc__ öznitelikleri kopyalanır, sarmalayıcının __dict__ sözlüğü güncellenir ve orijinal fonksiyonu gösteren bir __wrapped__ özniteliği eklenir. __type_params__ 3.12'den beri varsayılan olarak kopyalanıyor.
import functools
def kayit(fonk):
@functools.wraps(fonk)
def sarmalayici(*args, **kwargs):
print(f'{fonk.__name__} çağrıldı: {args} {kwargs}')
return fonk(*args, **kwargs)
return sarmalayici
@kayit
def topla(a, b):
"""İki sayıyı toplar."""
return a + b
print(topla.__name__)
print(topla.__doc__)
print(topla.__wrapped__(2, 3))
# çıktı: topla
# çıktı: İki sayıyı toplar.
# çıktı: 5__wrapped__ özniteliği sayesinde decorator'ı atlayıp orijinal fonksiyonu doğrudan çağırabilirsiniz; bu, özellikle testlerde ya da önbellekleme yapan bir decorator'ı devre dışı bırakmak istediğinizde işe yarar. Kural basit: kendi yazdığınız her fonksiyon decorator'ında iç fonksiyonu @functools.wraps(fonk) ile işaretleyin.
Decorator ile durum tutmak
Sarmalayıcı da bir fonksiyon nesnesi olduğu için ona öznitelik ekleyebilirsiniz. Aşağıdaki decorator, fonksiyonun kaç kez çağrıldığını cagri özniteliğinde tutar. Bu desen, basit ölçüm ve hata ayıklama işleri için kullanışlıdır.
import functools
def say(fonk):
@functools.wraps(fonk)
def sarmalayici(*args, **kwargs):
sarmalayici.cagri += 1
return fonk(*args, **kwargs)
sarmalayici.cagri = 0
return sarmalayici
@say
def selam():
return 'merhaba'
selam()
selam()
print(selam.cagri)
# çıktı: 2Argüman alan decorator yazmak

@yeniden_dene(deneme=3) gibi parametre alan bir decorator yazmak için bir katman daha gerekir. Resmî tanımı hatırlayın: @ işaretinden sonraki ifade önce değerlendirilir ve sonucu fonksiyona uygulanır. Yani yeniden_dene(deneme=3) çağrısının kendisi bir decorator döndürmelidir. Bu yüzden yapı üç katmanlıdır: dıştaki fonksiyon ayarları alır, ortadaki fonksiyon dekore edilecek fonksiyonu alır, en içteki sarmalayıcı ise her çağrıda çalışır.
import functools
def yeniden_dene(deneme=3, hata=Exception):
def dekorator(fonk):
@functools.wraps(fonk)
def sarmalayici(*args, **kwargs):
for i in range(1, deneme + 1):
try:
return fonk(*args, **kwargs)
except hata as e:
print(f'{i}. deneme başarısız: {e}')
raise RuntimeError(f'{fonk.__name__} {deneme} denemede de başarısız')
return sarmalayici
return dekorator
durum = {'sayac': 0}
@yeniden_dene(deneme=3, hata=ConnectionError)
def baglan():
durum['sayac'] += 1
if durum['sayac'] < 3:
raise ConnectionError('sunucu yanıt vermedi')
return 'bağlandı'
print(baglan())
# çıktı: 1. deneme başarısız: sunucu yanıt vermedi
# çıktı: 2. deneme başarısız: sunucu yanıt vermedi
# çıktı: bağlandıBu örnekte sarmalayıcı yalnızca belirtilen istisna türünü yakalar; başka bir hata olursa hemen yukarı iletilir. Bütün denemeler başarısız olursa açıklayıcı bir RuntimeError yükseltilir. Kendi istisna türlerinizi tanımlamak ve raise ... from ile neden zinciri kurmak için Python raise ve özel istisna yazısına bakabilirsiniz. Sık yapılan bir hata, argümanlı decorator'ı parantezsiz kullanmaktır: @yeniden_dene yazarsanız fonksiyonunuz deneme parametresine gider ve beklenmedik bir davranış ortaya çıkar.
Birden fazla decorator: uygulanma sırası
Bir fonksiyonu birden fazla decorator ile işaretlediğinizde, decorator'lar aşağıdan yukarıya, yani fonksiyona en yakın olandan başlayarak uygulanır. Çağrı sırasında ise en dıştaki sarmalayıcı önce çalışır. Aşağıdaki çıktı bu iki sırayı açıkça gösteriyor: tanım anında önce b, sonra a uygulanıyor; çağrı anında önce a sarmalayıcısı, sonra b sarmalayıcısı çalışıyor.
import functools
def a(fonk):
print('a uygulandı')
@functools.wraps(fonk)
def sarmalayici(*args, **kwargs):
print('a önce')
return fonk(*args, **kwargs)
return sarmalayici
def b(fonk):
print('b uygulandı')
@functools.wraps(fonk)
def sarmalayici(*args, **kwargs):
print('b önce')
return fonk(*args, **kwargs)
return sarmalayici
@a
@b
def is_yap():
print('iş yapıldı')
is_yap()
# çıktı: b uygulandı
# çıktı: a uygulandı
# çıktı: a önce
# çıktı: b önce
# çıktı: iş yapıldıBu sıra pratikte önemlidir. Örneğin bir yetki kontrolü decorator'ı ile bir önbellekleme decorator'ını birlikte kullanıyorsanız, hangisinin dışarıda olduğu, önbellekten dönen bir sonucun yetki kontrolünden geçip geçmediğini belirler. @property, @classmethod veya @staticmethod gibi decorator'ları kendi decorator'larınızla birleştirirken genellikle bu yerleşik decorator'ı en üste (en dışa) koymanız gerekir.
Decorator'lar tanım anında çalışır: kayıt deseni
Decorator ifadesi fonksiyon tanımlandığı anda değerlendirildiği için, modül içe aktarıldığında (import) decorator'ın gövdesi bir kez çalışır. Bu, fonksiyonları bir sözlüğe kaydetmek için çok kullanışlıdır: komut satırı araçları, olay işleyicileri ve eklenti sistemleri sıklıkla bu deseni kullanır. Bu tür bir decorator'ın fonksiyonu sarmalaması gerekmez; kaydettikten sonra orijinal fonksiyonu olduğu gibi döndürür.
KOMUTLAR = {}
def komut(ad):
def kaydet(fonk):
KOMUTLAR[ad] = fonk
return fonk
return kaydet
@komut('selam')
def selam():
return 'merhaba'
@komut('saat')
def saat():
return '12:00'
print(sorted(KOMUTLAR))
print(KOMUTLAR['selam']())
# çıktı: ['saat', 'selam']
# çıktı: merhabaBu davranışın bir yan etkisi de vardır: decorator'ın dış gövdesine yazdığınız ağır işler (dosya okuma, ağ bağlantısı gibi) her import sırasında çalışır. Bu yüzden asıl işi sarmalayıcının içine, yani çağrı anına bırakmak genellikle daha doğrudur.
Metotları dekore etmek
Bir sınıfın içindeki metodu dekore ettiğinizde, sarmalayıcı *args aldığı sürece self de ilk argüman olarak sorunsuzca iletilir. functools.wraps burada __qualname__ özniteliğini de kopyaladığı için log kaydında Hesap.yatir gibi tam ad görünür.
import functools
def kayit(fonk):
@functools.wraps(fonk)
def sarmalayici(*args, **kwargs):
print('çağrı:', fonk.__qualname__)
return fonk(*args, **kwargs)
return sarmalayici
class Hesap:
def __init__(self, bakiye):
self.bakiye = bakiye
@kayit
def yatir(self, tutar):
self.bakiye += tutar
return self.bakiye
print(Hesap(100).yatir(50))
# çıktı: çağrı: Hesap.yatir
# çıktı: 150Sınıf decorator'ları ve sınıfla yazılmış decorator'lar
Decorator yalnızca fonksiyonlara uygulanmaz. Language Reference'a göre sınıf tanımları da decorator ile sarılabilir; değerlendirme kuralları fonksiyon decorator'larıyla aynıdır ve sonuç sınıf adına bağlanır (PEP 3129). Bir sınıf decorator'ı genellikle sınıfı yerinde değiştirip aynı sınıfı döndürür. Standart kütüphanedeki @dataclass bunun en bilinen örneğidir; ayrıntılar Python dataclasses yazısında. Aşağıdaki küçük decorator, sınıfa okunaklı bir __repr__ ekliyor.
def tanit(cls):
def __repr__(self):
alanlar = ', '.join(f'{k}={v!r}' for k, v in vars(self).items())
return f'{type(self).__name__}({alanlar})'
cls.__repr__ = __repr__
return cls
@tanit
class Nokta:
def __init__(self, x, y):
self.x = x
self.y = y
print(Nokta(1, 2))
print(repr(Nokta('a', None)))
# çıktı: Nokta(x=1, y=2)
# çıktı: Nokta(x='a', y=None)Tersi de mümkündür: decorator'ın kendisi bir sınıf olabilir. __call__ yöntemi tanımlanan bir sınıfın örnekleri çağrılabilir olduğu için, sınıfın kendisi decorator gibi kullanılabilir. Bu durumda meta veriyi kopyalamak için functools.update_wrapper(self, fonk) çağrısını __init__ içinde yaparsınız. Durumu öznitelikte tutmak sınıf yapısıyla daha doğal görünebilir; ancak bu tür decorator'lar metotlara uygulandığında self bağlama davranışı fonksiyonlardan farklıdır, çünkü sınıf örnekleri descriptor protokolünü kendiliğinden uygulamaz. Descriptor'lar Python property ve descriptor yazısında anlatılıyor.
import functools
class Sayac:
def __init__(self, fonk):
functools.update_wrapper(self, fonk)
self.fonk = fonk
self.cagri = 0
def __call__(self, *args, **kwargs):
self.cagri += 1
return self.fonk(*args, **kwargs)
@Sayac
def kare(x):
return x * x
print(kare(4), kare(5))
print(kare.cagri, kare.__name__)
# çıktı: 16 25
# çıktı: 2 kareMini örnek: rol kontrolü yapan decorator
Şimdiye kadarki parçaları tek bir gerçekçi örnekte birleştirelim. Bir web uygulamasında bazı işlemleri yalnızca belirli rollere açmak istediğinizi düşünün. Her fonksiyonun başına aynı if bloğunu kopyalamak yerine, izin verilen rolleri parametre olarak alan bir decorator yazabilirsiniz. Aşağıdaki örnekte sarmalayıcı, ilk argümanın bir kullanıcı sözlüğü olduğunu varsayıyor; rol listede yoksa kendi tanımladığımız YetkiHatasi istisnasını yükseltiyor, varsa çağrıyı olduğu gibi iletiyor.
import functools
class YetkiHatasi(Exception):
pass
def rol_gerekli(*roller):
def dekorator(fonk):
@functools.wraps(fonk)
def sarmalayici(kullanici, *args, **kwargs):
if kullanici['rol'] not in roller:
raise YetkiHatasi(f"{kullanici['ad']} için {fonk.__name__} yasak")
return fonk(kullanici, *args, **kwargs)
return sarmalayici
return dekorator
@rol_gerekli('admin', 'editor')
def yazi_sil(kullanici, yazi_id):
return f'{yazi_id} numaralı yazı silindi'
print(yazi_sil({'ad': 'Elif', 'rol': 'admin'}, 7))
try:
yazi_sil({'ad': 'Can', 'rol': 'okur'}, 7)
except YetkiHatasi as e:
print('engellendi:', e)
# çıktı: 7 numaralı yazı silindi
# çıktı: engellendi: Can için yazi_sil yasakBu örnekte üç katmanın her biri ayrı bir iş yapıyor: rol_gerekli izin verilen rolleri toplar, dekorator hangi fonksiyonun korunacağını öğrenir, sarmalayici ise her çağrıda kontrolü yapar. Hata iletisinde fonk.__name__ kullanılabiliyor, çünkü fonk closure sayesinde sarmalayıcının içinden erişilebilir durumda. Gerçek projelerde kullanıcı bilgisi genellikle bir istek nesnesinden gelir ve web çatıları bu tür decorator'ları hazır sunar; buradaki amaç mekanizmayı göstermektir. Yetkisiz denemeleri kayıt altına almak için sarmalayıcıya bir log satırı eklemek de kolaydır; bunun için Python logging modülü yazısına bakabilirsiniz.
Standart kütüphanedeki hazır decorator'lar

Kendi decorator'ınızı yazmadan önce standart kütüphanede hazırının olup olmadığına bakın. En sık kullanılanlar şunlardır:
| Decorator | Modül | Ne yapar |
|---|---|---|
| @property | yerleşik | Metodu öznitelik gibi okunabilir yapar |
| @classmethod / @staticmethod | yerleşik | Sınıf metodu ve statik metot tanımlar |
| @functools.wraps | functools | Sarmalayıcıya orijinal fonksiyonun meta verisini kopyalar |
| @functools.cache | functools | Sınırsız önbellek; lru_cache(maxsize=None) ile aynı |
| @functools.lru_cache | functools | Boyutu sınırlı, en az kullanılanı atan önbellek |
| @functools.total_ordering | functools | Eksik karşılaştırma metotlarını tamamlar |
| @functools.singledispatch | functools | İlk argümanın tipine göre farklı fonksiyon çağırır |
| @dataclasses.dataclass | dataclasses | Sınıfa __init__, __repr__, __eq__ ekler |
| @contextlib.contextmanager | contextlib | Generator fonksiyonundan context manager üretir |
| @warnings.deprecated | warnings | Kullanımda DeprecationWarning verir (3.13+) |
functools.cache Python 3.9'dan beri vardır ve belgesine göre boyut sınırı olmadığı için hiçbir değeri atması gerekmez; bu da onu sınırlı bir lru_cache'ten daha küçük ve hızlı yapar. Önbellek iş parçacığı açısından güvenlidir, yani iç veri yapısı eşzamanlı güncellemelerde tutarlı kalır; ancak belge, kilit fonksiyon çağrısı boyunca tutulmadığı için aynı argümanlarla ikinci bir çağrının ilk çağrı bitmeden başlayabileceğini, yani "yalnızca bir kez çalışma" garantisi olmadığını da not eder. Önbellekli fonksiyonların argümanları hashable olmalıdır.
from functools import cache
@cache
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(80))
print(fib.cache_info())
fib.cache_clear()
print(fib.cache_info())
# çıktı: 23416728348467685
# çıktı: CacheInfo(hits=78, misses=81, maxsize=None, currsize=81)
# çıktı: CacheInfo(hits=0, misses=0, maxsize=None, currsize=0)cache_info() isabet (hits) ve kaçırma (misses) sayılarını gösterir, cache_clear() ise önbelleği boşaltır. functools modülünün diğer araçları için Python itertools ve functools yazısına bakabilirsiniz.
Python 3.13 ile gelen @warnings.deprecated (PEP 702), bir sınıfın, fonksiyonun ya da overload'un kullanımdan kaldırıldığını işaretler. Nesne kullanıldığında çalışma zamanında DeprecationWarning yayımlanabilir; statik tip denetleyicileri de kullanım yerinde uyarı üretir.
import warnings
from warnings import deprecated
@deprecated('eski_topla yerine topla kullanın')
def eski_topla(a, b):
return a + b
with warnings.catch_warnings(record=True) as kayitlar:
warnings.simplefilter('always')
print(eski_topla(1, 2))
print(kayitlar[0].category.__name__, '|', kayitlar[0].message)
# çıktı: 3
# çıktı: DeprecationWarning | eski_topla yerine topla kullanınSık yapılan hatalar
- Sarmalayıcıda return'ü unutmak: Sarmalayıcı
fonk(*args, **kwargs)çağırıp sonucu döndürmezse, dekore edilen fonksiyon her zamanNonedöner. Aşağıdaki örnektetopla(2, 3)bu yüzdenNoneveriyor. - functools.wraps kullanmamak: Fonksiyonun adı, belge dizesi ve tip notları kaybolur; log ve hata ayıklama çıktıları yanıltıcı olur.
- Decorator yerine sonucunu vermek:
@kayit()ile@kayitfarklıdır. Parametresiz decorator'ı parantezle, parametreli olanı parantezsiz kullanmak hataya yol açar. - Argümanları sabitlemek: Sarmalayıcıyı
def sarmalayici(x)gibi sabit imzayla yazarsanız, farklı imzalı fonksiyonlardaTypeErroralırsınız.*args, **kwargskullanın. - Ağır işi tanım anına koymak: Decorator'ın dış gövdesi import sırasında çalışır; asıl işi sarmalayıcıya bırakın.
- classmethod zincirine güvenmek: Python 3.13 zincirlenmiş
classmethoddescriptor desteğini kaldırdı;classmethodartıkpropertygibi başka descriptor'ları sarmak için kullanılamaz. What's New belgesi bunun yerine 3.10'da eklenen__wrapped__özniteliğini önerir.
def kayit(fonk):
def sarmalayici(*args, **kwargs):
fonk(*args, **kwargs)
return sarmalayici
@kayit
def topla(a, b):
return a + b
print(topla(2, 3))
# çıktı: NoneSık Sorulan Sorular
Decorator ile closure arasındaki ilişki nedir?
Fonksiyon decorator'larının çoğu closure kullanır: iç sarmalayıcı, dış fonksiyona verilen fonk argümanını hatırlar. Ancak decorator olmak için closure şart değildir; çağrılabilir bir nesne alıp çağrılabilir bir nesne döndüren her şey (kayıt örneğindeki gibi aynı fonksiyonu döndüren basit bir fonksiyon ya da __call__ tanımlı bir sınıf) decorator olarak kullanılabilir.
Async fonksiyonlar için decorator nasıl yazılır?
Dekore edilen fonksiyon async def ile tanımlıysa, sarmalayıcının da async def olması ve return await fonk(*args, **kwargs) yazması gerekir. Aksi halde sarmalayıcı bir coroutine nesnesi döndürür ama onu beklemez; asenkron yapı Python asyncio yazısında anlatılıyor.
Decorator'ın tip notlarını nasıl yazarım?
Sarmalayıcının imzası orijinal fonksiyonla aynı olacaksa, typing modülündeki ParamSpec ve 3.12'de gelen tip parametresi sözdizimi kullanılır. Tip notlarının genel mantığı için Python type hints ve typing yazısına bakabilirsiniz.
Bir decorator'ı geçici olarak devre dışı bırakabilir miyim?
functools.wraps kullandıysanız orijinal fonksiyona fonk.__wrapped__ ile ulaşabilirsiniz. Bu özellikle testlerde, önbellek veya yeniden deneme mantığını atlayarak saf fonksiyonu sınamak için kullanışlıdır.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Python öğrenme rehberi · Fonksiyon tanımlama · Kapsam, global ve nonlocal · *args ve **kwargs · dataclasses · Generator ve yield