Takip et

Decorators: Kodunuzun Hak Ettiği Makyajı Nasıl Yaparsınız?

Kodunuzun daha okunaklı, daha bakımı kolay ve daha güçlü olmasını mı istiyorsunuz? Fonksiyonlarınıza yeni yetenekler eklerken mevcut yapısını değiştirmemek kulağa harika gelmiyor mu? İşte tam da bu noktada, programlama dünyasının “süslü sargıları” olarak adlandırabileceğimiz decorator’lar devreye giriyor ve kodunuza ihtiyacı olan makyajı yaparak onu baştan yaratıyor.

Hiç var olan bir fonksiyonun davranışını değiştirmek, ona yeni özellikler eklemek ya da belirli bir mantığı birçok fonksiyona uygulamak istediğiniz oldu mu? Örneğin, bir fonksiyonun çalışma süresini ölçmek, çağrıldığında bir log kaydı oluşturmak veya sadece belirli kullanıcıların erişimine izin vermek gibi. Bu tür durumlarda, her fonksiyonun içine aynı kod bloğunu tekrar tekrar yazmak, hem kod tekrarına (DRY – Don’t Repeat Yourself prensibine aykırı) yol açar hem de kodunuzun okunabilirliğini ve bakımını zorlaştırır. İşte tam da bu soruna zarif bir çözüm sunan yapılar, decorator’lardır.

Decorator, esasen, başka bir fonksiyonu argüman olarak alan ve işlevselliğini genişleterek yeni bir fonksiyon döndüren bir tür fonksiyondur. Kısacası, bir fonksiyonu “saran” ve ona ek özellikler kazandıran bir sarmalayıcı (wrapper) gibidir. Tıpkı bir hediye paketinin içindeki hediyenin özünü değiştirmeden ona çekici bir görünüm katması veya ekstra bir koruma sağlaması gibi, decorator’lar da fonksiyonların temel mantığını bozmadan onlara ek işlevsellik katmanları ekler. Bu sayede, fonksiyonlarınızın çekirdek görevi net kalırken, çevresel işlevler (loglama, yetkilendirme, zamanlama vb.) ayrı bir yerde, modüler bir şekilde yönetilebilir hale gelir. Bu yaklaşım, kodunuzun daha temiz, daha anlaşılır ve sürdürülebilir olmasına büyük katkı sağlar. Aynı zamanda, fonksiyonel programlama prensiplerini benimseyerek kodunuzu daha esnek ve test edilebilir kılar. Bu yüzden, modern yazılım geliştirmede decorator’lar, özellikle Python gibi dillerde, vazgeçilmez bir araç haline gelmiştir. Onlar sayesinde, karmaşık sistemlerde bile kodun modülerliğini ve okunabilirliğini korumak mümkün olur.

Uzman İpucu: Decorator’lar, “concern separation” (ilgi alanlarının ayrılması) prensibini uygulamak için mükemmel bir yoldur. Çekirdek iş mantığını çevresel endişelerden (cross-cutting concerns) ayırarak kodunuzun modülerliğini ve test edilebilirliğini artırırsınız.

Decorator’ların Temel Yapısı: Fonksiyonlar Fonksiyonları Nasıl Sarar?

Decorator’lar, Python’da “fonksiyonlar birinci sınıf vatandaşlardır” (first-class citizens) ilkesinin bir sonucudur. Bu, fonksiyonların tıpkı sayılar veya dizeler gibi değişkenlere atanabileceği, başka fonksiyonlara argüman olarak gönderilebileceği ve fonksiyonlardan döndürülebileceği anlamına gelir. Bir decorator yazarken, bu yetenekten faydalanırız. Temel olarak, bir decorator üç katmanlı bir yapıyı içerir:

  1. Dış Decorator Fonksiyonu: Bu fonksiyon, asıl decorate edilecek fonksiyonu argüman olarak alır.
  2. İç Wrapper Fonksiyonu: Dış fonksiyon içinde tanımlanır. Bu fonksiyon, decorate edilecek asıl fonksiyonu sarar ve onun çağrılması öncesinde veya sonrasında ek işlemler yapar.
  3. Asıl Fonksiyonun Çağrılması: Wrapper fonksiyonun içinde, decorate edilen asıl fonksiyon çağrılır ve sonuç döndürülür.

