در این مطلب، دکوراتورها در پایتون (Python Decorators) مورد بررسی قرار می‌گیرند و با ارائه مثال‌هایی، مفهوم، چگونگی استفاده و کاربرد آن‌ها بیان می‌شود. یک دکوراتور، یک تابع را دریافت کرده، کارکردهایی را به آن می‌افزاید و آن را باز می‌گرداند. در ادامه مطلب دکوراتورها در پایتون، چگونگی ساخت یک دکوراتور و چرایی استفاده از آن بیان شده است.

مفهوم دکوراتورها در پایتون

پایتون دارای یک قابلیت جالب با عنوان «دکوراتور» (Decorators) برای افزایش کارایی یک کد موجود است. به این کار، «متاپروگرمینگ» (Metaprogramming) نیز گفته می‌شود، زیرا با بهره‌گیری از آن، یک بخش از برنامه تلاش می‌کند تا بخش دیگری از برنامه را در زمان اجرا ویرایش کند.

پیش‌نیازهای لازم برای یادگیری دکوراتورها

به منظور درک دکوراتورها، ابتدا باید برخی از مفاهیم پایه‌ای را در «زبان برنامه‌نویسی پایتون» (Python Programming Language) دانست. مساله‌ای که باید به خوبی آن را درک کرد این است که همه چیز در پایتون (بله؛ همه چیز، حتی کلاس‌ها) شی (Object) هستند. اسامی که تعریف می‌شوند، شناساگرهایی (Identifiers) هستند که به این اشیا گره خورده‌اند. توابع نیز از این قاعده مستثنی نیستند و آن‌ها نیز شی هستند (با خصیصه‌ها). اسامی متفاوت گوناگون را می‌توان به شی تابع مشابهی تخصیص داد.

def first(msg):
    print(msg)    

first("Hello")

second = first
second("Hello")

هنگامی که کد بالا اجرا می‌شود، هر دو تابع first و second خروجی مشابهی خواهند داشت. در اینجا، نام first و second به شی تابع مشابهی ارجاع دارند. اکنون، همه چیز عجیب‌تر می‌شود. تابع‌ها را می‌توان به عنوان آرگومان به تابع دیگر ارجاع داد. اگر کاربر از توابعی مانند filter ،map و reduce در پایتون استفاده کرده باشد، با این موضوع آشنایی دارد. چنین توابعی که تابع دیگری را به عنوان آرگومان دریافت می‌کنند «توابع مرتبه بالاتر» (Higher Order Functions) خوانده می‌شوند. در ادامه، مثالی از چنین توابعی ارائه شده است.

def inc(x):
    return x + 1

def dec(x):
    return x - 1

def operate(func, x):
    result = func(x)
    return result

توابع، به صورت زیر فراخوانی می‌شوند.

>>> operate(inc,3)
4
>>> operate(dec,3)
2

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

def is_called():
    def is_returned():
        print("Hello")
    return is_returned

new = is_called()

#Outputs "Hello"
new()

در اینجا، ()is_returned یک تابع تو در تو است که هر بار ()is_called فراخوانی شود، تعریف و بازگردانده می‌شود.

دکوراتورها در پایتون

توابع و متدها در پایتون «قابل فراخوانی» (Callable) نامیده می‌شوند زیرا همانطور که از نام آن‌ها پیداست، می‌توان آن‌ها را فایل فراخوانی کرد. در حقیقت، هر شیئی که متد ویژه ()__call__ را پیاده‌سازی کند، قابل فراخوانی محسوب می‌شود. بنابراین، در پایه‌ای‌ترین حالت، یک دکوراتور یک قابل فراخوانی است که یک قابل فراخوانی را باز می‌گرداند. به طور پایه‌ای، یک دکوراتور تابع را دریافت می‌کند، کارکردهایی را به آن اضافه می‌کند و آن را باز می‌گرداند.

def make_pretty(func):
    def inner():
        print("I got decorated")
        func()
    return inner

def ordinary():
    print("I am ordinary")

با اجرای کد زیر در شل پایتون، می‌توان این مبحث را بهتر درک کرد.

>>> ordinary()
I am ordinary

>>> # let's decorate this ordinary function
>>> pretty = make_pretty(ordinary)
>>> pretty()
I got decorated
I am ordinary

در مثال نشان داده شده در بالا، ()make_pretty یک دکوراتور است. در مرحله تخصیص، داریم که:

pretty = make_pretty(ordinary)

تابع ()ordinary دکوریت می‌شود و تابعی را باز می‌گرداند که نام pretty به آن داده شده است. می‌توان مشاهده کرد که تابع دکوراتور کارکردهای جدیدی را به تابع اصلی می‌افزاید. این امر مشابه بسته‌بندی یک هدیه است. دکوراتور مانند یک «پوشش‌دهنده» (Wrapper) عمل می‌کند. ماهیت شیئی که دکوریت می‌شود (در  واقع هدیه درون آن است) تغییر نمی‌کند. اما  اکنون، زیبا به نظر می‌رسد زیرا دکوریت شده است. به طور کلی، یک تابع را به صورت زیر دکوریت کرده و آن را مجددا تخصیص می‌دهند.

ordinary = make_pretty(ordinary).

این یک ساختار متداول است و به همین دلیل، پایتون دارای «نحو» (Syntax) خاصی برای ساده کردن آن است. می‌توان از نماد @ همراه با نام تابع دکوراتر استفاده کرد و آن را بالای تعریف تابع برای دکوریت شدن قرار داد. برای مثال:

