Nesne Tabanlı Programlama 2

7 - Python’ın İleri Seviye Özellikleri I: Dekoratörler ve Fonksiyonel Yaklaşımlar

Emre Can Yılmaz

Ondokuz Mayıs Üniversitesi

2026

Bu Konu Neden Var?

Geçen haftalarda sınıfların davranışlarını tasarladık; özel metotlarla Python’ın bazı davranışları nasıl beklediğini gördük.

Bu hafta odağı biraz değiştiriyoruz:

Bu kez sınıfları değil, fonksiyonları daha esnek kullanmayı konuşacağız.

Bugün iki temel soruya cevap arıyoruz:

  1. Bir fonksiyonun içine dokunmadan davranışını nasıl genişletirim?
  2. Veri işleme adımlarını kısa ama anlaşılır biçimde nasıl ifade ederim?

Bu iki sorunun ana araçları şunlardır:

  • dekoratörler
  • fonksiyonel araçlar (lambda, map, filter, reduce, sorted(key=...))

Bu Dersin Öğrenme Hedefleri

Bu dersin sonunda şunları ayırt edebiliyor olmanızı hedefliyoruz:

  • dekoratörün neyi sardığını ve neden kullanıldığını
  • @dekorator yazımının perde arkasında ne yaptığını
  • *args, **kwargs ve dönüş değerinin neden kritik olduğunu
  • functools.wraps kullanımının neden iyi bir alışkanlık olduğunu
  • lambda, map, filter, reduce araçlarının ne zaman yararlı, ne zaman gereksiz olduğunu
  • kısa yazacağım derken neden daha anlaşılmaz kod yazmamak gerektiğini

Ön Bilgi: Python’da Fonksiyonlar da Nesnedir

Python’da fonksiyonlar:

  • bir değişkene atanabilir,
  • başka bir fonksiyona parametre olarak verilebilir,
  • bir fonksiyondan geri döndürülebilir.

Bu fikir anlaşılmadan dekoratör mantığı tam oturmaz.

def selamla(isim):
    return f"Merhaba, {isim}!"


def islemi_uygula(fonksiyon, deger):
    return fonksiyon(deger)


print(islemi_uygula(selamla, "Ayşe"))

Burada selamla, sadece çağrılan bir şey değil; başka bir fonksiyona aktarılabilen bir değerdir.

Dekoratörün Temel Mantığı

Dekoratör, en basit haliyle şunu yapar:

  • bir fonksiyonu alır,
  • onu başka bir fonksiyonun içinde sarar,
  • yeni davranışı geri döndürür.

Kısaca:

Fonksiyonu değiştirmeden, etrafına yeni davranış ekleriz.

Zihinsel model:

çağrı -> wrapper -> orijinal fonksiyon -> wrapper -> sonuç

Dekoratörler Ne İşe Yarar?

Aynı yardımcı davranışı birçok fonksiyona ayrı ayrı yazmak yerine ortaklaştırabiliriz.

Örneğin:

  • loglama eklemek
  • zaman ölçmek
  • yetki kontrolü yapmak
  • önbellekleme uygulamak
  • girdi doğrulama yapmak

Bu yüzden dekoratörler çoğu zaman şu problemi çözer:

“Aynı yardımcı davranışı birçok fonksiyona nasıl uygularım?”

Önce El ile Saralım

@ sözdizimine geçmeden önce mekanizmayı çıplak haliyle görelim.

Şimdilik parametresiz bir örnekle başlıyoruz:

def log_decorator(func):
    def wrapper():
        print("Fonksiyon çağrılmadan önce")
        func()
        print("Fonksiyon çağrıldıktan sonra")
    return wrapper


def merhaba_de():
    print("Merhaba!")


merhaba_de = log_decorator(merhaba_de)
merhaba_de()