Python, bu yapıyı daha kolay ve okunaklı hale getirmek için özel bir “sözdizimsel şeker” (syntactic sugar) sunar: @ sembolü. Bir fonksiyonun üzerine @decorator_adı yazmak, aslında o fonksiyonu decorator_adı(fonksiyon) şeklinde çağırmak ve sonucu orijinal fonksiyonun üzerine atamakla eşdeğerdir. Bu, kodu inanılmaz derecede temiz ve anlaşılır hale getirir. Örneğin, bir fonksiyonun kaç kez çağrıldığını saymak için basit bir decorator’ı ele alalım. Bu decorator, her çağrıldığında bir sayacı artıracak ve ardından asıl fonksiyonu çalıştıracaktır. Böylece, asıl fonksiyonun kodunu değiştirmeden ona ek bir sayaç özelliği kazandırmış oluruz. Bu temel yapı, decorator’ların gücünü anlamak için kritik bir başlangıç noktasıdır ve daha karmaşık uygulamaların temelini oluşturur.


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

@cagir_sayaci
def merhaba_de(isim):
    return f"Merhaba, {isim}!"

print(merhaba_de("Ayşe"))
print(merhaba_de("Mehmet"))
print(merhaba_de("Zeynep"))
print(f"Toplamda merhaba_de fonksiyonu {merhaba_de.calls} kez çağrıldı.")

Yukarıdaki örnekte, cagir_sayaci decorator'ı, merhaba_de fonksiyonunun her çağrılışını sayar ve bu bilgiyi ekrana basar. Dikkat ederseniz, merhaba_de fonksiyonunun içine sayaçla ilgili hiçbir kod eklemedik. İşte decorator'ların güzelliği tam da burada yatıyor: iş mantığını çevresel endişelerden ayırmak.

Gerçek Dünya Senaryolarında Decorator Gücü: Pratik Uygulamalar Nelerdir?

Decorator'lar sadece teorik bir kavram olmanın ötesinde, yazılım geliştirmenin birçok alanında pratik ve güçlü çözümler sunar. Gerçek dünya uygulamalarında, kodun kalitesini artırmak, tekrarı önlemek ve sistemleri daha yönetilebilir kılmak için sıkça kullanılırlar. İşte decorator'ların en yaygın ve etkili kullanım senaryolarından bazıları:

Performans İzleme ve Loglama: Uygulamanızın Kalbi Nasıl Atıyor?

Uygulamaların performansını anlamak ve sorunları gidermek için loglama ve zamanlama hayati öneme sahiptir. Hangi fonksiyonların ne kadar sürdüğünü bilmek, darboğazları tespit etmede ilk adımdır. Decorator'lar, bu tür "çapraz kesen ilgileri" (cross-cutting concerns) fonksiyonlarınızın asıl mantığından ayırmak için mükemmel bir araçtır. Bir fonksiyonun her çağrıldığında çalışma süresini ölçen veya fonksiyonun giriş-çıkışlarını kaydeden bir decorator yazmak, kodunuzun çekirdek işlevselliğini değiştirmeden değerli metrikler toplamanıza olanak tanır. Örneğin, karmaşık bir veritabanı sorgusunun veya yoğun bir hesaplama işleminin ne kadar sürdüğünü izlemek için, ilgili fonksiyona sadece tek bir @zaman_olc decorator'ı eklemek yeterli olacaktır. Bu, hem geliştirme sürecini hızlandırır hem de gelecekteki bakım süreçlerinde büyük kolaylık sağlar. Loglama decorator'ları ise, fonksiyon çağrılarını, argümanları ve dönüş değerlerini otomatik olarak belirli bir log dosyasına veya konsola yazarak uygulamanızın davranışını adım adım takip etmenizi sağlar. Bu sayede, hata ayıklama süreçleri basitleşir ve sistemdeki olası anormallikler daha hızlı tespit edilebilir. Bu iki kullanım alanı, decorator'ların modülerlik ve bakım kolaylığı açısından sunduğu avantajları net bir şekilde ortaya koymaktadır. Daha da önemlisi, bu tür yardımcı kodları merkezi bir yerde tutarak, uygulamanın genelinde tutarlı bir performans izleme ve loglama stratejisi uygulamanıza olanak tanır.


