Skip to content

דסקריפטורים ב־ Python

🧩 דסקריפטורים ב־ Python: שליטה מתקדמת בגישה לאטריבוטים

דסקריפטור הוא אובייקט שמאפשר לך לשלוט בגישה לאטריבוטים של מחלקה באמצעות הגדרת מתודות מיוחדות: __get__, __set__ ו־__delete__. באמצעותם ניתן להוסיף לוגיקה מותאמת אישית בעת קריאה, כתיבה או מחיקה של אטריבוט.

🔧 מתודות בסיסיות בפרוטוקול הדסקריפטור

  • __get__(self, instance, owner): נקראת כאשר ניגשים לאטריבוט.
  • __set__(self, instance, value): נקראת כאשר מגדירים ערך לאטריבוט.
  • __delete__(self, instance): נקראת כאשר מוחקים את האטריבוט.
  • __set_name__(self, owner, name): מתודה אופציונלית שנקראת בעת יצירת המחלקה, ומאפשרת לדסקריפטור לדעת את שם האטריבוט אליו הוא משויך.

דוגמה בסיסית:

class Descriptor:
    def __get__(self, instance, owner):
        return 'ערך'

class MyClass:
    attr = Descriptor()

obj = MyClass()
print(obj.attr)  # יפלט 'ערך'

🧪 דוגמה עם __set__

class Descriptor:
    def __set__(self, instance, value):
        print(f"הוגדר הערך {value}")
        self._value = value

class MyClass:
    attr = Descriptor()

obj = MyClass()
obj.attr = 10  # יפלט 'הוגדר הערך 10'

🗑️ דוגמה עם __delete__

class Descriptor:
    def __delete__(self, instance):
        print("האטריבוט נמחק")
        del self._value

class MyClass:
    attr = Descriptor()

obj = MyClass()
del obj.attr  # יפלט 'האטריבוט נמחק'

🏷️ שימוש ב־__set_name__ לשמות פרטיים

class Descriptor:
    def __set_name__(self, owner, name):
        self.public_name = name
        self.private_name = '_' + name

    def __get__(self, instance, owner):
        return getattr(instance, self.private_name, 'לא הוגדר')

    def __set__(self, instance, value):
        setattr(instance, self.private_name, value)

class MyClass:
    attr = Descriptor()

obj = MyClass()
print(obj.attr)  # יפלט 'לא הוגדר'
obj.attr = 99
print(obj.attr)  # יפלט 99

✅ דוגמה: ולידציה של גיל

class ValidateAge:
    def __set_name__(self, owner, name):
        self.private_name = '_' + name

    def __get__(self, instance, owner):
        return getattr(instance, self.private_name, None)

    def __set__(self, instance, value):
        if not (0 <= value <= 100):
            raise ValueError("הגיל חייב להיות בין 0 ל־100")
        setattr(instance, self.private_name, value)

class Person:
    age = ValidateAge()

    def __init__(self, name, age):
        self.name = name
        self.age = age

try:
    p = Person("קוליה", 30)
    print(p.age)
    p.age = -5  # יגרום ל־ValueError
except ValueError as e:
    print(e)

🧠 דוגמה: קאשינג של חישוב כבד

import time

class CachedAttribute:
    def __init__(self, method):
        self.method = method
        self.cache = {}

    def __get__(self, instance, owner):
        if instance not in self.cache:
            self.cache[instance] = self.method(instance)
        return self.cache[instance]

class HeavyComputation:
    @CachedAttribute
    def compute(self):
        time.sleep(2)
        return "תוצאה"

hc = HeavyComputation()
start = time.time()
print(hc.compute)  # חישוב ראשון
print(f"זמן: {time.time() - start} שניות")

start = time.time()
print(hc.compute)  # תוצאה מקאש
print(f"זמן: {time.time() - start} שניות")

📝 דוגמה: לוג של שינויים

class LoggedAttribute:
    def __set_name__(self, owner, name):
        self.private_name = '_' + name

    def __get__(self, instance, owner):
        return getattr(instance, self.private_name, None)

    def __set__(self, instance, value):
        print(f"הוגדר {self.private_name} ל־{value}")
        setattr(instance, self.private_name, value)

class User:
    name = LoggedAttribute()
    age = LoggedAttribute()

    def __init__(self, name, age):
        self.name = name
        self.age = age

u = User("קטיה", 30)
u.name = "קטיושה"
u.age = 31

🧱 דוגמה: Singleton

class Singleton:
    def __init__(self, cls):
        self.cls = cls
        self.instance = None

    def __get__(self, instance, owner):
        if self.instance is None:
            self.instance = self.cls()
        return self.instance

class Database:
    def __init__(self):
        print("נוצרה מסד נתונים")

class AppConfig:
    db = Singleton(Database)

config1 = AppConfig()
config2 = AppConfig()
db1 = config1.db
db2 = config2.db
print(db1 is db2)  # True

🏭 דוגמה: Factory

class VehicleFactory:
    def __init__(self, cls):
        self.cls = cls

    def __get__(self, instance, owner):
        return self.cls()

class Car:
    def drive(self):
        print("נהיגה ברכב")

class Bike:
    def ride(self):
        print("רכיבה על אופניים")

class AppConfigCar:
    vehicle = VehicleFactory(Car)

class AppConfigBike:
    vehicle = VehicleFactory(Bike)

car_config = AppConfigCar()
car = car_config.vehicle
car.drive()

bike_config = AppConfigBike()
bike = bike_config.vehicle
bike.ride()

🏠 שימוש ב־@property

class Celsius:
    def __init__(self, temperature=0):
        self._temperature = temperature

    @property
    def temperature(self):
        print("קבלת ערך")
        return self._temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273.15:
            raise ValueError("הטמפרטורה לא יכולה להיות מתחת ל־273.15°C")
        print("הגדרת ערך")
        self._temperature = value

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

c = Celsius(37)
print(c.temperature)
c.temperature = -300  # יגרום ל־ValueError

⚠️ הערות חשובות

  • דסקריפטורים עם __set__ (data descriptors) גוברים על אטריבוטים של מופע.
  • דסקריפטורים ללא __set__ (non-data descriptors) ניתנים להחלפה על ידי אטריבוטים של מופע.
  • גישה לאטריבוט דרך __dict__ של מופע עוקפת את הדסקריפטור.
  • כאשר instance הוא None ב־__get__, יש להחזיר את הדסקריפטור עצמו (self).

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *