دکوریتور (decorator) ها در پایتون (python)

سلام دوستان، خیلی وقت بود که هیچ پستی ننوشته بودم، چند هفته ای مسافرت و این یه هفته رو هم درگیر کار های مدرسه بودم. امروز توی این پست قراره درباره decorator های پایتون بهتون یاد بدم. decorator ها چیز زیاد سختی نیستن اما خیلی گیج کننده اند.قبل از اینکه یه راست وارد مبحث decorator ها بشیم. فکر می کنم بهتره با چند تا چیز آشنا بشیم: 1. در پایتون، شما می تونید توابع رو به عنوان پارامتر به یک تابع دیگه بدید مثل اینdef run(function): return function() def someFunction(): return &quotHello World!&quot results = run(someFunction) print (results) # expected output: Hello World!اگر همین الان این کد رو اجرا کنید، Hello World! براتون چاپ میشه2. در پایتون، شما می تونید تابعی رو در نتیجه تابعی دیگر برگردونید مثل اینdef mainFunction(): def internalFunction(): return &quotHello World&quot return internalFunction # note the lack of parenthesis ( ) f = mainFunction() # returns a new callable results = f() # returns &quotHello World&quot print (results) # expected output: Hello Worldالبته در این خط از کدf = mainFunction() results = f()تابع f به نظر اضافه میاد. از اونجایی که پایتون کاملا منطقیه بهتون اجازه این کار رو میده که f رو حذف کنیدresults = mainFunction()() # double parenthesis ()() گیج نشید، این خط کد خلاصه شده کد های بالاست، چون با صدا زدن تابع mainFunction، تابعی دیگر به وجود میاد، می تونیم دوباره از ( ) برای صدا زدن اون تابع جدید که به وجود اومده استفاده کنیمخیلی خوب دیگه کافیه، هر چیزی که باید می دونستید رو بهتون گفتم، حالا آماده اید که با decorator ها مواجه بشیددکوریتور (decorator) ها در پایتون به چه دردی می خورند؟شاید این اولین سوالی باشه که براتون پیش اومده.گاهی اوقات شما ممکنه بخواهید که یه سری کار رو قبل از صدا زدن تابع یا بعد از صدا زدنش انجام بدید. حالا توابع زیر رو در نظر بگیریدdef a(List): return [x**2 for x in List] def b(List): return list(map(lambda x: x**2, List))هر دو یک لیست از اعداد رو میگیرن و هر کدوم از اون اعداد رو به توان 2 میرسنونن و لیست جدیدی از اون اعداد رو در نتیجه بر می گردونن. اما هر کدوم با دو روش متفاوت این کار رو می کنن. حالا فرض کنید که می خواهیم ببینیم کدومشون سریع تر عمل می کنه، برای اینکار می تونیم از این روش استفاده کنیمimport time def a(List): start_time = time.perf_counter() results = [x**2 for x in List] end_time = time.perf_counter() elapsed_time = end_time - start_time print (elapsed_time) return results def b(List): start_time = time.perf_counter() results = list(map(lambda x: x**2, List)) end_time = time.perf_counter() elapsed_time = end_time - start_time print (elapsed_time) return resultsهمون طور که می بینید، تقریبا همه چیز ثابته به غیر از قسمتی که قراره مقداری رو به متغیر results اختصاص بدیم، پس ما داریم یه کار رو دوبار تکرار می کنیم. توی این مثال که فقط 2 تابع داریم زیاد کار سختی نیست اما وقتی 20 تا داشته باشیم قضیه فرق می کنه (خدایی نکرده یه مشکلی پیش بیاد باید 20 تا تابع رو باز نویسی کنید خخخ)چگونه یک decorator بنویسیمبیاید فرض کنیم که decorator فرضی به نام counter داریم. از این دکوریتور به این شکل استفاده میشه@counter # use @ followed by the name of the decorator def a(List): return [x**2 for x in List] @counter def b(List): return list(map(lambda x: x**2, List))حالا اگه شما همچین کدی رو بنویسیدb([1,2,3])اتفاقی که می افته اینه که مفسر اون رو به این کد تبدیل می کنهcounter (b) ( [1,2,3] )حالا شما هر طوری که دلت خواست می تونی counter رو پیاده سازی کنی ولی این خط کد باید به درستی در موردش اجرا بشه. خود decorator که در این مورد counter هستش، تابعی که دکوریت شده (decorated function) رو میگیره و باید یک callable (چیزی که بشه با ( ) صداش زد) بر گردونه. callable دوم که حاصل صدا زدن counter هستش، آرگومان های تابعی که دکوریت شده رو میگیره که در این مورد لیستی شامل اعداد 1 و 2 و 3 بهش داده میشه. می تونید تابع counter رو اینطور پیاده کنیدdef counter(function): # function = the decorated function which is `b` def wrapper(the_list_of_numbers): # the_list_of_numbers = [1, 2, 3] start = time.perf_counter() results = function(the_list_of_numbers) end = time.perf_counter() elapsed = end - start print (f&quotelapsed time: {elapsed}&quot) return results return wrapperاما این دکوریتوری که نوشتیم فقط برای توابعی که یک آرگومان دارند جواب میده. برای اینکه این دکوریتور رو عمومی ترش کنیم می تونیم این کد رو بنویسیم:def counter(function): def wrapper(*args, **kwargs): start = time.perf_counter() results = function(*args, **kwargs) end = time.perf_counter() elapsed = end - start print (f&quotelapsed time: {elapsed}&quot) return results return wrapperحالا این دکوریتور رو می تونیم روی هر تابعی استفاده کنیم چون دیگه از لحاظ تعداد آرگومان ها محدودیتی نداریم.هنوز چند تا چیز دیگه راجب decorator ها مونده ولی از اونجایی که فعلا نمی خوام این پست رو طولانی کنم، اون ها رو توی پست بعدیم می گمخیلی ممنون که خوندید و امیدوارم که مفید واقع شده باشه