import time
import logging

# Loglama ayarları (Basit bir örnek)
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def zaman_olc(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        logging.info(f"{func.__name__} fonksiyonu {end_time - start_time:.4f} saniyede tamamlandı.")
        return result
    return wrapper

def logla_cagir(func):
    def wrapper(*args, **kwargs):
        logging.info(f"{func.__name__} çağrıldı. Argümanlar: {args}, Keyword Argümanlar: {kwargs}")
        result = func(*args, **kwargs)
        logging.info(f"{func.__name__} tamamlandı. Dönüş değeri: {result}")
        return result
    return wrapper

@zaman_olc
@logla_cagir
def uzun_islem(saniye):
    """Belirtilen saniye kadar uyuyan bir fonksiyon."""
    time.sleep(saniye)
    return f"{saniye} saniye uyunuldu."

print(uzun_islem(2))
print(uzun_islem(0.5))

Yetkilendirme ve Erişim Kontrolü: Kim Nereye Girebilir?

Web uygulamaları ve API'lerde güvenlik, en kritik konulardan biridir. Belirli kaynaklara veya fonksiyonlara yalnızca yetkili kullanıcıların erişebilmesini sağlamak, uygulamanızın bütünlüğü için elzemdir. Bu tür yetkilendirme kontrollerini her fonksiyonun başında manuel olarak yapmak, hem hataya açık bir süreçtir hem de kod tekrarına neden olur. İşte burada decorator'lar devreye girer ve yetkilendirme mantığını merkezi ve yeniden kullanılabilir bir yapıya dönüştürür. Bir yetkili_kullanici_gerekiyor veya admin_rolu_gerekiyor gibi bir decorator tanımlayarak, bu kontrolleri hedef fonksiyonlara kolayca uygulayabilirsiniz. Decorator, asıl fonksiyon çağrılmadan önce kullanıcının kimlik doğrulama durumunu, sahip olduğu rolleri veya izinlerini kontrol eder. Eğer kullanıcı gerekli kriterleri karşılamıyorsa, fonksiyonu çalıştırmaz ve bunun yerine bir hata (örneğin, AccessDeniedError) fırlatır veya bir HTTP yanıtı (örneğin, 403 Forbidden) döndürür. Bu sayede, iş mantığı kodunuz temiz kalır ve güvenlik kontrolleri ayrı bir katmanda ele alınır. Bu yaklaşım, sadece kodu düzenli tutmakla kalmaz, aynı zamanda güvenlik politikalarınızı merkezi bir yerden yönetmenize olanak tanır. Güvenlik kurallarında bir değişiklik olduğunda, sadece decorator'ı güncellemeniz yeterli olur; bu da tüm ilgili fonksiyonlara otomatik olarak yansır. Bu modülerlik, büyük ve karmaşık sistemlerde güvenlik yönetimini son derece kolaylaştırır.


class Kullanici:
    def __init__(self, username, roles):
        self.username = username
        self.roles = roles

mevcut_kullanici = Kullanici("admin", ["admin", "editor"])
# mevcut_kullanici = Kullanici("guest", ["viewer"])

def rol_gerekiyor(gerekli_rol):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if gerekli_rol not in mevcut_kullanici.roles:
                raise PermissionError(f"Bu işlem için '{gerekli_rol}' rolü gereklidir.")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@rol_gerekiyor("admin")
def yonetici_paneli_goster():
    return "Yönetici Paneline Hoş Geldiniz!"

@rol_gerekiyor("editor")
def makale_duzenle(makale_id):
    return f"{makale_id} numaralı makale düzenleniyor."

try:
    print(yonetici_paneli_goster())
except PermissionError as e:
    print(e)

try:
    print(makale_duzenle(123))
except PermissionError as e:
    print(e)

Önbellekleme (Caching) ile Hızlandırma: Aynı İşi İki Kez Yapmayın!

Performans optimizasyonu, özellikle tekrarlayan ve maliyetli işlemler içeren uygulamalarda hayati öneme sahiptir. Bir fonksiyonun aynı girdilerle her çağrıldığında aynı sonucu üretmesi durumunda, bu sonucu tekrar tekrar hesaplamak yerine bir kez hesaplayıp saklamak ve sonraki çağrılarda önbellekten sunmak, uygulamanın tepki süresini önemli ölçüde hızlandırabilir. Decorator'lar, önbellekleme mantığını fonksiyonun asıl işlevinden ayırmak için ideal bir mekanizma sunar. Python'ın standart kütüphanesinde yer alan functools.lru_cache gibi hazır decorator'lar bu işi çok kolay hale getirir. lru_cache, en son kullanılanları (Least Recently Used) tutarak önbellek boyutunu yönetir ve hafıza kullanımını optimize eder. Kendi özel önbellekleme decorator'ınızı yazarken de, fonksiyonun aldığı argümanları bir anahtar olarak kullanarak sonuçları bir sözlükte saklayabilir ve aynı anahtar ile tekrar çağrıldığında doğrudan bu saklanmış değeri döndürebilirsiniz. Bu yöntem, özellikle yoğun hesaplamalı algoritmalar, veritabanı sorguları veya harici API çağrıları gibi zaman alıcı operasyonlar için büyük bir performans kazancı sağlar. Örneğin, bir döviz kuru API'sinden veri çeken bir fonksiyonunuz varsa, bu fonksiyonu bir önbellekleme decorator'ı ile sararak, belirli bir süre içinde aynı kur bilgisi istendiğinde API'ye tekrar istek göndermeyi engelleyebilir ve hem ağ trafiğini azaltabilir hem de kullanıcının bekleme süresini kısaltabilirsiniz. Bu, uygulamanızın genel kullanıcı deneyimini doğrudan iyileştiren somut bir fayda sağlar. Böylece, kodunuz sadece daha hızlı olmakla kalmaz, aynı zamanda daha verimli ve kaynak dostu hale gelir.


from functools import lru_cache
import time

@lru_cache(maxsize=None)  # Önbellek boyutu sınırsız
def fibonacci(n):
    """Pahalı bir Fibonacci hesaplama fonksiyonu."""
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

@lru_cache(maxsize=3) # En fazla 3 farklı sonucu önbelleğe al
def agirlik_hesapla(malzeme, miktar):
    print(f"{malzeme} için ağırlık hesaplanıyor...")
    time.sleep(1) # Gerçek bir API çağrısı veya yoğun hesaplama simülasyonu
    return f"{miktar * 100} gram {malzeme}"

print("Fibonacci(30) hesaplanıyor (ilk kez)...")
start = time.perf_counter()
fibonacci(30)
end = time.perf_counter()
print(f"Süre: {end - start:.4f} saniye")

print("Fibonacci(30) hesaplanıyor (önbellekten)...")
start = time.perf_counter()
fibonacci(30)
end = time.perf_counter()
print(f"Süre: {end - start:.4f} saniye (önbellekten okundu)")

print("\nAğırlık hesaplamaları:")
print(agirlik_hesapla("demir", 5))
print(agirlik_hesapla("altın", 2))
print(agirlik_hesapla("bakır", 10))
print(agirlik_hesapla("demir", 5)) # Önbellekten gelir
print(agirlik_hesapla("gümüş", 1)) # "demir" önbellekten atılır

Argüman Alan Decorator'lar Nasıl Yazılır ve Ne İşe Yarar?

Şimdiye kadar gördüğümüz decorator'lar, doğrudan bir fonksiyonu sarmalayan basit yapılardı. Ancak birçok durumda, decorator'ların kendilerinin de bazı yapılandırma parametrelerine veya seçeneklere ihtiyacı olabilir. Örneğin, bir yetkilendirme decorator'ı yazdığımızda, hangi rolün gerekli olduğunu decorator'a söylemek isteyebiliriz (@rol_gerekiyor("admin") gibi). İşte bu tür senaryolar için argüman alan decorator'lar devreye girer. Bu decorator'lar, temelde ekstra bir sarma katmanı ekleyerek çalışır. Yani, bir decorator fabrikası gibi düşünebilirsiniz.

Argüman alan bir decorator yazmak için, aslında üç iç içe geçmiş fonksiyon yapısına ihtiyacımız vardır:

  1. En Dış Fonksiyon: Bu fonksiyon, decorator'ın alacağı argümanları (örneğin, gerekli_rol) kabul eder. Bu fonksiyonun dönüş değeri, asıl decorator fonksiyonu olacaktır.
  2. Orta Fonksiyon (Asıl Decorator): Bu fonksiyon, decorate edilecek hedef fonksiyonu (örneğin, yonetici_paneli_goster) argüman olarak alır. Bu, daha önce gördüğümüz standart decorator yapısının kendisidir.
  3. En İç Wrapper Fonksiyonu: Bu fonksiyon, hedef fonksiyonu sarar ve çağrı öncesi/sonrası mantığı içerir.

Bu üç katmanlı yapı, decorator'ın ilk çağrıldığında argümanları almasını, ardından bu argümanları kapatma (closure) özelliği aracılığıyla saklamasını ve daha sonra gerçek fonksiyon sarmalama işlemini gerçekleştirmesini sağlar. Bu, decorator'larınıza esneklik ve yeniden kullanılabilirlik katarak, farklı bağlamlarda farklı davranışlar sergilemelerine olanak tanır. Örneğin, bir loglama decorator'ı yazarken, log seviyesini (INFO, DEBUG, WARNING) bir argüman olarak geçerek, farklı fonksiyonlar için farklı detay seviyelerinde loglama yapabilirsiniz. Bu, kodunuzu daha dinamik ve yapılandırılabilir hale getirir. Argüman alan decorator'lar, özellikle framework'ler ve kütüphanelerde (örneğin, Flask veya Django'daki rota tanımlamaları) yaygın olarak kullanılır ve geliştiricilere güçlü ve esnek bir soyutlama katmanı sunar.


