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

2025

Giriş

Bu hafta Python’ın güçlü özelliklerinden dekoratörler ve fonksiyonel yaklaşımlar konularını işleyeceğiz. Bu sunumda:

  • Kodunuzu daha esnek, okunabilir ve yeniden kullanılabilir hale getirmenin yollarını öğreneceksiniz.
  • Bolca örnekle konuları pekiştireceğiz.

Dekoratör Nedir?

  • Tanım: Fonksiyonların veya sınıfların davranışlarını değiştirmek, onlara yeni yetenekler katmak için kullanılan özel fonksiyonlardır.
    • Bir fonksiyonu alır, ona ek özellikler ekler ve yeni bir fonksiyon döndürür.
  • Amaç:
    • Kod tekrarını önler (DRY: Don’t Repeat Yourself).
    • Modülerliği ve okunabilirliği artırır.
  • Kullanım Alanları: Loglama, zaman ölçümü, yetkilendirme, önbellekleme.

Temel Kullanım

Dekoratörler @dekoratör_adı ile fonksiyonun üstüne yazılır:

def my_decorator(func):
    def wrapper():
        print("Fonksiyon öncesi işlem.")
        func()
        print("Fonksiyon sonrası işlem.")
    return wrapper

@my_decorator
def say_hello():
    print("Merhaba!")

say_hello()

Temel Kullanım (Devamı)

Çıktı:

Fonksiyon öncesi işlem.
Merhaba!
Fonksiyon sonrası işlem.

Açıklama:

  • my_decorator, say_hello fonksiyonunu sarmalar ve ek işlemler yapar.
  • @my_decorator, say_hello = my_decorator(say_hello) anlamına gelir.

Örnek 1: Zaman Ölçümü

Bir fonksiyonun çalışma süresini ölçen dekoratör:

import time

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

@timer
def slow_function():
    time.sleep(2)
    print("Fonksiyon çalıştı.")

slow_function()

Örnek 1: Zaman Ölçümü (Devamı)

Çıktı:

Fonksiyon çalıştı.
slow_function 2.0023 saniye sürdü.

Açıklama: *args ve **kwargs sayesinde dekoratör esnek hale gelir.

Örnek 2: Çağrı Sınırı

Fonksiyonun kaç kez çağrılabileceğini sınırlayalım:

def limit_calls(max_calls):
    def decorator(func):
        calls = 0
        def wrapper(*args, **kwargs):
            nonlocal calls
            if calls < max_calls:
                calls += 1
                return func(*args, **kwargs)
            else:
                print(f"{func.__name__} maksimum çağrı sayısına ulaştı.")
        return wrapper
    return decorator

Örnek 2: Çağrı Sınırı (devam)

@limit_calls(3)
def say_hello():
    print("Merhaba!")

for _ in range(5):
    say_hello()

Örnek 2: Çağrı Sınırı (Devamı)

Çıktı:

Merhaba!
Merhaba!
Merhaba!
say_hello maksimum çağrı sayısına ulaştı.
say_hello maksimum çağrı sayısına ulaştı.

Açıklama: nonlocal calls ile içteki değişken kontrol edilir.

Örnek 3: Yetkilendirme (Slayt 1)

Yetkilendirme kontrolü yapan dekoratör (uzun kod, 2 slayta bölündü):

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

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

Açıklama: Kullanıcı sınıfı ve dekoratör tanımı burada yapıldı.

Örnek 3: Yetkilendirme (Slayt 2)

@require_role("admin")
def access_secret_data(user):
    return "Gizli Veri: XYZ123"

admin = User("Ali", "admin")
user = User("Ayşe", "user")

print(access_secret_data(admin))  # Çalışır: "Gizli Veri: XYZ123"
# print(access_secret_data(user))  # Hata: PermissionError

Açıklama: admin yetkisi olan kullanıcı erişebilir, diğerleri hata alır.

Örnek 4: Önbellekleme (Memoization)

Fibonacci hesaplarken önbellek kullanalım:

from time import sleep

def memoize(func):
    cache = {}
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

@memoize
def fibonacci(n):
    if n <= 1:
        return n
    sleep(0.1) # Gecikleme simülasyonu için
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(30))  # İlk hesaplama
print(fibonacci(30))  # Önbellekten gelir

Açıklama: Tekrarlanan hesaplamalar önbellekten alınarak hızlanır.

Sınıf Dekoratörleri

  • Sınıf dekoratörleri, sınıfların davranışlarını veya yapısını değiştirmek için kullanılır.
  • Fonksiyon dekoratörlerine benzer, ancak hedef sınıf düzeyindedir.

