Python'da dekoratör yazma: sarmalayıcıdan fabrikaya
Beceri rehberi

Python'da dekoratör yazma: sarmalayıcıdan fabrikaya

Hazırlayan: Mindset Institute EditörlüğüGüncellendi: 6 dk okuma

Python dekoratörlerini closure, functools.wraps ve argüman alan fabrikalarla örneklerle öğrenin; sık hataları ve önbellek kullanımını görün.

Python dekoratörü tam olarak ne yapar?

Dekoratör, bir fonksiyonu alıp yerine başka bir fonksiyon döndüren fonksiyondur. Python sözlüğü de aynı tanımı verir: dekoratör, genellikle @ söz dizimiyle uygulanan, başka bir fonksiyon döndüren fonksiyondur. @ işareti yalnızca kısayoldur. Bir fonksiyonun üstüne @dekorator yazmak, fonksiyonu normal biçimde tanımlayıp ardından ad = dekorator(ad) satırını çalıştırmakla anlamca aynıdır.

Bu mekanizmanın çalışması için şu fikirler gerekir. Önce, fonksiyonlar Python'da değerdir: bir değişkene atanır, argüman olarak verilir, başka bir fonksiyondan döndürülür. Sonra, iç içe tanımlanmış bir fonksiyon, dıştaki fonksiyonun değişkenlerini hatırlar (closure). Son olarak, değişken sayıda argüman kabul eden sarmalayıcılarla (args için tek yıldızlı, anahtar kelimeli argümanlar olan kwargs için çift yıldızlı parametre) hangi imzaya sahip olursa olsun bir fonksiyonu sarmalayabilirsiniz. Dekoratör yazmayı öğrenmek, bu bu fikirleri rahat kullanmaktan ibarettir.

Dekoratörlerin değeri, ortak davranışı tek yerde toplamasıdır. Zaman ölçme, günlük kaydı tutma, yetki denetimi, sonucu önbellekte saklama ve hata sonrası yeniden deneme gibi işler onlarca fonksiyonda aynı biçimde tekrar ediyorsa, her fonksiyonun gövdesini kirletmek yerine bunları üstüne bir satırlık etiket olarak koyarsınız.

İlk dekoratörünüzü adım adım yazın

Örnek olarak bir fonksiyonun ne kadar sürdüğünü ölçen dekoratörü kuralım. Önce iskeleti düşünün: dış fonksiyon hedef fonksiyonu parametre olarak alır, içte bir sarmalayıcı tanımlar, sarmalayıcı hedefi çağırır ve dış fonksiyon sarmalayıcıyı döndürür.

import time from functools import wraps

def sure_olc(func): @wraps(func) def sarmal(*args): basla = time.perf_counter() try: return func(*args) finally: gecen = time.perf_counter() - basla print(func.__name__, "sürdü:", gecen, "sn") return sarmal

@sure_olc def rapor_olustur(satirlar): return sorted(satirlar)

Adımları sırayla okuyun. Önce args parametresi, hedef fonksiyona gelen konumsal argümanları olduğu gibi iletmenizi sağlar; anahtar kelimeli argümanlar için sarmalayıcıya çift yıldızlı bir kwargs parametresi eklenir ve func çağrısına aynı biçimde geçirilir. Sonra hedefin dönüş değerini mutlaka geri vermeniz gerekir; bu örnekte return func(...) satırı bunu yapar. try ve finally bloğu, hedef fonksiyon hata fırlatsa bile süre bilgisinin yazılmasını sağlar. Son olarak sarmal fonksiyonu döndürürsünüz, çağırmazsınız: parantezsiz return sarmal, parantezli yazılırsa dekoratör bozulur.

Doğru çalıştığını anlamanın yolu basit bir deneydir. rapor_olustur'u küçük bir listeyle çağırın, çıktıda fonksiyon adı ve süre görünmelidir. Sonra dekoratörü kaldırıp fonksiyonu doğrudan çağırdığınızda aynı sonucu aldığınızı doğrulayın: dekoratör davranış ekler, sonucu değiştirmemelidir.

functools.wraps neden hemen her dekoratörde kullanılır?

Dekoratör sonunda orijinal fonksiyonun yerine sarmalayıcıyı koyar. Önlem almazsanız rapor_olustur.__name__ değeri sarmal olur, docstring kaybolur ve tip ipuçları görünmez hale gelir. Hata ayıklayıcı, belge üreticileri ve günlük kayıtları bu yüzden yanıltıcı adlar gösterir.

Python belgesine göre functools.wraps, sarmalayıcıya orijinal fonksiyonun modül, ad, nitelikli ad, açıklamalar ve belge metni gibi alanlarını kopyalar, __dict__ içeriğini günceller ve sarmalayıcıya __wrapped__ adlı bir referans ekler. Bu referans sayesinde sarılmış orijinal fonksiyona dışarıdan ulaşılabilir; testte dekoratörü atlayıp iş mantığını doğrudan çağırmak istediğinizde işe yarar.