def tekrarlama_sayisi(sayi):
    def decorator(func):
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(sayi):
                results.append(func(*args, **kwargs))
            return results # Tüm sonuçları liste olarak döndürüyoruz
        return wrapper
    return decorator

@tekrarlama_sayisi(3)
def merhaba_de_tekrarlı(isim):
    import random
    return f"Merhaba {isim}! (Rastgele Sayı: {random.randint(1, 100)})"

print(merhaba_de_tekrarlı("Ali"))

@tekrarlama_sayisi(2)
def kare_al(x):
    return x * x

print(kare_al(5))

Sınıflarla Decorator Kullanımı: Nesne Yönelimli Yaklaşım Nasıl Entegre Edilir?

Python'da decorator'lar genellikle fonksiyon tabanlı olarak yazılsa da, bazen daha karmaşık durum yönetimi veya konfigürasyon gerektiren senaryolar için sınıf tabanlı decorator'lar kullanmak daha mantıklı olabilir. Sınıf tabanlı bir decorator, state (durum) tutabilme yeteneği sayesinde daha gelişmiş özellikler sunar. Örneğin, bir decorator'ın birden fazla fonksiyonda ortak bir durumu yönetmesi veya decorator'ın kendi içinde karmaşık bir mantığı barındırması gerektiğinde sınıflar devreye girer. Bir sınıfı decorator olarak kullanabilmek için, o sınıfın __call__ metodunu uygulaması gerekir. __call__ metodu, bir sınıf örneğini sanki bir fonksiyonmuş gibi çağrılabilir kılar. Yani, my_instance() yazdığınızda aslında my_instance.__call__() metodunu çağırmış olursunuz.