Burada Aslında Ne Oldu?

  • log_decorator, merhaba_de fonksiyonunu aldı.
  • İçeride wrapper adında yeni bir fonksiyon üretti.
  • Eski davranışın önüne ve arkasına ek davranış koydu.
  • Geriye artık doğrudan eski fonksiyon değil, sarmalanmış yeni fonksiyon döndü.

Yani şu ifade kritik:

Dekoratör, orijinal fonksiyonun yerine geçen yeni bir fonksiyon üretir.

@ Yazımı Ne Anlama Gelir?

@log_decorator
def merhaba_de():
    print("Merhaba!")

Bu yazım, temelde şu anlama gelir:

def merhaba_de():
    print("Merhaba!")

merhaba_de = log_decorator(merhaba_de)

Yani @ özel bir sihir değil; daha okunabilir bir kısayoldur.

İlk Teknik Risk: Parametreler Kaybolabilir

İlk denemelerde sık görülen hata şudur:

def decorator(func):
    def wrapper():
        return func()
    return wrapper

Bu yapı yalnızca parametresiz fonksiyonlarda çalışır.

Fonksiyon parametre alıyorsa dekoratör kırılır.

Bu yüzden çoğu genel amaçlı dekoratörde şu yapı gerekir:

  • *args
  • **kwargs

Daha Sağlam Sürüm: Esnek Sarma

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"{func.__name__} çağrılıyor")
        result = func(*args, **kwargs)
        print(f"{func.__name__} tamamlandı")
        return result
    return wrapper


@log_decorator
def topla(a, b):
    return a + b


print(topla(3, 5))

İkinci Teknik Risk: Dönüş Değerini Kaybetmek

Dekoratör yazarken sık yapılan hatalardan biri de şudur:

  • func(...) çağrılır,
  • ama sonuç geri döndürülmez.

Bu durumda orijinal fonksiyon değer üretse bile dışarıya None gider.

Pratik kural:

Dekoratör, mümkünse orijinal fonksiyonun çağrılma mantığını ve dönüş davranışını bozmamalıdır.

Gerçekçi Örnek: Zaman Ölçümü

import time
from functools import wraps


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


@timer
def yavas_toplam():
    time.sleep(1)
    return sum(range(1000))


print(yavas_toplam())

Neden wraps Kullanıyoruz?

@wraps(func) kullanılmazsa sarmalanan fonksiyonun bazı kimlik bilgileri kaybolur.

Örneğin:

  • fonksiyon adı
  • dokümantasyon metni
  • bazı araçların gördüğü metadata

Bu yüzden iyi pratik şudur:

Genel amaçlı dekoratör yazıyorsanız, çoğu durumda functools.wraps kullanın.

wraps Etkisini Görmek

def plain_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper


@plain_decorator
def selam():
    """Basit selamlama fonksiyonu"""
    return "Merhaba"


print(selam.__name__)   # wrapper
print(selam.__doc__)    # None

@wraps(func) kullanıldığında bu bilgiler korunur.

Parametre Alan Dekoratörler

Bazen dekoratörün kendisi de ayar almak ister.

Örnek soru:

“Bu dekoratör hangi etiketi kullansın?”

Bu durumda yapı üç katmanlı olur:

  1. dekoratör parametresini alan dış fonksiyon
  2. gerçek dekoratör
  3. wrapper

Örnek: Etiket Ekleyen Dekoratör

from functools import wraps

def announce(label):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(f"[{label}] başladı")
            result = func(*args, **kwargs)
            print(f"[{label}] bitti")
            return result
        return wrapper
    return decorator

@announce("RAPOR")
def rapor_olustur():
    print("Rapor hazırlanıyor")

rapor_olustur()

Örnek: Yetki Kontrolü

Bu örnekte varsayımımız şu: dekoratör, ilk parametresi user olan fonksiyonlara uygulanacak.

from functools import wraps

class User:
    def __init__(self, name, role):
        self.name = name
        self.role = role

def require_role(required_role):
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.role != required_role:
                raise PermissionError(f"'{required_role}' yetkisi gereklidir.")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_role("admin")