Pratik kural: sarmalayıcı tanımlayan her dekoratörde @wraps(func) yazın. Bu satır neredeyse hiç maliyet getirmez ve ileride birisi fonksiyonun adına dayanan bir şey (yönlendirme tablosu, otomatik belge, önbellek anahtarı) kurduğunda sorun çıkmasını engeller.

Argüman alan dekoratör nasıl yazılır?

@tekrar_dene(DENEME_SAYISI) gibi parantezli kullanımda dekoratör aslında bir dekoratör fabrikasıdır. Katman sayısı artar: en dıştaki fonksiyon ayarları alır, ortadaki fonksiyon hedefi alır, en içteki fonksiyon gerçek çağrıyı yönetir.

import time from functools import wraps

def tekrar_dene(deneme_sayisi, hedef_hata=Exception, bekleme=None): def dekorator(func): @wraps(func) def sarmal(*args): son_hata = None for _ in range(deneme_sayisi): try: return func(*args) except hedef_hata as hata: son_hata = hata if bekleme: time.sleep(bekleme) raise son_hata return sarmal return dekorator

@tekrar_dene(DENEME_SAYISI, hedef_hata=ConnectionError) def servisten_veri_al(adres): ...

Burada dikkat edilecek nokta, hangi hatanın tekrar deneneceğini belirleyen hedef_hata parametresidir. Her hatayı yutan bir dekoratör, programlama hatalarını da gizler. Yalnızca geçici olduğunu bildiğiniz hata türünü yakalayın; kalıcı bir hatayı yeniden denemenin tek sonucu gecikmedir.

İsteğe bağlı argümanlı dekoratör de yazılabilir, ancak hem @dekorator hem @dekorator(...) biçimini destekleyen kod okunması zor hale gelir. Ekip içinde tek bir biçime sadık kalmak daha sürdürülebilir.

Yerleşik dekoratörler ve önbellekleme

Dekoratörleri çoğu zaman kendiniz yazmadan kullanırsınız. @property bir metodu nitelik gibi okunabilir yapar. @classmethod ve @staticmethod, Python sözlüğünün dekoratör örneği olarak andığı yerleşik dekoratörlerdir. Standart kütüphanedeki functools modülü ise @wraps dışında kullanışlı araçlar sunar.

functools.cache, aynı argümanlarla yapılan çağrıların sonucunu bir sözlükte saklar; sınırsız büyür ve eski kayıtları silmez. functools.lru_cache ise en son kullanılan çağrıları tutar ve boyutu maxsize ile sınırlanabilir. Her ikisinde de argümanlar hashlenebilir olmalıdır; liste ya da sözlük gibi değişebilir argümanlarla çağırırsanız hata alırsınız. Sarılan fonksiyonun cache_info() metodu isabet ve ıskalama sayılarını, cache_clear() metodu ise önbelleği temizlemeyi sağlar.

Önbellekleme yalnızca saf fonksiyonlarda güvenlidir: aynı girdiye her zaman aynı çıktıyı veren, dış durumdan etkilenmeyen fonksiyonlar. Veritabanından güncel veri okuyan ya da saate bağlı sonuç üreten bir fonksiyonu @cache ile sararsanız, bayat veri döndürürsünüz.

Sık yapılan hatalar ve kontrol listesi

  • @wraps eklemeyi unutmak: fonksiyon adı ve belge metni kaybolur.
  • Dönüş değerini geri vermemek: sarmalayıcı hedefi çağırır ama sonucu atar, çağıran taraf sürekli None alır.
  • Parantez karışıklığı: argümansız dekoratörü @dekorator(), argümanlı dekoratörü @dekorator diye yazmak.
  • Asenkron fonksiyonu senkron sarmalayıcıyla sarmak: sarmalayıcı yalnızca coroutine nesnesi döndürür, içindeki iş beklenmeden biter. Asenkron fonksiyon için sarmalayıcıyı da async def olarak yazıp await ile çağırmanız gerekir.
  • Dekoratör içinde sessizce hata yutmak: except bloğunda hata günlüğe yazılmıyorsa sorunun izini sürmek imkansızlaşır.

Birden fazla dekoratör üst üste konduğunda en alttaki önce uygulanır; yani en üstteki dekoratör, tümünün dışındaki son katmandır. Yetki denetimi gibi erken kesmesi gereken bir dekoratörü, önbellek gibi sonucu saklayan dekoratörün üstüne koyup koymayacağınızı bilinçli seçin.

