الدرس 7 من 8

الـ Decorators

الـ @ اللي هتشوفها فوق دوال كتير.

الفكرة ببساطة

الـ decorator زي جراب الموبايل: الموبايل هو هو، بس الجراب ضاف عليه حماية وحامل. الـ decorator بياخد function، ويلفها بكود زيادة قبلها أو بعدها، من غير ما تلمس الكود الأصلي.

الأساس: الدوال في بايثون قيم عادية

functions_are_objects.py
def shout(text):
    return text.upper()

say = shout              # no () -> we pass the function itself
print(say("hello"))

def apply(func, value):  # a function that receives another function
    return func(value)

print(apply(shout, "ahlan"))

أول decorator: قيس الوقت

timer.py
import time
from functools import wraps

def timer(func):
    @wraps(func)                       # keeps the original function's name
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs) # call the original function
        elapsed = time.time() - start
        print(f"{func.__name__} took {elapsed:.2f}s")
        return result
    return wrapper

@timer                                 # same as: slow_report = timer(slow_report)
def slow_report():
    time.sleep(1.2)
    return "report ready"

print(slow_report())
wrapper الدالة الجديدة اللي بتلف الأصلية.
*args بتستقبل أي عدد من الـ arguments العادية.
**kwargs بتستقبل أي عدد من الـ keyword arguments.
@wraps بتحافظ على اسم ووصف الدالة الأصلية.
🌍 مثال واقعي: صلاحيات الدخولفي أي موقع، صفحات زي «طلباتي» مش المفروض تفتح غير لو اليوزر عامل login. بدل ما تكتب الشرط ده في كل صفحة، بتعمل decorator وتحطه فوق كل صفحة محتاجاه.
login_required.py
current_user = {"name": "Sara", "logged_in": False}

def login_required(func):
    def wrapper(*args, **kwargs):
        if not current_user["logged_in"]:
            print("Please log in first")
            return None
        return func(*args, **kwargs)
    return wrapper

@login_required
def show_orders():
    print(f"Orders for {current_user['name']}: #1024, #1031")

show_orders()
current_user["logged_in"] = True
show_orders()

الـ @property: decorator جاهز هتستخدمه كتير

property.py
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    @property
    def area(self):             # computed, but used like an attribute
        return self.width * self.height

r = Rectangle(4, 5)
print(r.area)                   # no () needed
💡 هتقابلها فين؟في FastAPI هتكتب @app.get("/users") فوق كل endpoint، وفي PyTorch هتشوف @torch.no_grad(). انت مش لازم تكتب decorators كتير، بس لازم تفهمها لما تشوفها.
الدرس اللي فات