Örnek 1: Metod Ekleme

Sınıfa yeni bir metod ekleyelim:

def add_method(method):
    def decorator(cls):
        setattr(cls, method.__name__, method)
        return cls
    return decorator

def print_hello(self):
    print("Merhaba, dünya!")

@add_method(print_hello)
class MyClass:
    pass

obj = MyClass()
obj.print_hello()

Örnek 1: Metod Ekleme (devam)

Çıktı:

Merhaba, dünya!

Açıklama: print_hello metodu sınıfa dinamik olarak eklenir.

setattr Fonksiyonu

  • Tanım: Bir nesneye dinamik olarak bir özellik veya metod eklemek için kullanılır.
  • Sözdizimi: setattr(object, name, value)
  • Parametreler:
    • object: Özellik eklemek istediğiniz nesne.
    • name: Eklemek istediğiniz özelliğin adı (string).
    • value: Özelliğin değeri.
  • Örnek:
class MyClass:
    pass
obj = MyClass()
# Dinamik olarak yeni bir özellik ekleyelim
setattr(obj, 'new_attr', 42)
print(obj.new_attr)  # 42
  • Açıklama: setattr, obj nesnesine new_attr adında bir özellik ekler.

Örnek 2: Metodları Loglama

Sınıfın metodlarını loglayalım:

def log_methods(cls):
    for name, value in cls.__dict__.items():
        if callable(value) and not name.startswith("__"):
            setattr(cls, name, log_decorator(value))
    return cls

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

@log_methods
class MyClass:
    def method1(self):
        print("Method1 çalıştı.")

obj = MyClass()
obj.method1()

Örnek 2: Metodları Loglama (devam)

Çıktı:

method1 çağrıldı.
Method1 çalıştı.

Lambda İfadeleri ve Fonksiyonel Yaklaşımlar

Lambda İfadeleri

  • Tanım: Anonim (isimsiz) fonksiyonlar oluşturmak için kullanılır.
  • Sözdizimi: lambda arguments: expression
  • Kullanım: Kısa, tek satırlık fonksiyonlar için idealdir.
square = lambda x: x ** 2
print(square(5))  # 25

Örnek 1: Map ile Kullanım

Bir listenin elemanlarına fonksiyon uygulayalım:

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

Açıklama: map, her elemana lambda fonksiyonunu uygular.

Örnek 2: Filter ile Kullanım

Listeden eleman filtreleyelim:

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

Açıklama: filter, çift sayıları seçer.

Örnek 3: Reduce ile Kullanım

Listeyi tek bir değere indirgeyelim:

from functools import reduce

numbers = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, numbers)
print(product)  # 120

Açıklama: reduce, elemanları çarpar.

Örnek 4: Sıralama ile Kullanım

Tuple listesini sıralayalım:

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

Açıklama: Notlara göre sıralama yapılır.

Örnek 5: Gerçek Dünya Uygulaması (Slayt 1)

E-ticaret ürün analizi (uzun kod, 2 slayta bölündü):

products = [
    {"id": 1, "name": "Laptop", "price": 5000, "stock": 10, "category": "Elektronik"},
    {"id": 2, "name": "Mouse", "price": 200, "stock": 50, "category": "Elektronik"},
    {"id": 3, "name": "Kitap", "price": 50, "stock": 100, "category": "Kitap"}
]

Açıklama: Ürün listesi tanımlandı.

Örnek 5: Gerçek Dünya Uygulaması (Slayt 2)

# Elektronik ürünlerin ortalama fiyatı
electronics = list(filter(lambda p: p["category"] == "Elektronik", products))
prices = list(map(lambda p: p["price"], electronics))
avg_price = sum(prices) / len(prices)
print(f"Elektronik ortalama fiyat: {avg_price}")  # 2600.0

Açıklama: Fonksiyonel araçlarla veri işlenir.

Özet

  • Dekoratörler:
    • Fonksiyon ve sınıflara esneklik katar.
    • Örnekler: Zaman ölçümü, yetkilendirme, önbellekleme.
  • Fonksiyonel Yaklaşımlar:
    • lambda, map, filter, reduce ile kısa ve etkili kod.
    • Veri işleme ve sıralama için güçlü araçlar.
  • Öneri: Bol pratik yapın!

Alıştırmalar

  1. Bir fonksiyonun çalışma süresini ölçen dekoratör yazın.
  2. map ile bir listedeki kelimelerin uzunluklarını hesaplayın.
  3. filter ile 70 üzeri notları seçen bir fonksiyon yazın.

Teşekkürler