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

گردآوری و تالیف : ارسطو عباسی
تاریخ انتشار : 03 آبان 1397
دسته بندی ها : پایتون

دکوراتورها در پایتون ویژگی بسیار قدرتمندی هستند که به شما قابلیت تلفیق یک تابع با توابع دیگری را می‌دهند. 

ایده دکوراتورها این است که بتوانیم فانکشنالیتی بیشتری را به یک کلاس یا تابع اضافه کنیم. منظور این است که همراه با کارایی خود تابع، ما یکسری کارکردهای خارجی را نیز به تابع اضافه کنیم. استفاده از این تکنیک می‌تواند در بسیاری از شرایط مفید باشد. برای مثال وقتی که می‌خواهید کدهای‌تان قابلیت استفاده‌پذیری مجدد را داشته باشند، دکوراتورها می‌توانند به شما کمک بکنند. 

با یادگیری شیوه استفاده از دکوراتورها، این قابلیت را پیدا خواهید کرد که بتوانید به صورتی شگفت‌ انگیز قابلیت خوانایی کدهای‌تان را افزایش دهید. دکوراتورها بدون داشتن دسترسی مستقیم به بلاک فانکشن این قابلیت را دارند که رفتار تابع را تغییر دهند و کدهای جدیدی را به آن اضافه کنند. در روند یادگیری زبان برنامه‌نویسی پایتون، استفاده صحیح از دکوراتور موضوعی است که باید آن را به خوبی یاد بگیرید. افرادی که از فلسک یا فریمورکی مانند Click استفاده می‌کنند به خوبی با دکوراتورها آشنایی دارند، اما در یک محدوده وسیعی از توسعه‌دهندگان، آن‌ها تنها شیوه استفاده از آن را می‌دانند اما خودشان نمی‌توانند یک دکوراتور بنویسند. 

دکوراتورها چگونه کار می‌کنند

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

@my_decorator

def hello():
    print('hello')

نکته: وقتی در پایتون یک تابع را فراخوانی کنید، آن تابع تبدیل به یک شئ از کلاس اصلی برنامه می‌شود.

تابع Hello یک شئ تابعی است. @my_decorator که در بالای تابع تعریف شده است، در حقیقت خود یک تابع است که قابلیت استفاده از شئ hello –شئ تابعی- را دارد. بعد از دسترسی داشتن به این شئ، دکوراتور یک شئ متفاوت را برای مفسر ارسال می‌کند. تابعی که دکوراتور برگشت می‌دهد نیز به عنوان hello شناخته می‌شود. 

در حقیقت می‌توان بسیار ساده‌تر با در نظر گرفتن حالت زیر آن را در به خاطر سپرد:

hello = decorate(hello)

hello یک شئ است که از دکورات کردن تابع hello به دست می‌آید. دکوارتور قابلیت استفاده از یک تابع به عنوان ورودی را دارد اما الزامی برای خروجی فرستادن به صورت یک تابع وجود ندارد.

ایجاد یک دکوراتور

همانطور که گفتیم، دکوراتور یک تابع است که یک تابع دیگر را به عنوان ورودی دریافت می‌کند و در نهایت یک شئ را برگشت می‌دهد. بنابراین برای ایجاد یک دکوراتور تنها کافی‌ست که یک تابع را ایجاد کنید:

def my_decorator(f):
    return 5

می‌توان از هر تابعی به عنوان یک دکوراتور استفاده کرد. در این مثال دکوراتور یک تابع را در خود به عنوان ورودی قرار داده است و شئ متفاوتی را به خروجی ارسال می‌کند. در حقیقت دکوراتور ما همواره یک تابع را به ورودی می‌فرستد و همواره عدد ۵ را برمی‌گرداند. 

اما وقتی که دکوراتور را به صورت زیر اعمال بکنیم دچار خطا می‌شویم:

@my_decorator

def hello():
    print('hello')

پیغام خطا:

>>> hello()

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
'int' object is not callable

دلیل اصلی این است که دکوراتور ما همواره یک عدد int را بر می‌گرداند و این مورد قابل فراخوانی یا Callable نیست. نمی‌توان آن را مانند یک تابع فراخوانی کنیم.  برای جلوگیری از این مشکل شما باید چیزی را به خروجی بفرستید که در واقع خود یک تابع باشد. بنابراین شئ که خود دکوراتور بر می‌گرداند باید یک تابع باشد. 

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

def mydecorator(f):  # f is the function passed to us from python
    def log_f_as_called():
        print(f'{f} was called.')
        f()
    return log_f_as_called

همانطور که می‌توانیم مشاهده کنیم ما توابع را به صورت تو در تو تعریف کردیم و تابع mydecorator خروجی تابع داخلی را ارسال می‌کند. 

حال به مثال قبلی‌مان باز گردیم. با پیاده‌سازی چنین حالتی می‌توانیم تابع hello قبلی را فراخوانی کنیم.

@mydecorator

def hello():
    print('hello')

با اجرای این کد خروجی زیر را دریافت خواهیم کرد:

<function hello at 0x7f27738d7510> was called.
hello

ترکیب کردن درست توابع 

یک تابع را می‌توان به تعدادی نامحدود دکورات کنیم. در این حالت دکوراتورها زنجیره‌ای از تاثیرات/تغییرات را دریافت می‌کنند. در این صورت تاثیرات دکوراتورها روی توابع بعد از خودشان به صورت زنجیره وار اعمال می‌شود و این روند تا پایان ادامه دارد. به کد زیر توجه کنید:

@a
@b
@c
def hello():
    print('hello')

در حقیقت این مورد فرمول hello = a(b(c(hello))) را اجرا می‌کند. می‌توانید این کار را به صورت زیر نیز انجام دهید:

@mydecorator

@mydecorator

def hello():

    print('hello')

>>> hello()

<function mydec.<locals>.a at 0x7f277383d378> was called.

<function hello at 0x7f2772f78ae8> was called.

hello

براساس خروجی مطمئنا متوجه شده‌اید که اولین دکوراتور روی دومین مورد تاثیر گذاشته و خط جداگانه‌ای به صورت مستقل چاپ کرده است.

مقالات پیشنهادی

توسعه وب پایتون: جانگو در مقابل فلَسک در سال ۲۰۱۸

در دنیای توسعه پایتون، چندین نمونه از فریمورک‌ها و کتابخانه‌ها وجود دارد که در سال‌های اخیر رشد بالایی داشته‌اند، برای مثال می‌توان به Bottle و Cherry...

Import به صورت Relative و Absolute در پایتون

اگر قبلا روی یک پروژه پایتونی که شامل بیشتر از یک فایل می‌شود کار کرده باشید پس یقین دارم که از دستور import زیاد استفاده کرده‌اید و مجبور بوده‌اید که...

ساخت سرورپایتون روی رزبری پای

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

با پایتون می‌توانید چکارهایی را انجام دهید؟

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