@make_pretty
def ordinary():
    print("I am ordinary")

کد بالا برابر با کد زیر است.

def ordinary():
print("I am ordinary")
ordinary = make_pretty(ordinary)

این فقط یک «نحو شیرین» (Syntactic Sugar) برای تسهیل پیاده‌سازی دکوراتورها است.

دکوریت کردن توابع با پارامترها

دکوراترهای بالا ساده هستند و تنها با توابعی کار می‌کنند که هیچ پارامتری ندارند. اما اگر توابع پارامترها را به صورت زیر دریافت کنند چه می‌شود؟

def divide(a, b):
return a/b

این تابع دارای دو پارامتر a و b است. واضح است که اگر b با مقدار ۰ پاس داده شود، پیغام خطا صادر می‌شود.

>>> divide(2,5)
0.4
>>> divide(2,0)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero

اکنون، دکوراتوری برای بررسی این مورد که منجر به خطا می‌شود، ایجاد شده است.

def smart_divide(func):
   def inner(a,b):
      print("I am going to divide",a,"and",b)
      if b == 0:
         print("Whoops! cannot divide")
         return

      return func(a,b)
   return inner

@smart_divide
def divide(a,b):
    return a/b

این پیاده‌سازی جدید، در صورتی که شرایط خطا ایجاد شود، None را باز می‌گرداند.

>>> divide(2,5)
I am going to divide 2 and 5
0.4

>>> divide(2,0)
I am going to divide 2 and 0
Whoops! cannot divide

در این شرایط، می‌توان توابعی که پارامترها را دریافت می‌کنند دکوریت کرد. یک مخاطب با دقت متوجه خواهد شد که پارامترهای تابع تویی ()inner درون دکوراتور، مشابه پارامترهای توابعی هستند که دکوریت می‌کنند. با در نظر داشتن این موضوع، می‌توان دکوراتورهای عمومی (کلی) ساخت که با هر تعداد پارامتر کار می‌کنند. در پایتون، این کار فوق‌العاده جالب با استفاده از (function(*args, **kwargs انجام می‌شود. بدین شکل، args تاپل آرگومان‌های موقعیتی خواهد بود و kwargs دیکشنری (Dictionary) آرگومان‌های کلیدواژه‌ها است. مثالی از چنین دکوراتوری به صورت زیر است.

def works_for_all(func):
def inner(*args, **kwargs):
print("I can decorate any function")
return func(*args, **kwargs)
return inner

زنجیر کردن دکوراتورها در پایتون

چندین دکوراتور را می‌توان در پایتون به یکدیگر زنجیر کرد. این یعنی یک تابع را می‌توان چندین بار با دکوراتورهای گوناگون دکوریت کرد. به سادگی می‌توان دکوراتورها را بالای تابع مورد نظر قرار داد.

def star(func):
    def inner(*args, **kwargs):
        print("*" * 30)
        func(*args, **kwargs)
        print("*" * 30)
    return inner

def percent(func):
    def inner(*args, **kwargs):
        print("%" * 30)
        func(*args, **kwargs)
        print("%" * 30)
    return inner

@star
@percent
def printer(msg):
    print(msg)
printer("Hello")

خروجی پایتون به صورت زیر خواهد بود.

******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************

نحو بالا برای:

@star
@percent
def printer(msg):
print(msg)

برابر با نحو زیر است:

def printer(msg):
print(msg)
printer = star(percent(printer))

ترتیبی که در آن دکوراترها به هم متصل می‌شوند حائز اهمیت است. اگر ترتیب به صورت زیر معکوس شود:

@percent
@star
def printer(msg):
print(msg)

اجرا به صورت زیر انجام می‌شود:

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
Hello
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

اگر این مطلب برای شما مفید بوده است، آموزش‌ها و مطالب زیر نیز به شما پیشنهاد می‌شوند:

بر اساس رای ۲۳ نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

«الهام حصارکی»، فارغ‌التحصیل مقطع کارشناسی ارشد مهندسی فناوری اطلاعات، گرایش سیستم‌های اطلاعات مدیریت است. او در زمینه هوش مصنوعی و داده‌کاوی، به ویژه تحلیل شبکه‌های اجتماعی، فعالیت می‌کند.

3 نظر در “دکوراتورها در پایتون | به زبان ساده

  • خیلی خوب توضیح دادید. ممنون . کاربردهای دکوریتورها بیشتر کجا هست؟ مثلا یک نمونه گفتید در رفع خطا مثلا خطای تقسیم بر صفر.

    1. پارسا غلام‌پور — says: ۴ شهریور، ۱۴۰۱ در ۱۰:۴۰ ب٫ظ

      ۲ مثال از موارد استفاده یا کاربردهای دکوریتورها:
      ۱. هنگامی که نیاز دارید رفتار یک تابع رو تغییر بدید، بدون اینکه خود اون تابع رو تغییر بدید. یک مثال ساده: زمانی که می‌خواید یه تابع ثبت سفارش فقط و فقط در صورتی انجام بشه که کاربر قبلش لاگین کرده.
      ۲. در مواقعی که می‌خواید یک کد روی توابع مختلفی اجرا بشه، که این یعنی پیشگیری از اینکه واسه هر فانکشن کدهای تکراری بنویسیم.

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد.