دکوراتورها در پایتون | به زبان ساده
در این مطلب، دکوراتورها در پایتون (Python Decorators) مورد بررسی قرار میگیرند و با ارائه مثالهایی، مفهوم، چگونگی استفاده و کاربرد آنها بیان میشود. یک دکوراتور، یک تابع را دریافت کرده، کارکردهایی را به آن میافزاید و آن را باز میگرداند. در ادامه مطلب دکوراتورها در پایتون، چگونگی ساخت یک دکوراتور و چرایی استفاده از آن بیان شده است.
مفهوم دکوراتورها در پایتون
پایتون دارای یک قابلیت جالب با عنوان «دکوراتور» (Decorators) برای افزایش کارایی یک کد موجود است. به این کار، «متاپروگرمینگ» (Metaprogramming) نیز گفته میشود، زیرا با بهرهگیری از آن، یک بخش از برنامه تلاش میکند تا بخش دیگری از برنامه را در زمان اجرا ویرایش کند.
پیشنیازهای لازم برای یادگیری دکوراتورها
به منظور درک دکوراتورها، ابتدا باید برخی از مفاهیم پایهای را در «زبان برنامهنویسی پایتون» (Python Programming Language) دانست. مسالهای که باید به خوبی آن را درک کرد این است که همه چیز در پایتون (بله؛ همه چیز، حتی کلاسها) شی (Object) هستند. اسامی که تعریف میشوند، شناساگرهایی (Identifiers) هستند که به این اشیا گره خوردهاند. توابع نیز از این قاعده مستثنی نیستند و آنها نیز شی هستند (با خصیصهها).
اسامی متفاوت گوناگون را میتوان به شی تابع مشابهی تخصیص داد.
1def first(msg):
2 print(msg)
3
4first("Hello")
5
6second = first
7second("Hello")
هنگامی که کد بالا اجرا میشود، هر دو تابع first و second خروجی مشابهی خواهند داشت. در اینجا، نام first و second به شی تابع مشابهی ارجاع دارند. اکنون، همه چیز عجیبتر میشود. تابعها را میتوان به عنوان آرگومان به تابع دیگر ارجاع داد. اگر کاربر از توابعی مانند filter ،map و reduce در پایتون استفاده کرده باشد، با این موضوع آشنایی دارد. چنین توابعی که تابع دیگری را به عنوان آرگومان دریافت میکنند «توابع مرتبه بالاتر» (Higher Order Functions) خوانده میشوند. در ادامه، مثالی از چنین توابعی ارائه شده است.
1def inc(x):
2 return x + 1
3
4def dec(x):
5 return x - 1
6
7def operate(func, x):
8 result = func(x)
9 return result
توابع، به صورت زیر فراخوانی میشوند.
>>> operate(inc,3) 4 >>> operate(dec,3) 2
علاوه بر آن، یک تابع میتواند تابع دیگری را با استفاده از تابع Return در پایتون بازگرداند.
1def is_called():
2 def is_returned():
3 print("Hello")
4 return is_returned
5
6new = is_called()
7
8#Outputs "Hello"
9new()
در اینجا، ()is_returned یک تابع تو در تو است که هر بار ()is_called فراخوانی شود، تعریف و بازگردانده میشود.
دکوراتورها در پایتون
توابع و متدها در پایتون «قابل فراخوانی» (Callable) نامیده میشوند زیرا همانطور که از نام آنها پیداست، میتوان آنها را فایل فراخوانی کرد. در حقیقت، هر شیئی که متد ویژه ()__call__ را پیادهسازی کند، قابل فراخوانی محسوب میشود.
بنابراین، در پایهایترین حالت، یک دکوراتور یک قابل فراخوانی است که یک قابل فراخوانی را باز میگرداند. به طور پایهای، یک دکوراتور تابع را دریافت میکند، کارکردهایی را به آن اضافه میکند و آن را باز میگرداند.
1def make_pretty(func):
2 def inner():
3 print("I got decorated")
4 func()
5 return inner
6
7def ordinary():
8 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) خاصی برای ساده کردن آن است. میتوان از نماد @ همراه با نام تابع دکوراتر استفاده کرد و آن را بالای تعریف تابع برای دکوریت شدن قرار داد. برای مثال:
1@make_pretty
2def ordinary():
3 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
اکنون، دکوراتوری برای بررسی این مورد که منجر به خطا میشود، ایجاد شده است.
1def smart_divide(func):
2 def inner(a,b):
3 print("I am going to divide",a,"and",b)
4 if b == 0:
5 print("Whoops! cannot divide")
6 return
7
8 return func(a,b)
9 return inner
10
11@smart_divide
12def divide(a,b):
13 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
زنجیر کردن دکوراتورها در پایتون
چندین دکوراتور را میتوان در پایتون به یکدیگر زنجیر کرد. این یعنی یک تابع را میتوان چندین بار با دکوراتورهای گوناگون دکوریت کرد.
به سادگی میتوان دکوراتورها را بالای تابع مورد نظر قرار داد.
1def star(func):
2 def inner(*args, **kwargs):
3 print("*" * 30)
4 func(*args, **kwargs)
5 print("*" * 30)
6 return inner
7
8def percent(func):
9 def inner(*args, **kwargs):
10 print("%" * 30)
11 func(*args, **kwargs)
12 print("%" * 30)
13 return inner
14
15@star
16@percent
17def printer(msg):
18 print(msg)
19printer("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 ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
سلام توضیحاتتون بسیار عالی و مثالهای بسیار خوب ، واضح و قابل فهم
ممنون از لطف شما
سلام ، ممنون از توضیحات شما
خوب بود و درکل مبحث مهم و پراستفاده ای هست//
اما یک مقدار توضیحات ذهنو گیج میکنه، خیلی روان توضیح داده نشده.
ممنونم.
واقعا چنین مطلب سنگینی رو به این صورت بیان کردن احسنت دارد
خیلی خوب توضیح دادید. ممنون . کاربردهای دکوریتورها بیشتر کجا هست؟ مثلا یک نمونه گفتید در رفع خطا مثلا خطای تقسیم بر صفر.
۲ مثال از موارد استفاده یا کاربردهای دکوریتورها:
۱. هنگامی که نیاز دارید رفتار یک تابع رو تغییر بدید، بدون اینکه خود اون تابع رو تغییر بدید. یک مثال ساده: زمانی که میخواید یه تابع ثبت سفارش فقط و فقط در صورتی انجام بشه که کاربر قبلش لاگین کرده.
۲. در مواقعی که میخواید یک کد روی توابع مختلفی اجرا بشه، که این یعنی پیشگیری از اینکه واسه هر فانکشن کدهای تکراری بنویسیم.