Sınıf tabanlı bir decorator yazarken iki ana yaklaşım vardır:

  1. Sınıfı doğrudan bir fonksiyon decorator'ı olarak kullanmak: Bu durumda, decorator sınıfının __init__ metodu decorate edilecek fonksiyonu alır ve __call__ metodu ise wrapper fonksiyonu gibi davranır. Bu, decorator'ın her çağrıldığında state'i korumasını sağlar.
  2. Sınıfı bir argüman alan decorator fabrikası olarak kullanmak: Bu yaklaşım, argüman alan fonksiyon decorator'larına benzer. En dıştaki fonksiyon yerine, decorator sınıfının bir örneği oluşturulur ve bu örnek daha sonra decorate edilecek fonksiyonu alır.

Sınıf tabanlı decorator'lar, özellikle büyük framework'lerde ve modüler uygulamalarda, decorator'lara daha fazla esneklik ve nesne yönelimli tasarım desenlerini entegre etme olanağı sunduğu için tercih edilebilir. Örneğin, bir API rate-limiting (istek sınırlama) decorator'ı, her bir API uç noktası için ayrı ayrı kalan istek hakkı gibi durum bilgilerini tutmak zorunda kalabilir. Bu durumda, bir sınıf decorator'ı, bu state'i kolayca yönetebilir ve her fonksiyon çağrısında güncelleyebilir. Bu, fonksiyonel yaklaşımla yazılması daha zor veya daha karmaşık olabilecek durum tabanlı işlemleri basitleştirir. Böylece, hem kodunuzun yapısı daha düzenli olur hem de karmaşık iş akışlarını daha anlaşılır bir şekilde modelleyebilirsiniz.


class CalismaSayaci:
    def __init__(self, func):
        self.func = func
        self.sayac = 0

    def __call__(self, *args, **kwargs):
        self.sayac += 1
        print(f"'{self.func.__name__}' fonksiyonu {self.sayac}. kez çalıştırıldı.")
        return self.func(*args, **kwargs)

@CalismaSayaci
def merhaba_de_sinif(isim):
    return f"Merhaba {isim}!"

@CalismaSayaci
def topla_sinif(a, b):
    return a + b

print(merhaba_de_sinif("Deniz"))
print(merhaba_de_sinif("Can"))
print(topla_sinif(5, 7))
print(topla_sinif(10, 20))

İleri Seviye İpuçları ve Püf Noktaları: Decorator Sihirbazı Olmaya Hazır Mısınız?

Decorator'ların temellerini ve pratik kullanımlarını kavradıktan sonra, onları daha verimli ve sorunsuz bir şekilde kullanmanızı sağlayacak bazı ileri seviye tekniklere ve ipuçlarına göz atabiliriz. Bu püf noktaları, özellikle büyük ve karmaşık projelerde decorator'larla çalışırken karşılaşabileceğiniz yaygın sorunları önlemenize ve kodunuzun kalitesini artırmanıza yardımcı olacaktır. Unutmayın, iyi yazılmış bir decorator, sadece işlevsellik katmakla kalmaz, aynı zamanda kodun okunabilirliğini ve bakımı kolaylığını da artırır.

  • functools.wraps Kullanımı: Bu, decorator'larla çalışırken en önemli ipuçlarından biridir. Bir fonksiyonu bir decorator ile sardığınızda, orijinal fonksiyonun adı (__name__), dokümantasyon dizisi (__doc__) ve diğer meta verileri kaybolur. Bu, hata ayıklama ve dokümantasyon için ciddi sorunlara yol açabilir. functools.wraps decorator'ı, sarılmış fonksiyonun meta verilerini wrapper fonksiyona otomatik olarak kopyalayarak bu sorunu çözer. Her decorator yazdığınızda, wrapper fonksiyonunuzu @functools.wraps(func) ile sarmaladığınızdan emin olun. Bu küçük detay, kodunuzun profesyonelliğini ve bakım kolaylığını önemli ölçüde artırır.
    
    from functools import wraps
    
    def logla(func):
        @wraps(func) # Önemli! Meta verileri korur.
        def wrapper(*args, **kwargs):
            print(f"'{func.__name__}' çağrıldı.")
            return func(*args, **kwargs)
        return wrapper
    
    @logla
    def toplama(a, b):
        """İki sayıyı toplar."""
        return a + b
    
    print(toplama.__name__) # 'toplama' döndürür (wraps kullanıldığında)
    print(toplama.__doc__)  # 'İki sayıyı toplar.' döndürür (wraps kullanıldığında)
    
            

  • Decorator Zincirleme (Chaining): Bir fonksiyona birden fazla decorator uygulayabilirsiniz. Decorator'lar, üstten alta doğru uygulanır. Yani, en üstteki decorator önce çağrılır ve en alttaki fonksiyonu sarmalar. Ardından, bir sonraki decorator bu sarmalanmış fonksiyonu sarmalar ve bu böyle devam eder. Bu, modülerliği artırarak, farklı işlevsellikleri ayrı decorator'larla yönetmenize olanak tanır. Örneğin, bir fonksiyonun hem çalışma süresini ölçmek hem de erişimini kontrol etmek için @zaman_olc ve @rol_gerekiyor("admin") decorator'larını aynı anda kullanabilirsiniz. Zincirleme sırası önemlidir; genellikle güvenlik ve doğrulama decorator'ları daha yukarıda, yani daha önce çalışacak şekilde konumlandırılır.
    
    @zaman_olc # Bu önce çalışır
    @logla_cagir # Bu ikinci çalışır
    def veritabanindan_veri_cek():
        time.sleep(1.5)
        return "Veri çekme başarılı."
    
    print(veritabanindan_veri_cek())
    
            

  • Parametreli Decorator'lar ve Sınıf Decorator'lar Arasındaki Seçim: Bir decorator'ın durum tutması (stateful) gerektiğinde veya karmaşık konfigürasyonlara sahip olması durumunda sınıf tabanlı decorator'lar daha uygun olabilir. Fonksiyon tabanlı parametreli decorator'lar (üç iç içe fonksiyon yapısı), daha basit durumsuz veya az durumlu senaryolar için yeterlidir. Karar verirken, decorator'ın ne kadar durum yönettiğini ve ne kadar esnek olması gerektiğini göz önünde bulundurun.
  • Hata Ayıklama (Debugging) Kolaylığı: Decorator'lar, çağrı yığınını (call stack) değiştirdiği için hata ayıklamayı bazen zorlaştırabilir. functools.wraps kullanmak bu sorunu büyük ölçüde hafifletir. Ayrıca, decorator içindeki print ifadeleri veya bir debugger kullanmak, akışı anlamanıza yardımcı olabilir. Karmaşık decorator'lar yazarken, onları küçük, test edilebilir parçalara ayırmak da hata ayıklamayı kolaylaştırır.