Yazdığınız dekoratörü şu soruları sorarak sınayın: fonksiyonun adı ve belge metni korunuyor mu, argümanlı ve argümansız çağrılar doğru çalışıyor mu, hata durumunda davranış açık mı, hedef fonksiyonun dönüş değeri bozulmadan geliyor mu. Bu sorular, dekoratör kaynaklı hataların büyük bölümünü sahaya çıkmadan yakalar.

Dekoratörlerden sınıf tasarımına: öğrenme yolu

Başlangıç sırası şöyle olabilir: önce iç içe fonksiyon ve closure, sonra parametresiz dekoratör ve @wraps, ardından argüman alan dekoratör, en sonda sınıf tabanlı dekoratör ve sınıfları süsleyen dekoratörler. Her aşamada yazdığınız dekoratör gerçek bir işe, örneğin kendi projenizdeki bir günlük kaydına ya da süre ölçümüne bağlanırsa kalıcı olur.

Mindset'in Python ile Nesne Yönelimli Programlama eğitimi, sınıfları, özel metotları ve veri modelini, metaclass ve refaktör konularına kadar uzanan bir çizgide işler. Dekoratörlerin sınıf metotlarında ve veri modelinde nerede ortaya çıktığını bu çerçevede görmek, onları tekil bir numara olarak değil tasarım aracı olarak kavramaya yarar. Python İleri Seviye: Eşzamanlılık ve Performans eğitimindeki profilleme ve ölçüm disiplini modülü ise süre ölçen ve önbellek kullanan dekoratörlerin neyi çözüp neyi çözmediğini sayısal veriyle değerlendirmek isteyen geliştiriciler için uygun bir tamamlayıcıdır.

Kurumsal bir yazılım ekibinde bu eğitimler, ortak kod tabanında tekrar eden altyapı kodunu (günlük kaydı, yeniden deneme, yetki) tek bir kütüphaneye toplamak isteyen ekiplerin ortak bir dil oluşturmasına yardımcı olur.

İlgili Mindset programı

Python ile Nesne Yönelimli Programlama

Sık sorulanlar

Sık sorulan sorular

Dekoratör ile normal fonksiyon sarmalamak arasında fark var mı?

Mekanik olarak fark yoktur: @dekorator satırı, fonksiyonu tanımladıktan sonra ad = dekorator(ad) yazmakla aynı sonucu verir. Fark okunabilirliktedir. Etiket biçimi, davranışın fonksiyon tanımının hemen üstünde görünmesini sağlar; elle sarmalama ise çağrı noktalarına dağılır ve unutulması kolaydır. Aynı fonksiyonun hem sarılmış hem sarılmamış halini kullanmanız gerekiyorsa elle sarmalama da geçerli bir seçimdir.

Sınıflara da dekoratör uygulanabilir mi?

Evet. Python sözlüğü aynı kavramın sınıflar için de geçerli olduğunu belirtir, ancak daha seyrek kullanılır. Sınıf dekoratörü bir sınıfı alır, değiştirir ya da başka bir sınıfla değiştirir. Standart kütüphanedeki dataclass bunun tanıdık örneğidir: sınıfa __init__, __repr__ ve karşılaştırma gibi metotlar ekler ve sınıfı geri döndürür.

Dekoratör programı yavaşlatır mı?

Her sarmalayıcı, çağrıya fazladan bir fonksiyon çağrısı ekler. Çoğu iş için bu maliyet önemsizdir, ancak çok sık çağrılan ve çok kısa süren fonksiyonlarda fark edilebilir. Karar vermeden önce ölçün: profil çıkarıp gerçekten sarmalayıcıda mı zaman harcandığını görün. Yavaşlığın kaynağı genellikle sarılan işin kendisidir; önbellek dekoratörü de çoğu zaman bunu tersine çevirir.

Birden çok dekoratör hangi sırayla çalışır?

Dekoratörler alttan üste doğru uygulanır; yani fonksiyona en yakın olan önce sarar. Çağrı sırasında ise dıştaki, yani en üstteki sarmalayıcı önce devreye girer ve içtekini çağırır. Bu yüzden yetki denetimi gibi erken durdurması gereken dekoratör genellikle en üste, sonucu saklayan önbellek dekoratörü ise denetimin içine konur.

functools.cache ile lru_cache arasındaki fark nedir?

Python belgesine göre cache, boyut sınırı olmayan lru_cache ile eşdeğerdir ve eski kayıtları atmadığı için daha hafiftir. lru_cache ise maxsize ile en son kullanılanları tutar ve sınıra ulaşınca en eski kullanılanı çıkarır. Argüman çeşitliliği sınırsızsa bellek şişmesini önlemek için sınırlı lru_cache seçin.

Python Dekoratör Nasıl Yazılır: Örneklerle Adım Adım