Decorator در پایتون چیست؟ آموزش Decorators به زبان ساده

در این آموزش با Decorator در پایتون آشنا می‌شویم و یاد می‌گیریم چگونه بدون تغییر مستقیم کد یک تابع، رفتار آن را گسترش دهیم.

Decorator در پایتون چیست؟

Decorator یا دکوراتور یکی از قابلیت‌های قدرتمند پایتون است که به ما اجازه می‌دهد رفتار یک تابع یا متد را بدون تغییر مستقیم کد اصلی آن تغییر دهیم یا امکانات جدیدی به آن اضافه کنیم.

دکوراتورها در پروژه‌های واقعی کاربردهای زیادی دارند؛ برای مثال می‌توان از آن‌ها برای ثبت زمان اجرای تابع، بررسی دسترسی کاربر، ثبت Log و موارد مشابه استفاده کرد.

تابع در پایتون یک شیء است

برای درک Decorator ابتدا باید بدانیم که در پایتون تابع‌ها نیز مانند بسیاری از اشیای دیگر می‌توانند داخل متغیر قرار بگیرند یا به عنوان آرگومان به تابع دیگری داده شوند.

def say_hello():
    print("Hello Python")

message = say_hello

message()

خروجی:

Hello Python

در این مثال، تابع say_hello را داخل متغیر message قرار داده‌ایم.

ارسال تابع به عنوان آرگومان

می‌توانیم یک تابع را به عنوان آرگومان به تابع دیگری ارسال کنیم.

def say_hello():
    print("Hello Python")

def execute(function):
    function()

execute(say_hello)

این مفهوم یکی از پایه‌های اصلی ساخت Decorator است.

تابع تو در تو یا Nested Function

تابعی که داخل تابع دیگری تعریف شود، Nested Function نام دارد.

def outer():

    def inner():
        print("Hello")

    inner()

outer()

Decoratorها معمولاً از تابع‌های تو در تو برای ایجاد تابع جدید استفاده می‌کنند.

ساخت اولین Decorator

یک Decorator ساده می‌تواند به شکل زیر نوشته شود:

def decorator(function):

    def wrapper():
        print("قبل از اجرای تابع")

        function()

        print("بعد از اجرای تابع")

    return wrapper

حالا می‌توانیم از این Decorator استفاده کنیم:

def say_hello():
    print("Hello Python")

say_hello = decorator(say_hello)

say_hello()

خروجی:

قبل از اجرای تابع
Hello Python
بعد از اجرای تابع

استفاده از علامت @

پایتون برای استفاده ساده‌تر از Decoratorها یک syntax مخصوص در اختیار ما قرار می‌دهد.

به جای:

say_hello = decorator(say_hello)

می‌توانیم بنویسیم:

@decorator
def say_hello():
    print("Hello Python")

علامت @ باعث می‌شود Decorator روی تابع اعمال شود.

Decorator با *args و **kwargs

اگر تابعی که قرار است Decorate شود آرگومان داشته باشد، باید Decorator بتواند این آرگومان‌ها را دریافت کند.

def decorator(function):

    def wrapper(*args, **kwargs):

        print("شروع اجرای تابع")

        result = function(*args, **kwargs)

        print("پایان اجرای تابع")

        return result

    return wrapper

حالا می‌توانیم تابعی با آرگومان‌های مختلف را Decorate کنیم.

@decorator
def add(a, b):
    return a + b

result = add(10, 20)

print(result)

خروجی:

شروع اجرای تابع
پایان اجرای تابع
30

functools.wraps چیست؟

هنگام استفاده از Decorator، اطلاعاتی مانند نام و توضیحات تابع اصلی ممکن است با اطلاعات تابع wrapper جایگزین شود.

برای جلوگیری از این مشکل می‌توانیم از functools.wraps استفاده کنیم.

from functools import wraps

def decorator(function):

    @wraps(function)
    def wrapper(*args, **kwargs):

        return function(*args, **kwargs)

    return wrapper