Bu ileri seviye ipuçları ve püf noktalarıyla, decorator'ları sadece kullanmakla kalmayacak, aynı zamanda onları ustaca tasarlayabilecek ve uygulayabileceksiniz. Decorator'lar, Python'ın en güçlü özelliklerinden biridir ve doğru kullanıldığında kodunuzu daha modüler, daha okunaklı ve daha sürdürülebilir hale getirir.

Sonuç: Kodunuzun Geleceği Decorator'larla Parlıyor mu?

Bu makalede, programlama dünyasının “süslü sargıları” olan decorator’ların ne olduğunu, nasıl çalıştığını ve kodunuza nasıl inanılmaz bir makyaj yapabileceğini detaylıca inceledik. Basit loglama işlemlerinden karmaşık yetkilendirme mekanizmalarına, performans optimizasyonundan modüler kod tasarımına kadar geniş bir yelpazede decorator'ların gücünü gördük. Onlar, kod tekrarını azaltmanın (DRY prensibi), ilgileri ayırmanın (separation of concerns) ve fonksiyonel programlama paradigmalarını benimsemenin güçlü bir yoludur. Artık fonksiyonlarınıza yeni yetenekler kazandırırken, onların çekirdek mantığını değiştirmek zorunda kalmayacaksınız. Decorator'lar sayesinde, kodunuz daha okunaklı, daha bakımı kolay ve çok daha esnek hale gelecek. Unutmayın, iyi yazılmış bir decorator, bir fonksiyonun ömrünü uzatır ve onu farklı bağlamlarda yeniden kullanılabilir kılar. Bu yüzden, bir sonraki projenizde veya mevcut kod tabanınızı iyileştirirken, decorator'ların sunduğu bu sihirli dokunuşları kullanmaktan çekinmeyin. Kodunuzu güzelleştirme ve güçlendirme yolculuğunuzda decorator'lar size harika bir yol arkadaşı olacaktır.

