این بدان معناست که هر زمان که نمونه_function را فراخوانی می کنیم، اساساً تابع add_one تعریف شده در دکوراتور را فراخوانی می کنیم.
هنگام استفاده از دکوراتورها، ممکن است بخواهیم تابع تزئین شده زمانی که از تابع wrapper فراخوانی می شود، آرگومان ها را دریافت کند.
به عنوان مثال، اگر تابعی داشته باشیم که به دو پارامتر نیاز دارد و مجموع آنها را برمی گرداند:
def add(a,b): return a + b print(add(1,2)) # 3
و اگر از دکوراتور استفاده کنیم که 1 به خروجی اضافه کند:
def add_one_decorator(func): def add_one(): value = func() return value + 1 return add_one @add_one_decorator def add(a,b): return a + b add(1,2) # TypeError: add_one_decorator.<locals>.add_one() takes 0 positional arguments but 2 were given
هنگام انجام این کار، با یک خطا مواجه می شویم: تابع wrapper (add_one) هیچ آرگومان نمی گیرد اما ما دو آرگومان ارائه می دهیم.
برای رفع این مشکل، باید هر آرگومان دریافتی از add_one را هنگام فراخوانی آن به تابع تزئین شده منتقل کنیم:
def add_one_decorator(func): def add_one(*args, **kwargs): value = func(*args, **kwargs) return value + 1 return add_one @add_one_decorator def add(a,b): return a+b print(add(1,2)) # 4
ما از *args و **kwargs استفاده می کنیم تا نشان دهیم که تابع add_one wrapper باید بتواند هر مقدار از آرگومان های موقعیتی (args) و آرگومان های کلمه کلیدی (kwargs) را دریافت کند.
args لیستی از تمام کلمات کلیدی موقعیتی داده شده خواهد بود، در این مورد [1،2].
kwargs یک فرهنگ لغت با کلیدها به عنوان آرگومان های کلیدواژه مورد استفاده و مقادیر به عنوان مقادیر اختصاص داده شده به آنها، در این مورد یک فرهنگ لغت خالی خواهد بود.
نوشتن func(*args، **kwargs) نشان میدهد که میخواهیم func را با همان آرگومانهای موقعیتی و کلیدواژهای که دریافت شد، فراخوانی کنیم.
این تضمین می کند که همه آرگومان های موقعیتی و کلیدواژه ارسال شده به تابع تزئین شده به تابع اصلی منتقل می شوند.
چرا دکوراتورها در پایتون مفید هستند؟ نمونه کد واقعی
اکنون که نگاهی به دکوراتورهای پایتون انداختیم، بیایید چند نمونه واقعی از مفید بودن دکوراتورها را ببینیم.
ورود به سیستم
هنگام ساخت برنامه های بزرگتر، اغلب مفید است که گزارش هایی از عملکردهایی که با اطلاعات اجرا شده اند، مانند اینکه چه آرگومان هایی استفاده شده اند، و چه تابعی در طول زمان اجرای برنامه بازگردانده است، مفید است.
این میتواند برای عیبیابی و اشکالزدایی در مواقعی که مشکل پیش میآید بسیار مفید باشد، تا به شما کمک کند تا مشخص کنید مشکل از کجا منشأ گرفته است. حتی اگر برای اشکال زدایی نباشد، ورود به سیستم می تواند برای نظارت بر وضعیت برنامه شما مفید باشد.
در اینجا یک مثال ساده از اینکه چگونه می توانیم یک لاگر ساده (با استفاده از بسته لاگ داخلی پایتون) ایجاد کنیم تا اطلاعات مربوط به برنامه خود را در حین اجرا در فایلی به نام main.log ذخیره کنیم:
ثبت ورود به سیستم
import logging def function_logger(func): logging.basicConfig(level = logging.INFO, filename="main.log") def wrapper(*args, **kwargs): result = func(*args, **kwargs) logging.info(f"{func.__name__} ran with positional arguments: {args} and keyword arguments: {kwargs}. Return value: {result}") return result return wrapper @function_logger def add_one(value): return value + 1 print(add_one(1))
هر زمان که تابع add_one اجرا شود، یک گزارش جدید به فایل main.log اضافه می شود:
INFO:root:add_one ran with positional arguments: (1,) and keyword arguments: {}. Return value: 2
Cashing
اگر برنامه ای داشته باشیم که نیاز به اجرای یک تابع چندین بار با آرگومان های یکسان داشته باشد و مقدار یکسان را برگرداند، می تواند به سرعت ناکارآمد شود و منابع غیر ضروری را اشغال کند.
برای جلوگیری از این امر، ذخیره آرگومان های استفاده شده و مقدار بازگشتی تابع در هر زمان که فراخوانی می شود، می تواند مفید باشد و اگر قبلاً تابع را با همان آرگومان ها فراخوانی کرده ایم، به سادگی از مقدار برگشتی مجددا استفاده کنیم.
در پایتون، این را می توان با استفاده از دکوراتور @lru_cache از ماژول functools که با پایتون نصب می شود، پیاده سازی کرد.
LRU به Least Recently Used اشاره دارد، به این معنی که هر زمان که تابع فراخوانی شد، آرگومان های استفاده شده و مقدار بازگشتی ذخیره می شوند. اما به محض اینکه تعداد این ورودی ها به حداکثر اندازه رسید که به طور پیش فرض 128 است، کمترین ورودی اخیر حذف خواهد شد.
from functools import lru_cache @lru_cache def fibonacci(n): if n <= 1: return n return fibonacci(n - 1) + fibonacci(n - 2)
در این مثال، تابع فیبوناچی آرگومان n را می گیرد و اگر کمتر از 1 باشد، n را برمی گرداند، در غیر این صورت مجموع تابع فراخوانی شده با n-1 و n-2 را برمی گرداند.
بنابراین، اگر تابع با n=10 فراخوانی شود، 55 را برمی گرداند:
print(fibnoacci(10)) # 55
در این حالت وقتی تابع fibonacci(10) را صدا می زنیم، تابع fibonacci(9) و fibonacci(8) را صدا می کند و به همین ترتیب تا زمانی که به 1 یا 0 برسد.
اگر بخواهیم از این تابع بیش از یک بار استفاده کنیم:
fibonacci(50) fibonacci(100)
ما می توانیم از حافظه پنهان ورودی هایی که ذخیره شده اند استفاده کنیم. بنابراین وقتی فیبوناچی (50) را صدا می زنیم، می تواند پس از رسیدن به 10، فراخوانی تابع فیبوناچی را متوقف کند و هنگامی که ما فیبوناچی (100) را صدا می زنیم، می تواند پس از رسیدن به 50، فراخوانی تابع را متوقف کند و برنامه را بسیار کارآمدتر می کند.
این مثالها یک چیز مشترک دارند و آن این است که پیادهسازی آنها برای توابع از قبل موجود در پایتون بسیار آسان است. نیازی نیست کد خود را تغییر دهید یا به صورت دستی تابع خود را در دیگری بپیچید.
امکان استفاده ساده از نحو @، استفاده از ماژول ها و بسته های اضافی را آسان می کند.
خلاصه
دکوراتورهای پایتون این امکان را به شما می دهد که بدون هیچ زحمتی عملکردها را بدون نیاز به تغییر آنها گسترش دهید. در این آموزش، نحوه کار دکوراتورها را یاد گرفتید و نمونه هایی از موارد استفاده از آنها را مشاهده کردید.
اگر از محتوای آموزشی من لذت می برید، بلاگ من را برای محتوای بیشتر پایتون بررسی کنید.
همیشه در حال یادگیری. پیروز و برقرار باشید - سعید دامغانیان