استفاده از wraps باعث می‌شود اطلاعات مهم تابع اصلی بهتر حفظ شوند.

مثال کاربردی: اندازه‌گیری زمان اجرای تابع

یکی از کاربردهای جالب Decorator، اندازه‌گیری زمان اجرای یک تابع است.

import time
from functools import wraps

def timer(function):

    @wraps(function)
    def wrapper(*args, **kwargs):

        start = time.time()

        result = function(*args, **kwargs)

        end = time.time()

        print("زمان اجرا:", end - start)

        return result

    return wrapper


@timer
def calculate():

    total = 0

    for i in range(1000000):
        total += i

    return total


calculate()

در این مثال Decorator قبل و بعد از اجرای تابع زمان را اندازه‌گیری می‌کند.

Decorator با آرگومان

گاهی می‌خواهیم خود Decorator نیز هنگام استفاده آرگومان دریافت کند. در این حالت معمولاً به یک تابع اضافی نیاز داریم.

def repeat(times):

    def decorator(function):

        @wraps(function)
        def wrapper(*args, **kwargs):

            for _ in range(times):
                function(*args, **kwargs)

        return wrapper

    return decorator


@repeat(3)
def hello():
    print("Hello Python")


hello()

خروجی:

Hello Python
Hello Python
Hello Python

استفاده از چند Decorator

می‌توان بیش از یک Decorator را روی یک تابع قرار داد.

@decorator_one
@decorator_two
def hello():
    print("Hello")

Decoratorها به ترتیب مشخصی روی تابع اعمال می‌شوند؛ بنابراین هنگام استفاده از چند Decorator باید ترتیب آن‌ها را در نظر بگیریم.

کاربردهای Decorator در پروژه‌های واقعی

  • ثبت Log و اطلاعات اجرای توابع
  • اندازه‌گیری زمان اجرای کد
  • بررسی دسترسی کاربران
  • کش کردن نتایج
  • اعتبارسنجی ورودی‌ها
  • اضافه کردن قابلیت‌های مشترک به چند تابع

Decoratorها در بسیاری از کتابخانه‌ها و فریم‌ورک‌های پایتون نیز استفاده می‌شوند.

اشتباهات رایج هنگام استفاده از Decorator

  • فراموش کردن return نتیجه تابع اصلی
  • عدم استفاده از *args و **kwargs در موارد لازم
  • نادیده گرفتن functools.wraps
  • پیچیده کردن بیش از حد Decoratorها

بهتر است Decoratorها را زمانی استفاده کنیم که واقعاً باعث ساده‌تر و قابل نگهداری‌تر شدن کد شوند.

تمرین‌های Decorator در پایتون

تمرین ۱

یک Decorator بنویسید که قبل از اجرای تابع عبارت شروع و بعد از آن عبارت پایان را چاپ کند.

تمرین ۲

یک Decorator بسازید که تعداد دفعات اجرای یک تابع را نمایش دهد.

تمرین ۳

یک Decorator بنویسید که زمان اجرای تابع را اندازه‌گیری کند.

تمرین ۴

یک Decorator بسازید که یک تابع را سه بار اجرا کند.

سوالات متداول

Decorator در پایتون چیست؟

Decorator ابزاری برای اضافه کردن یا تغییر رفتار یک تابع یا متد بدون تغییر مستقیم کد اصلی آن است.

علامت @ در پایتون چه کاربردی دارد؟

علامت @ برای اعمال ساده‌تر Decorator روی یک تابع یا متد استفاده می‌شود.

چرا از functools.wraps استفاده می‌کنیم؟

برای حفظ اطلاعاتی مانند نام و metadata تابع اصلی هنگام استفاده از Decorator.

آیا Decorator فقط برای تابع‌هاست؟

Decoratorها می‌توانند برای توابع و متدها استفاده شوند و در موارد مختلف برنامه‌نویسی کاربرد دارند.