def raporu_gor(user):
    return f"{user.name} raporu görüntülüyor"

Bu Örnekte Ne Kazandık?

Yetki kontrolünü her fonksiyonun içine ayrı ayrı yazmak yerine ortaklaştırdık.

Böylece:

  • tekrar azalır,
  • iş kuralı merkezi hale gelir,
  • unutma ihtimali düşer.

Ama dikkat:

Dekoratörler, kötü tasarımı sihirli biçimde düzeltmez. Sadece tekrar eden çapraz kesit davranışlarını toplamak için uygundur.

Ne Zaman Dekoratör Kullanmak Mantıklı?

Dekoratörler özellikle şu durumlarda doğrudur:

  • loglama
  • yetkilendirme
  • zaman ölçümü
  • önbellekleme
  • girdi doğrulama

Şu durumda dikkatli olunmalıdır:

  • iş mantığının tamamı dekoratör içine taşınıyorsa
  • akış izlenemez hale geliyorsa
  • hata ayıklama zorlaşıyorsa

Sık Yapılan Hatalar

  1. wrapper içinde return unutmak
  2. *args, **kwargs kullanmamak
  3. @wraps kullanmamak
  4. Çok fazla dekoratörü üst üste koyup akışı görünmez hale getirmek
  5. Dekoratörü, aslında normal bir yardımcı fonksiyonun yeterli olduğu yerde kullanmak

Dekoratör kullanmak her zaman avantaj sağlamaz; akışı okumayı zorlaştırıyorsa daha basit bir çözüm düşünülmelidir.

Fonksiyonel Yaklaşımlar

Dekoratörlerde fonksiyonun davranışını düzenledik.

Şimdi ise fonksiyonu, veri işleme adımlarını daha açık kurmak için kullanacağız.

Yeni sorumuz şudur:

Bir listedeki veriyi daha düzenli ve okunur biçimde nasıl dönüştürür, filtreler veya sıralarım?

lambda Nedir?

lambda, kısa ve tek ifadeli anonim fonksiyon tanımlamaya yarar.

Temel biçim:

lambda parametreler: ifade

Örnek:

kare = lambda x: x ** 2
print(kare(5))

Ama burada önemli sınır şudur:

lambda, küçük ve yerel kullanım için uygundur; büyük mantıklar için değildir.

lambda Ne Zaman Uygun, Ne Zaman Değil?

Uygun:

  • sorted(..., key=...) içinde kısa seçim yapmak
  • map veya filter içinde çok küçük dönüşümler yapmak

Uygun değil:

  • çok adımlı işlem gerekiyorsa
  • açıklayıcı bir isim faydalı olacaksa
  • ifade uzayıp okunabilirliği bozuyorsa

Özet kural:

Anlatmak zorlaşıyorsa def tercih edin.

map Örneği

numbers = [1, 2, 3, 4, 5]
squares = list(map(lambda x: x ** 2, numbers))
print(squares)

Python 3’te map(...) sonucu doğrudan liste değildir; bu yüzden burada list(...) ile sardık.

Bu örnek teknik olarak doğrudur; ama burada şu soruyu sormalıyız:

Bunu daha okunabilir başka nasıl yazardık?

Aynı İşlem: Liste Üreteci ile

numbers = [1, 2, 3, 4, 5]
squares = [x ** 2 for x in numbers]
print(squares)

Birçok durumda bu sürüm daha doğrudan okunur.

Bu yüzden önemli nokta şudur:

Python’da her map kullanımı zorunlu olarak en iyi çözüm değildir.

filter Örneği

numbers = [1, 2, 3, 4, 5, 6]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)

Anlamı nettir:

  • koşulu sağlayanlar kalır,
  • diğerleri elenir.

Aynı İşlem: Yine Liste Üreteci ile

numbers = [1, 2, 3, 4, 5, 6]
evens = [x for x in numbers if x % 2 == 0]
print(evens)

Burada da çoğu durumda liste üreteci daha doğal okunur.

