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

Motivasyon: Bu Konu Neden Var?

Geçen haftalarda sınıfların davranışlarını tasarladık.

Bu hafta ise şu soruya geçiyoruz:

“Davranışı, fonksiyon seviyesinde nasıl esnek hale getiririm?”

  • Bazen bir fonksiyonun öncesine veya sonrasına ek iş koymak isteriz.
  • Bazen aynı veri işleme mantığını kısa ve düzenli biçimde ifade etmek isteriz.
  • Bazen de kod tekrarını azaltırken okunabilirliği korumamız gerekir.

Bugünkü iki ana araç:

  1. Dekoratörler
  2. 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

Önceki Haftalarla Bağlantı

Bu konu, önceki haftaların doğal devamıdır:

  1. Kapsülleme ve soyutlamada arayüzün önemini gördük.
  2. Polimorfizmde aynı çağrının farklı davranışlar üretebildiğini gördük.
  3. Geçen hafta Python’ın sınıflarınızdan bazı özel davranışlar beklediğini gördük.
  4. Bu hafta ise fonksiyonları da birer nesne gibi ele alıp davranışı saracağız.

Kısaca:

Geçen hafta sınıf davranışını şekillendirdik; bu hafta fonksiyon davranışını şekillendiriyoruz.

Büyük Resim

Bugün iki temel soruya cevap arıyoruz:

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

İlk sorunun ana cevabı: dekoratörler

İkinci sorunun ana araçları: fonksiyonel yaklaşımlar

Ö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.

Kısa Örnek: Fonksiyon Parametre Olarak Geçmek

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 Mantığının Çekirdeği

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ı Akışı

Dekoratörü şöyle düşünmek faydalıdır:

  • asıl iş, orijinal fonksiyonun içindedir,
  • dışarıda bir wrapper vardır,
  • çağrı önce wrapper içine gelir,
  • sonra orijinal fonksiyon çalıştırılır,
  • gerekirse çağrı öncesine ve sonrasına ek davranış eklenir.

Kısaca akış şöyledir:

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

Dekoratör Nedir?

  • Loglama eklemek
  • Zaman ölçmek
  • Yetki kontrolü yapmak
  • Önbellekleme uygulamak
  • Girdi doğrulama yapmak

gibi tekrar eden işleri her fonksiyona tek tek yazmak yerine ortaklaştırır.

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:

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 en sık yapılan hatalardan biri de şudur:

  • func(...) çağrılır
  • ama sonucu 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():
    return "Merhaba"


print(selam.__name__)  # wrapper

@wraps(func) kullanıldığında bu isim bilgisi korunur ve çıktı selam olur.

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ü

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 örnek, ilk parametresi user olan fonksiyonlar için yazılmıştır.

Bu Örnekte Tasarım Açısından Ne Öğreniyoruz?

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

Bu bize şunu gösterir:

  • iş kuralı merkezi hale gelir,
  • tekrar azalır,
  • 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

Pratik ilke:

Dekoratör güçlüdür; ama okunabilirlikten pahalıya gelmemelidir.

Geçiş: Fonksiyonel Yaklaşımlar

Şimdi odağı biraz değiştiriyoruz.

Dekoratörlerde bir fonksiyonun etrafına yeni davranış ekledik.

Bu bölümde ise fonksiyonları, veri işlerken kullandığımız küçük araçlar gibi düşüneceğiz.

Yani artık şu soruya bakıyoruz:

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.

Fonksiyonel Araçlar Tablosu

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?”

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)

filter(...) için de aynı durum geçerlidir: sonuç doğrudan liste olmadığı için list(...) kullanıldı.

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 burada verilmek istenen mesaj şudur:

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

Pratik Tercih

Python’da map ve filter bilinmelidir.

Ama günlük kullanımda birçok durumda liste üreteçleri daha doğal okunur.

Bu yüzden seçim yaparken şu soru daha yararlıdır:

Bu kodu bir hafta sonra en hızlı hangi biçimde okuyabilirim?

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 toplama 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.

Gerçek Dünya Akışı: Veri İşleme

products = [
    {"name": "Laptop", "price": 50000, "stock": 5, "category": "Elektronik"},
    {"name": "Mouse", "price": 1200, "stock": 25, "category": "Elektronik"},
    {"name": "Kitap", "price": 350, "stock": 40, "category": "Kitap"},
]

electronics = [product for product in products if product["category"] == "Elektronik"]
prices = [product["price"] for product in electronics]
avg_price = sum(prices) / len(prices)

print(avg_price)

Bu örnek özellikle şu açıdan önemli:

  • filtreleme var,
  • dönüştürme var,
  • özetleme var.

Yani gerçek veri işleme problemleri çoğu zaman bu üç adımın birleşimidir.

Hangi Aracı Ne Zaman Seçmeliyim?

Pratik karar çerçevesi:

  • basit dönüşüm varsa: liste üreteci veya map
  • basit filtre varsa: liste üreteci veya filter
  • tek ölçüte göre sıralama varsa: sorted(key=...)
  • gerçekten kümülatif bir indirgeme gerekiyorsa: reduce

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.

Bu Hafta Kapsam Dışında Bıraktıklarımız

Bilişsel yükü kontrollü tutmak için bugün şunlara ayrıntılı girmiyoruz:

  • sınıf dekoratörleri,
  • birden fazla dekoratörün sıralı etkisi,
  • yerleşik dekoratörler (@property, @classmethod, @staticmethod),
  • jeneratörler ve iterator tabanlı fonksiyonel akışlar.

Bunlar önemlidir; ancak bugünkü çekirdek kavram önce fonksiyon dekoratörlerini doğru anlamaktır.

Alıştırmalar

  1. Bir fonksiyon çağrılmadan önce ve sonra mesaj yazan dekoratör oluşturun.
  2. Sadece pozitif sayılarda çalışan bir doğrulama dekoratörü yazın.
  3. Bir öğrenci listesini nota göre büyükten küçüğe sıralayın.
  4. Bir listedeki tek sayıları hem filter ile hem liste üreteci ile yazın ve hangisinin daha okunur olduğunu tartışın.

Teşekkürler