اشکان محمدی
اشکان محمدی
خواندن ۶ دقیقه·۴ سال پیش

دکوریتور (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 ها مونده ولی از اونجایی که فعلا نمی خوام این پست رو طولانی کنم، اون ها رو توی پست بعدیم می گم

خیلی ممنون که خوندید و امیدوارم که مفید واقع شده باشه

pythonپایتوندکوریتورdecoratorآموزش
یه برنامه نویس ساده که از تجربیات و آموخته هاش می نویسه
شاید از این پست‌ها خوشتان بیاید