در این مقاله، مفهوم دکوراتور در پایتون در قالب یک سیستم توصیه گر توضیح داده خواهد شد. به این منظور، تصور کنید که شما برای خدمات خردهفروشی کار میکنید. دادههای تاریخی تراکنش موجود بوده و شما برای ساخت فیلترینگ مشارکتی (Collaborative Filtering) به آنها نیاز دارید.
مانند هر پروژهای، گام اول شما بررسی و پاکسازی دادهها خواهد بود. شما احتمالا به دنبال تراکنشهایی هستید که به نظر نامعمول میآیند و میخواهید این تراکنشها را از دادهها حذف کنید. ممکن است بخواهید همه شناسههای تراکنشی که حذف میکنید را نگهداری کنید تا بتوانید آنها را با مشتری در میان بگذارید.
شما میتوانید کدی بنویسید که تمام شناسههای منحصربهفرد تراکنش موجود در مجموعه داده شما را لیست کند، سپس عملیات فیلترینگ انجام دهد و دوباره تمام شناسههای منحصربهفرد محصول (item) را لیست کند و سپس تفاوت بین این دو لیست را پیدا کند.
کد به شکل زیر است:
# define a filtering function def filtering_func(df): # apply some filtering # return filtered_dataframe # list the transaction ID's original_trans_set = set(df['trans_id']) # apply the filtering function df = filtering_func(df) # list the transaction ID's that are left over post_filtering_trans_set = set(df['trans_id']) # take the difference to find what ID's were dropped filtered_trans = original_trans_set - post_filtering_trans_set
برای جلوگیری از تکرار کد، بهتر است یک تابع تعریف کنید که عمل فیلتر را انجام داده و شناسههای تراکنش مشکوک را برگرداند. بهتر است از یک الگوی برنامهنویسی چندگانه (Multitier architecture) استفاده کنید و لایه بیزنس (Business layer) را جدا کنید تا هر بار که نیاز به فیلتر کردن دارید، کافی است تابع مربوط به فیلترکردن را در لایه بیزنس صدا کنید. با این کار، هر بار که بخواهید فرایند فیلترکردن را تغییر دهید یا قابلیت جدیدی اضافه کنید، کافی است در تابع مربوطه تغییرات را اعمال کنید و نیازی نیست کد فیلترکردن را در سراسر کد خود تغییر دهید.
شما میتوانید برای هر گام فیلترینگ یک تابع بنویسید، اما در این صورت باید هر بار کد را دوباره بنویسید. بهتر است که تابع را به یک قالب تبدیل کنید که میتواند گزینههای مختلف فیلترینگ را اجرا کند.
دکوراتورها (Decorators ) به کمک ما میآیند!
این دقیقا جایی است که دکوراتورها استفاده می شوند! دکوراتور یک تابع را تنظیم می کند و رفتار آن را به دلخواه تغییر میدهد.
در اینجا، تابعی که میخواهیم پوشش دهیم، تابع فیلترینگ (filtering_func) است. میتوانیم با تعریف یک دکوراتور، رفتار توصیف شده بالا را روی این تابع اعمال کنیم.
دکوراتور به همان شکلی که تابع تعریف میشود، تعریف میشود و سپس در تعریف توابع دیگر فراخوانی میشود. پیادهسازی سادهشده این کد با استفاده از یک دکوراتور به این صورت است:
def tracking_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): df = kwargs['data'] original_trans_set = set(df['trans_id']) df = func(*args, **kwargs) post_filtering_trans_set = set(df['trans_id']) filtered_trans = list(original_trans_set - post_filtering_trans_set) return df, filtered_trans return wrapper @tracking_decorator def filtering_func(data): # apply some filtering # return filtered_dataframe
حالا هر زمان از تابع filtering_func استفاده کنیم، تابع tracking به طور خودکار در آن تعبیه شده است!
میتوانیم یک تابع فیلترینگ برای حذف مقادیر null، حذف پرت، حذف مقادیر نامعتبر یا فیلتر کردن بر اساس تاریخ ایجاد کنیم – هر رفتاری که میخواهیم – و دکوراتور tracking_decorator@ مطمئن میشود که در هر گام فیلترینگ تغییراتی که رخ میدهد را ردیابی کنیم.
جزئیات بیشتر
این توضیح خیلی خلاصه بود، بنابراین بیایید کمی بیشتر به کد بپردازیم تا اطمینان حاصل کنیم که میتوانیم آن را به خوبی بکار ببریم.
در مثال بالا چند نکته مهم وجود دارد:
استفاده از functools.wraps(func)@ در صورتی حیاتی است که قصد دارید از تابع داخلی به وظایف دکوراتور دسترسی داشته باشید. به عنوان مثال، اگر میخواهید بتوانید تابع ()filtering_func را فراخوانی کرده و نام ستون شناسه تراکنش را به عنوان آرگومان به تابع دکوراتور منتقل کنید، باید از functools.wraps(func)@ استفاده کنید.
args * و kwargs ** دو مفهوم بسیار قدرتمند در پایتون هستند. اگر با آنها آشنایی ندارید، میتوانید در مورد آنها اینجا بخوانید.
خط df = func(*args, **kwargs) به طور عمده در هر پیادهسازی از هر قابلیت دکوراتوری تغییر نخواهد کرد. ممکن است شما شکل این متغیر را تغییر دهید، اما هدف کلی یک دکوراتور، باقی گذاشتن فراخوانی تابع به گونهای کلی است که برای پذیرش هر تابعی مناسب باشد.
مثال کامل
بیایید با یک مثال با برخی از دادههای نمونه آشنا شویم.
ابتدا باید دادههای نمونه خود را تعریف کنیم:
import pandas as pd transaction_ids = [101, 102, 103, 104, 105] item_ids = ['shirts', 'socks', 'jeans', 'socks', 'shirts'] sale_amts = [25, 12, 32, None, 20] df = pd.DataFrame({'trans_id': transaction_ids, 'item_id': item_ids, 'sale_amt': sale_amts})
ما عمدتاً یک مقدار null را در دادههای ما معرفی کردهایم تا برخی از قابلیتها را نشان دهیم.
حالا بیایید دکوراتور و تابع فیلترینگ ما را تعریف کنیم:
import functools def tracking_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): df = kwargs['data'] original_trans_set = set(df['trans_id']) df = func(*args, **kwargs) post_filtering_trans_set = set(df['trans_id']) filtered_trans = list(original_trans_set - post_filtering_trans_set) return df, filtered_trans return wrapper @tracking_decorator def remove_nulls(data): return data.dropna()
حالا وقتی تابع remove_nulls را اجرا میکنیم، به طور خودکار منطق tracking ما فراخوانی میشود و همچنین یک فریم داده فیلتر شده و یک لیست شناسههای تراکنش حذف شده برگردانده می شود.
df, dropped_transactions = remove_nulls(data=df)
مهندس رضا استادی
مدیرعامل شرکت دانش بنیان فن آوران گیتی افروز