Bu nedenle verilmek istenen mesaj şudur:

Araç seçimi kısalık için değil, okunabilirlik için yapılmalıdır.

reduce Nedir ve Neden Daha Dikkatli Kullanılır?

reduce, listedeki elemanları adım adım birleştirerek tek sonuç üretir.

from functools import reduce

numbers = [1, 2, 3, 4]
toplam = reduce(lambda x, y: x + y, numbers)
print(toplam)

Teknik olarak yararlıdır; ama ilk karşılaşmada adım adım takip etmek daha zor olabilir.

Ayrıca boş liste ve başlangıç değeri gibi durumlarda ayrıca karar vermek gerekir.

Örneğin basit toplama işlemlerinde çoğu zaman sum(numbers) daha doğrudan bir çözümdür.

Bu yüzden:

reduce bilinmeli; ama her indirgeme işinde ilk tercih olmak zorunda değildir.

sorted(key=...) Kullanımı

Fonksiyonel düşüncenin en yararlı ve günlük örneklerinden biri sıralamadır:

students = [("Ahmet", 85), ("Mehmet", 92), ("Ayşe", 78)]
sorted_students = sorted(students, key=lambda student: student[1], reverse=True)
print(sorted_students)

Burada lambda, çok küçük ve yerel bir iş yaptığı için uygundur.

Fonksiyonel Araçlar: Kısa Karar Çerçevesi

Araç Temel amaç Tipik soru
map her elemana dönüşüm uygulamak “Her elemanı neye çevireyim?”
filter bazı elemanları seçmek “Hangileri kalsın?”
reduce çok elemanı tek sonuca indirmek “Hepsini nasıl birleştireyim?”
sorted(key=...) bir ölçüte göre sıralamak “Neye bakarak sıralayayım?”

Ama en üst ilke şudur:

Bir hafta sonra yeniden baktığınızda en hızlı anlayacağınız çözüm genelde daha iyidir.

Bu Haftanın Özeti

  • Dekoratörler, fonksiyonun davranışını sarmalayarak genişletir.
  • @dekorator, aslında yeniden atama yapan daha okunabilir bir sözdizimidir.
  • Sağlam dekoratörlerde genellikle *args, **kwargs, return ve @wraps önemlidir.
  • Fonksiyonel araçlar kısa yazmayı sağlar; ama asıl hedef okunabilir veri akışıdır.
  • lambda küçük işler için iyidir; büyüyen mantıklarda def daha doğrudur.
  • map ve filter yararlıdır; ama birçok günlük durumda liste üreteçleri daha doğal okunur.
  • reduce güçlüdür; fakat her indirgeme işinde ilk tercih olmak zorunda değildir.

Alıştırmalar

  1. Bir fonksiyon çağrılmadan önce ve sonra mesaj yazan dekoratör oluşturun.
def mesaj_yazdir(func):
    def wrapper(*args, **kwargs):
        # Burayı tamamlayın
        pass
    return wrapper


@mesaj_yazdir
def selam_ver():
    print("Merhaba")
  1. Sadece pozitif sayılarda çalışan bir doğrulama dekoratörü yazın.
def pozitif_kontrol(func):
    def wrapper(sayi):
        # sayi <= 0 ise hata verin
        # değilse orijinal fonksiyonu çağırın
        pass
    return wrapper


@pozitif_kontrol
def karekok_al(sayi):
    return sayi ** 0.5
  1. Bir öğrenci listesini nota göre büyükten küçüğe sıralayın.
ogrenciler = [("Ali", 70), ("Zeynep", 92), ("Can", 85)]

# sorted(..., key=..., reverse=...) kullanarak sıralayın
  1. Bir listedeki tek sayıları hem filter ile hem liste üreteci ile yazın. Sonra hangi sürümün daha kolay okunduğunu iki cümleyle açıklayın.
sayilar = [1, 2, 3, 4, 5, 6, 7, 8, 9]

Teşekkürler