Sıkça Sorulan Sorular (SSS)

Decorator kullanmak kodun performansını düşürür mü?
Genel olarak, decorator'ların eklediği soyutlama katmanı nedeniyle çok küçük bir performans düşüşü olabilir. Ancak bu, çoğu uygulama için ihmal edilebilir düzeydedir. Decorator'ların sağladığı kod okunabilirliği, bakım kolaylığı ve modülerlik avantajları, bu küçük performans farkından çok daha değerlidir. Performans kritik uygulamalarda bile, functools.lru_cache gibi önbellekleme decorator'ları aslında performansı artırabilir.
Her zaman decorator kullanmalı mıyım?
Hayır, her zaman değil. Decorator'lar, aynı işlevselliği birden fazla fonksiyona uygulamanız gerektiğinde veya bir fonksiyonun davranışını dışarıdan, temiz bir şekilde değiştirmek istediğinizde en etkilidir. Eğer bir işlevsellik sadece tek bir fonksiyona özgüyse ve basitse, doğrudan o fonksiyonun içine yazmak daha anlaşılır olabilir. Anahtar, kod tekrarını önlemek ve ilgileri ayırmak arasındaki dengeyi bulmaktır.
Decorator'lar sadece Python'da mı var?
Hayır, decorator deseni ve benzeri yapılar diğer programlama dillerinde de bulunur. Örneğin, Java'da "annotations", C#'da "attributes", JavaScript'te ES7'den itibaren gelen "decorators" ve Ruby'deki "modules" ve "mixins" benzer amaçlara hizmet eder. Ancak Python'daki @ sözdizimi, onları özellikle zarif ve kolay kullanılabilir kılar.
Bir decorator'ı nasıl hata ayıklarım?
Decorator'lar çağrı yığınını değiştirdiği için hata ayıklama bazen zorlayıcı olabilir. Bu durumu kolaylaştırmak için aşağıdaki yöntemleri kullanabilirsiniz:
  • functools.wraps kullanın. Bu, fonksiyonun orijinal adını ve meta verilerini koruyarak hata izini daha okunur hale getirir.
  • Decorator içindeki print ifadeleri veya loglama mesajları ekleyerek akışı takip edin.
  • Bir hata ayıklayıcı (debugger) kullanıyorsanız, breakpoint'leri hem orijinal fonksiyonda hem de decorator'ın wrapper fonksiyonunda ayarlayarak adım adım ilerleyin.
  • Karmaşık decorator'ları daha küçük, test edilebilir parçalara ayırın ve her bir parçayı ayrı ayrı test edin.
Sınıf tabanlı decorator'lar ile fonksiyon tabanlı argüman alan decorator'lar arasındaki temel fark nedir?
Temel fark, "durum (state) yönetimi" kabiliyetindedir. Fonksiyon tabanlı argüman alan decorator'lar (üç iç içe fonksiyon yapısıyla), decorator'a özgü basit parametreleri kapatma (closure) ile saklayabilirken, sınıf tabanlı decorator'lar daha karmaşık durumları (örneğin, bir sayacı, bir önbellek sözlüğünü veya farklı fonksiyonlar arasında paylaşılan verileri) sınıfın örnek değişkenleri aracılığıyla yönetebilirler. Bu, sınıf tabanlı decorator'ları özellikle karmaşık, durumu olan ve daha nesne yönelimli bir yaklaşıma ihtiyaç duyan senaryolar için daha uygun hale getirir.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık