🧩 דסקריפטורים ב־ Python: שליטה מתקדמת בגישה לאטריבוטים
דסקריפטור הוא אובייקט שמאפשר לך לשלוט בגישה לאטריבוטים של מחלקה באמצעות הגדרת מתודות מיוחדות: __get__
, __set__
ו־__delete__
. באמצעותם ניתן להוסיף לוגיקה מותאמת אישית בעת קריאה, כתיבה או מחיקה של אטריבוט.
Contents
🔧 מתודות בסיסיות בפרוטוקול הדסקריפטור
__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
).